diff options
352 files changed, 9049 insertions, 4095 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 70a23cdf106b..93c0c4d7a024 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1139,7 +1139,6 @@ package android.hardware.camera2 { public final class CameraManager { method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException; method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; - field public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; // 0xef10e60L } public abstract static class CameraManager.AvailabilityCallback { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 4a4ba6371939..bab20615cf77 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6787,6 +6787,8 @@ public class DevicePolicyManager { * {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link #ENCRYPTION_STATUS_INACTIVE}, * {@link #ENCRYPTION_STATUS_ACTIVATING}, {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY}, * {@link #ENCRYPTION_STATUS_ACTIVE}, or {@link #ENCRYPTION_STATUS_ACTIVE_PER_USER}. + * + * @throws SecurityException if called on a parent instance. */ public int getStorageEncryptionStatus() { throwIfParentInstance("getStorageEncryptionStatus"); diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 9e5e8deda84b..2e3b5d286138 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1058,6 +1058,41 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id /** + * This change id excludes the packages it is applied to from the camera compat force rotation + * treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context. + * @hide + */ + @ChangeId + @Overridable + @Disabled + public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION = + 263959004L; // buganizer id + + /** + * This change id excludes the packages it is applied to from activity refresh after camera + * compat force rotation treatment. See com.android.server.wm.DisplayRotationCompatPolicy for + * context. + * @hide + */ + @ChangeId + @Overridable + @Disabled + public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id + + /** + * This change id makes the packages it is applied to do activity refresh after camera compat + * force rotation treatment using "resumed -> paused -> resumed" cycle rather than "resumed -> + * ... -> stopped -> ... -> resumed" cycle. See + * com.android.server.wm.DisplayRotationCompatPolicy for context. + * @hide + */ + @ChangeId + @Overridable + @Disabled + public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = + 264301586L; // buganizer id + + /** * This change id is the gatekeeper for all treatments that force a given min aspect ratio. * Enabling this change will allow the following min aspect ratio treatments to be applied: * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 5291d2b73891..7ccf07a2a764 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -29,7 +29,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.ActivityThread; import android.app.AppOpsManager; -import android.app.compat.CompatChanges; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.ImageFormat; @@ -47,7 +46,6 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.renderscript.Allocation; import android.renderscript.Element; import android.renderscript.RSIllegalArgumentException; @@ -284,14 +282,6 @@ public class Camera { */ public native static int getNumberOfCameras(); - private static final boolean sLandscapeToPortrait = - SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false); - - private static boolean shouldOverrideToPortrait() { - return CompatChanges.isChangeEnabled(CameraManager.OVERRIDE_FRONT_CAMERA_APP_COMPAT) - && sLandscapeToPortrait; - } - /** * Returns the information about a particular camera. * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1. @@ -301,7 +291,8 @@ public class Camera { * low-level failure). */ public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) { - boolean overrideToPortrait = shouldOverrideToPortrait(); + boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait( + ActivityThread.currentApplication().getApplicationContext()); _getCameraInfo(cameraId, overrideToPortrait, cameraInfo); IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); @@ -498,7 +489,8 @@ public class Camera { mEventHandler = null; } - boolean overrideToPortrait = shouldOverrideToPortrait(); + boolean overrideToPortrait = CameraManager.shouldOverrideToPortrait( + ActivityThread.currentApplication().getApplicationContext()); return native_setup(new WeakReference<Camera>(this), cameraId, ActivityThread.currentOpPackageName(), overrideToPortrait); } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index be99f0fefee7..ea6f624b77ee 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -115,8 +115,14 @@ public final class CameraManager { @ChangeId @Overridable @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE) - @TestApi - public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; + public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; + + /** + * Package-level opt in/out for the above. + * @hide + */ + public static final String PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT = + "android.camera.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT"; /** * System property for allowing the above @@ -602,7 +608,7 @@ public final class CameraManager { try { Size displaySize = getDisplaySize(); - boolean overrideToPortrait = shouldOverrideToPortrait(); + boolean overrideToPortrait = shouldOverrideToPortrait(mContext); CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId, mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait); try { @@ -722,7 +728,7 @@ public final class CameraManager { "Camera service is currently unavailable"); } - boolean overrideToPortrait = shouldOverrideToPortrait(); + boolean overrideToPortrait = shouldOverrideToPortrait(mContext); cameraUser = cameraService.connectDevice(callbacks, cameraId, mContext.getOpPackageName(), mContext.getAttributionTag(), uid, oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion, @@ -1154,9 +1160,26 @@ public final class CameraManager { return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId); } - private static boolean shouldOverrideToPortrait() { - return CompatChanges.isChangeEnabled(OVERRIDE_FRONT_CAMERA_APP_COMPAT) - && CameraManagerGlobal.sLandscapeToPortrait; + /** + * @hide + */ + public static boolean shouldOverrideToPortrait(@Nullable Context context) { + if (!CameraManagerGlobal.sLandscapeToPortrait) { + return false; + } + + if (context != null) { + PackageManager packageManager = context.getPackageManager(); + + try { + return packageManager.getProperty(context.getOpPackageName(), + PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + // No such property + } + } + + return CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT); } /** diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index a6c79b3a289f..0c2468e65577 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -87,6 +87,7 @@ public class CameraDeviceImpl extends CameraDevice // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) private ICameraDeviceUserWrapper mRemoteDevice; + private boolean mRemoteDeviceInit = false; // Lock to synchronize cross-thread access to device public interface final Object mInterfaceLock = new Object(); // access from this class and Session only! @@ -338,6 +339,8 @@ public class CameraDeviceImpl extends CameraDevice mDeviceExecutor.execute(mCallOnOpened); mDeviceExecutor.execute(mCallOnUnconfigured); + + mRemoteDeviceInit = true; } } @@ -1754,8 +1757,8 @@ public class CameraDeviceImpl extends CameraDevice } synchronized(mInterfaceLock) { - if (mRemoteDevice == null) { - return; // Camera already closed + if (mRemoteDevice == null && mRemoteDeviceInit) { + return; // Camera already closed, user is not interested in errors anymore. } // Redirect device callback to the offline session in case we are in the middle diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index dba1a5e8dfc6..6a667fe39974 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -251,6 +251,10 @@ public final class DeviceStateManager { @Nullable private Boolean lastResult; + public FoldStateListener(Context context) { + this(context, folded -> {}); + } + public FoldStateListener(Context context, Consumer<Boolean> listener) { mFoldedDeviceStates = context.getResources().getIntArray( com.android.internal.R.array.config_foldedDeviceStates); @@ -266,5 +270,10 @@ public final class DeviceStateManager { mDelegate.accept(folded); } } + + @Nullable + public Boolean getFolded() { + return lastResult; + } } } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index e1d15defad38..125bdaf07b90 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -74,6 +74,7 @@ interface IUserManager { String getUserAccount(int userId); void setUserAccount(int userId, String accountName); long getUserCreationTime(int userId); + int getUserSwitchability(int userId); boolean isUserSwitcherEnabled(int mUserId); boolean isRestricted(int userId); boolean canHaveRestrictedProfile(int userId); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 5487a1203833..aa0ac3187996 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -58,7 +58,6 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.location.LocationManager; import android.provider.Settings; -import android.telephony.TelephonyManager; import android.util.AndroidException; import android.util.ArraySet; import android.util.Log; @@ -595,8 +594,11 @@ public class UserManager { /** * Specifies if a user is disallowed from transferring files over USB. * - * <p>This restriction can only be set by a device owner, a profile owner on the primary - * user or a profile owner of an organization-owned managed profile on the parent profile. + * <p>This restriction can only be set by a <a href="https://developers.google.com/android/work/terminology#device_owner_do"> + * device owner</a> or a <a href="https://developers.google.com/android/work/terminology#profile_owner_po"> + * profile owner</a> on the primary user's profile or a profile owner of an organization-owned + * <a href="https://developers.google.com/android/work/terminology#managed_profile"> + * managed profile</a> on the parent profile. * When it is set by a device owner, it applies globally. When it is set by a profile owner * on the primary user or by a profile owner of an organization-owned managed profile on * the parent profile, it disables the primary user from transferring files over USB. No other @@ -1670,7 +1672,7 @@ public class UserManager { public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 1 << 2; /** - * Result returned in {@link #getUserSwitchability()} indicating user swichability. + * Result returned in {@link #getUserSwitchability()} indicating user switchability. * @hide */ @Retention(RetentionPolicy.SOURCE) @@ -2037,25 +2039,16 @@ public class UserManager { * @hide */ @Deprecated - @RequiresPermission(allOf = { - Manifest.permission.READ_PHONE_STATE, - Manifest.permission.MANAGE_USERS}, // Can be INTERACT_ACROSS_USERS instead. - conditional = true) + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @UserHandleAware public boolean canSwitchUsers() { - boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0; - boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM); - boolean inCall = false; - TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); - if (telephonyManager != null) { - inCall = telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE; + try { + return mService.getUserSwitchability(mUserId) == SWITCHABILITY_STATUS_OK; + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } - boolean isUserSwitchDisallowed = hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId); - return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall - && !isUserSwitchDisallowed; } /** @@ -2089,34 +2082,14 @@ public class UserManager { * @return A {@link UserSwitchabilityResult} flag indicating if the user is switchable. * @hide */ - @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE, - android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) { - final TelephonyManager tm = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - - int flags = SWITCHABILITY_STATUS_OK; - if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) { - flags |= SWITCHABILITY_STATUS_USER_IN_CALL; - } - if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, userHandle)) { - flags |= SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED; - } - - // System User is always unlocked in Headless System User Mode, so ignore this flag - if (!isHeadlessSystemUserMode()) { - final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0; - final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM); - - if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) { - flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED; - } + try { + return mService.getUserSwitchability(userHandle.getIdentifier()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); } - - return flags; } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 94a6382227f3..b21187af1271 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9819,11 +9819,10 @@ public final class Settings { "fingerprint_side_fps_auth_downtime"; /** - * Whether or not a SFPS device is required to be interactive for auth to unlock the device. + * Whether or not a SFPS device is enabling the performant auth setting. * @hide */ - public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED = - "sfps_require_screen_on_to_auth_enabled"; + public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled"; /** * Whether or not debugging is enabled. diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 4b25c8832068..182a49758892 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -52,7 +52,7 @@ public final class Adjustment implements Parcelable { /** @hide */ @StringDef (prefix = { "KEY_" }, value = { KEY_CONTEXTUAL_ACTIONS, KEY_GROUP_KEY, KEY_IMPORTANCE, KEY_PEOPLE, KEY_SNOOZE_CRITERIA, - KEY_TEXT_REPLIES, KEY_USER_SENTIMENT + KEY_TEXT_REPLIES, KEY_USER_SENTIMENT, KEY_IMPORTANCE_PROPOSAL }) @Retention(RetentionPolicy.SOURCE) public @interface Keys {} @@ -122,6 +122,19 @@ public final class Adjustment implements Parcelable { public static final String KEY_IMPORTANCE = "key_importance"; /** + * Weaker than {@link #KEY_IMPORTANCE}, this adjustment suggests an importance rather than + * mandates an importance change. + * + * A notification listener can interpet this suggestion to show the user a prompt to change + * notification importance for the notification (or type, or app) moving forward. + * + * Data type: int, one of importance values e.g. + * {@link android.app.NotificationManager#IMPORTANCE_MIN}. + * @hide + */ + public static final String KEY_IMPORTANCE_PROPOSAL = "key_importance_proposal"; + + /** * Data type: float, a ranking score from 0 (lowest) to 1 (highest). * Used to rank notifications inside that fall under the same classification (i.e. alerting, * silenced). diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index ad2e9d510998..dc4cb9f09835 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1711,6 +1711,8 @@ public abstract class NotificationListenerService extends Service { private ShortcutInfo mShortcutInfo; private @RankingAdjustment int mRankingAdjustment; private boolean mIsBubble; + // Notification assistant importance suggestion + private int mProposedImportance; private static final int PARCEL_VERSION = 2; @@ -1748,6 +1750,7 @@ public abstract class NotificationListenerService extends Service { out.writeParcelable(mShortcutInfo, flags); out.writeInt(mRankingAdjustment); out.writeBoolean(mIsBubble); + out.writeInt(mProposedImportance); } /** @hide */ @@ -1786,6 +1789,7 @@ public abstract class NotificationListenerService extends Service { mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class); mRankingAdjustment = in.readInt(); mIsBubble = in.readBoolean(); + mProposedImportance = in.readInt(); } @@ -1878,6 +1882,22 @@ public abstract class NotificationListenerService extends Service { } /** + * Returns the proposed importance provided by the {@link NotificationAssistantService}. + * + * This can be used to suggest that the user change the importance of this type of + * notification moving forward. A value of + * {@link NotificationManager#IMPORTANCE_UNSPECIFIED} means that the NAS has not recommended + * a change to the importance, and no UI should be shown to the user. See + * {@link Adjustment#KEY_IMPORTANCE_PROPOSAL}. + * + * @return the importance of the notification + * @hide + */ + public @NotificationManager.Importance int getProposedImportance() { + return mProposedImportance; + } + + /** * If the system has overridden the group key, then this will be non-null, and this * key should be used to bundle notifications. */ @@ -2041,7 +2061,7 @@ public abstract class NotificationListenerService extends Service { boolean noisy, ArrayList<Notification.Action> smartActions, ArrayList<CharSequence> smartReplies, boolean canBubble, boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo, - int rankingAdjustment, boolean isBubble) { + int rankingAdjustment, boolean isBubble, int proposedImportance) { mKey = key; mRank = rank; mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; @@ -2067,6 +2087,7 @@ public abstract class NotificationListenerService extends Service { mShortcutInfo = shortcutInfo; mRankingAdjustment = rankingAdjustment; mIsBubble = isBubble; + mProposedImportance = proposedImportance; } /** @@ -2107,7 +2128,8 @@ public abstract class NotificationListenerService extends Service { other.mIsConversation, other.mShortcutInfo, other.mRankingAdjustment, - other.mIsBubble); + other.mIsBubble, + other.mProposedImportance); } /** @@ -2166,7 +2188,8 @@ public abstract class NotificationListenerService extends Service { && Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()), (other.mShortcutInfo == null ? 0 : other.mShortcutInfo.getId())) && Objects.equals(mRankingAdjustment, other.mRankingAdjustment) - && Objects.equals(mIsBubble, other.mIsBubble); + && Objects.equals(mIsBubble, other.mIsBubble) + && Objects.equals(mProposedImportance, other.mProposedImportance); } } diff --git a/core/java/android/text/TextShaper.java b/core/java/android/text/TextShaper.java index a1d6cc8e283a..6da0b63dbc1f 100644 --- a/core/java/android/text/TextShaper.java +++ b/core/java/android/text/TextShaper.java @@ -173,7 +173,7 @@ public class TextShaper { private TextShaper() {} /** - * An consumer interface for accepting text shape result. + * A consumer interface for accepting text shape result. */ public interface GlyphsConsumer { /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 6f4a63ca0fd7..8d52d001da2e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1274,7 +1274,7 @@ public final class ViewRootImpl implements ViewParent, mTmpFrames.attachedFrame = attachedFrame; mTmpFrames.compatScale = compatScale[0]; mInvCompatScale = 1f / compatScale[0]; - } catch (RemoteException e) { + } catch (RemoteException | RuntimeException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index ed9cb00db290..abc49266bb19 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -853,6 +853,143 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; /** + * Activity level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the activity should be excluded from the + * camera compatibility force rotation treatment. + * + * <p>The camera compatibility treatment aligns orientations of portrait app window and natural + * orientation of the device and set opposite to natural orientation for a landscape app + * window. Mismatch between them can lead to camera issues like sideways or stretched + * viewfinder since this is one of the strongest assumptions that apps make when they implement + * camera previews. Since app and natural display orientations aren't guaranteed to match, the + * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to + * camera and is removed once camera is closed. + * + * <p>The camera compatibility can be enabled by device manufacturers on the displays that have + * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed + * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> + * for more details). + * + * <p>With this property set to {@code true} or unset, the system may apply the force rotation + * treatment to fixed orientation activities. Device manufacturers can exclude packages from the + * treatment using their discretion to improve display compatibility. + * + * <p>With this property set to {@code false}, the system will not apply the force rotation + * treatment. + * + * <p><b>Syntax:</b> + * <pre> + * <activity> + * <property + * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION" + * android:value="true|false"/> + * </activity> + * </pre> + * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = + "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; + + /** + * Activity level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the activity should be excluded + * from the activity "refresh" after the camera compatibility force rotation treatment. + * + * <p>The camera compatibility treatment aligns orientations of portrait app window and natural + * orientation of the device and set opposite to natural orientation for a landscape app + * window. Mismatch between them can lead to camera issues like sideways or stretched + * viewfinder since this is one of the strongest assumptions that apps make when they implement + * camera previews. Since app and natural display orientations aren't guaranteed to match, the + * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to + * camera and is removed once camera is closed. + * + * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed -> + * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle + * (if overridden, see {@link #PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE} for context). + * This allows to clear cached values in apps (e.g. display or camera rotation) that influence + * camera preview and can lead to sideways or stretching issues persisting even after force + * rotation. + * + * <p>The camera compatibility can be enabled by device manufacturers on the displays that have + * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed + * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> + * for more details). + * + * <p>With this property set to {@code true} or unset, the system may "refresh" activity after + * the force rotation treatment. Device manufacturers can exclude packages from the "refresh" + * using their discretion to improve display compatibility. + * + * <p>With this property set to {@code false}, the system will not "refresh" activity after the + * force rotation treatment. + * + * <p><b>Syntax:</b> + * <pre> + * <activity> + * <property + * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH" + * android:value="true|false"/> + * </activity> + * </pre> + * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = + "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"; + + /** + * Activity level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the activity should be or shouldn't be + * "refreshed" after the camera compatibility force rotation treatment using "paused -> + * resumed" cycle rather than "stopped -> resumed". + * + * <p>The camera compatibility treatment aligns orientations of portrait app window and natural + * orientation of the device and set opposite to natural orientation for a landscape app + * window. Mismatch between them can lead to camera issues like sideways or stretched + * viewfinder since this is one of the strongest assumptions that apps make when they implement + * camera previews. Since app and natural display orientations aren't guaranteed to match, the + * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to + * camera and is removed once camera is closed. + * + * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed -> + * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle + * (if overridden by device manufacturers or using this property). This allows to clear cached + * values in apps (e.g., display or camera rotation) that influence camera preview and can lead + * to sideways or stretching issues persisting even after force rotation. + * + * <p>The camera compatibility can be enabled by device manufacturers on the displays that have + * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed + * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> + * for more details). + * + * <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed" + * cycle using their discretion to improve display compatibility. + * + * <p>With this property set to {@code true}, the system will "refresh" activity after the + * force rotation treatment using "resumed -> paused -> resumed" cycle. + * + * <p>With this property set to {@code false}, the system will not "refresh" activity after the + * force rotation treatment using "resumed -> paused -> resumed" cycle even if the device + * manufacturer adds the corresponding override. + * + * <p><b>Syntax:</b> + * <pre> + * <activity> + * <property + * android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE" + * android:value="true|false"/> + * </activity> + * </pre> + * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = + "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"; + + /** * @hide */ public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array"; diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index d067d4bc366b..497f0668107f 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -66,8 +66,7 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; /** - * <p>The {@link ContentCaptureManager} provides additional ways for for apps to - * integrate with the content capture subsystem. + * <p>Provides additional ways for apps to integrate with the content capture subsystem. * * <p>Content capture provides real-time, continuous capture of application activity, display and * events to an intelligence service that is provided by the Android system. The intelligence diff --git a/core/java/android/webkit/WebResourceError.java b/core/java/android/webkit/WebResourceError.java index 11f1b6f17566..4c874892d576 100644 --- a/core/java/android/webkit/WebResourceError.java +++ b/core/java/android/webkit/WebResourceError.java @@ -19,7 +19,7 @@ package android.webkit; import android.annotation.SystemApi; /** - * Encapsulates information about errors occured during loading of web resources. See + * Encapsulates information about errors that occurred during loading of web resources. See * {@link WebViewClient#onReceivedError(WebView, WebResourceRequest, WebResourceError) WebViewClient.onReceivedError(WebView, WebResourceRequest, WebResourceError)} */ public abstract class WebResourceError { diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java index 1024e2e50c3e..4a4f561c71ed 100644 --- a/core/java/android/window/BackEvent.java +++ b/core/java/android/window/BackEvent.java @@ -18,10 +18,8 @@ package android.window; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.view.RemoteAnimationTarget; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -52,8 +50,6 @@ public class BackEvent implements Parcelable { @SwipeEdge private final int mSwipeEdge; - @Nullable - private final RemoteAnimationTarget mDepartingAnimationTarget; /** * Creates a new {@link BackEvent} instance. @@ -62,16 +58,12 @@ public class BackEvent implements Parcelable { * @param touchY Absolute Y location of the touch point of this event. * @param progress Value between 0 and 1 on how far along the back gesture is. * @param swipeEdge Indicates which edge the swipe starts from. - * @param departingAnimationTarget The remote animation target of the departing application - * window. */ - public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge, - @Nullable RemoteAnimationTarget departingAnimationTarget) { + public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) { mTouchX = touchX; mTouchY = touchY; mProgress = progress; mSwipeEdge = swipeEdge; - mDepartingAnimationTarget = departingAnimationTarget; } private BackEvent(@NonNull Parcel in) { @@ -79,7 +71,6 @@ public class BackEvent implements Parcelable { mTouchY = in.readFloat(); mProgress = in.readFloat(); mSwipeEdge = in.readInt(); - mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); } public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() { @@ -105,7 +96,6 @@ public class BackEvent implements Parcelable { dest.writeFloat(mTouchY); dest.writeFloat(mProgress); dest.writeInt(mSwipeEdge); - dest.writeTypedObject(mDepartingAnimationTarget, flags); } /** @@ -136,16 +126,6 @@ public class BackEvent implements Parcelable { return mSwipeEdge; } - /** - * Returns the {@link RemoteAnimationTarget} of the top departing application window, - * or {@code null} if the top window should not be moved for the current type of back - * destination. - */ - @Nullable - public RemoteAnimationTarget getDepartingAnimationTarget() { - return mDepartingAnimationTarget; - } - @Override public String toString() { return "BackEvent{" @@ -153,7 +133,6 @@ public class BackEvent implements Parcelable { + ", mTouchY=" + mTouchY + ", mProgress=" + mProgress + ", mSwipeEdge" + mSwipeEdge - + ", mDepartingAnimationTarget" + mDepartingAnimationTarget + "}"; } } diff --git a/core/java/android/window/BackMotionEvent.aidl b/core/java/android/window/BackMotionEvent.aidl new file mode 100644 index 000000000000..7c675c35c073 --- /dev/null +++ b/core/java/android/window/BackMotionEvent.aidl @@ -0,0 +1,22 @@ +/* + * 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 android.window; + +/** + * @hide + */ +parcelable BackMotionEvent; diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java new file mode 100644 index 000000000000..8012a1c26bac --- /dev/null +++ b/core/java/android/window/BackMotionEvent.java @@ -0,0 +1,150 @@ +/* + * 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 android.window; + +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.RemoteAnimationTarget; + +/** + * Object used to report back gesture progress. Holds information about a {@link BackEvent} plus + * any {@link RemoteAnimationTarget} the gesture manipulates. + * + * @see BackEvent + * @hide + */ +public final class BackMotionEvent implements Parcelable { + private final float mTouchX; + private final float mTouchY; + private final float mProgress; + + @BackEvent.SwipeEdge + private final int mSwipeEdge; + @Nullable + private final RemoteAnimationTarget mDepartingAnimationTarget; + + /** + * Creates a new {@link BackMotionEvent} instance. + * + * @param touchX Absolute X location of the touch point of this event. + * @param touchY Absolute Y location of the touch point of this event. + * @param progress Value between 0 and 1 on how far along the back gesture is. + * @param swipeEdge Indicates which edge the swipe starts from. + * @param departingAnimationTarget The remote animation target of the departing + * application window. + */ + public BackMotionEvent(float touchX, float touchY, float progress, + @BackEvent.SwipeEdge int swipeEdge, + @Nullable RemoteAnimationTarget departingAnimationTarget) { + mTouchX = touchX; + mTouchY = touchY; + mProgress = progress; + mSwipeEdge = swipeEdge; + mDepartingAnimationTarget = departingAnimationTarget; + } + + private BackMotionEvent(@NonNull Parcel in) { + mTouchX = in.readFloat(); + mTouchY = in.readFloat(); + mProgress = in.readFloat(); + mSwipeEdge = in.readInt(); + mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); + } + + @NonNull + public static final Creator<BackMotionEvent> CREATOR = new Creator<BackMotionEvent>() { + @Override + public BackMotionEvent createFromParcel(Parcel in) { + return new BackMotionEvent(in); + } + + @Override + public BackMotionEvent[] newArray(int size) { + return new BackMotionEvent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeFloat(mTouchX); + dest.writeFloat(mTouchY); + dest.writeFloat(mProgress); + dest.writeInt(mSwipeEdge); + dest.writeTypedObject(mDepartingAnimationTarget, flags); + } + + /** + * Returns the progress of a {@link BackEvent}. + * + * @see BackEvent#getProgress() + */ + @FloatRange(from = 0, to = 1) + public float getProgress() { + return mProgress; + } + + /** + * Returns the absolute X location of the touch point. + */ + public float getTouchX() { + return mTouchX; + } + + /** + * Returns the absolute Y location of the touch point. + */ + public float getTouchY() { + return mTouchY; + } + + /** + * Returns the screen edge that the swipe starts from. + */ + @BackEvent.SwipeEdge + public int getSwipeEdge() { + return mSwipeEdge; + } + + /** + * Returns the {@link RemoteAnimationTarget} of the top departing application window, + * or {@code null} if the top window should not be moved for the current type of back + * destination. + */ + @Nullable + public RemoteAnimationTarget getDepartingAnimationTarget() { + return mDepartingAnimationTarget; + } + + @Override + public String toString() { + return "BackMotionEvent{" + + "mTouchX=" + mTouchX + + ", mTouchY=" + mTouchY + + ", mProgress=" + mProgress + + ", mSwipeEdge" + mSwipeEdge + + ", mDepartingAnimationTarget" + mDepartingAnimationTarget + + "}"; + } +} diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java index dd4385c8f50c..38c52e7473f1 100644 --- a/core/java/android/window/BackProgressAnimator.java +++ b/core/java/android/window/BackProgressAnimator.java @@ -40,7 +40,7 @@ public class BackProgressAnimator { private final SpringAnimation mSpring; private ProgressCallback mCallback; private float mProgress = 0; - private BackEvent mLastBackEvent; + private BackMotionEvent mLastBackEvent; private boolean mStarted = false; private void setProgress(float progress) { @@ -82,9 +82,9 @@ public class BackProgressAnimator { /** * Sets a new target position for the back progress. * - * @param event the {@link BackEvent} containing the latest target progress. + * @param event the {@link BackMotionEvent} containing the latest target progress. */ - public void onBackProgressed(BackEvent event) { + public void onBackProgressed(BackMotionEvent event) { if (!mStarted) { return; } @@ -95,11 +95,11 @@ public class BackProgressAnimator { /** * Starts the back progress animation. * - * @param event the {@link BackEvent} that started the gesture. + * @param event the {@link BackMotionEvent} that started the gesture. * @param callback the back callback to invoke for the gesture. It will receive back progress * dispatches as the progress animation updates. */ - public void onBackStarted(BackEvent event, ProgressCallback callback) { + public void onBackStarted(BackMotionEvent event, ProgressCallback callback) { reset(); mLastBackEvent = event; mCallback = callback; @@ -129,8 +129,7 @@ public class BackProgressAnimator { } mCallback.onProgressUpdate( new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(), - progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(), - mLastBackEvent.getDepartingAnimationTarget())); + progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge())); } } diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl index 6af8ddda3a62..159c0e8afed0 100644 --- a/core/java/android/window/IOnBackInvokedCallback.aidl +++ b/core/java/android/window/IOnBackInvokedCallback.aidl @@ -17,7 +17,7 @@ package android.window; -import android.window.BackEvent; +import android.window.BackMotionEvent; /** * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager @@ -30,18 +30,19 @@ oneway interface IOnBackInvokedCallback { * Called when a back gesture has been started, or back button has been pressed down. * Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}. * - * @param backEvent The {@link BackEvent} containing information about the touch or button press. + * @param backMotionEvent The {@link BackMotionEvent} containing information about the touch + * or button press. */ - void onBackStarted(in BackEvent backEvent); + void onBackStarted(in BackMotionEvent backMotionEvent); /** * Called on back gesture progress. * Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}. * - * @param backEvent The {@link BackEvent} containing information about the latest touch point - * and the progress that the back animation should seek to. + * @param backMotionEvent The {@link BackMotionEvent} containing information about the latest + * touch point and the progress that the back animation should seek to. */ - void onBackProgressed(in BackEvent backEvent); + void onBackProgressed(in BackMotionEvent backMotionEvent); /** * Called when a back gesture or back button press has been cancelled. diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl index e6bb1f64ad86..0032b9ce0512 100644 --- a/core/java/android/window/ITaskOrganizerController.aidl +++ b/core/java/android/window/ITaskOrganizerController.aidl @@ -40,7 +40,8 @@ interface ITaskOrganizerController { void unregisterTaskOrganizer(ITaskOrganizer organizer); /** Creates a persistent root task in WM for a particular windowing-mode. */ - void createRootTask(int displayId, int windowingMode, IBinder launchCookie); + void createRootTask(int displayId, int windowingMode, IBinder launchCookie, + boolean removeWithTaskOrganizer); /** Deletes a persistent root task in WM */ boolean deleteRootTask(in WindowContainerToken task); diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java index c9ddf92d3740..203d79aad7a3 100644 --- a/core/java/android/window/TaskFragmentCreationParams.java +++ b/core/java/android/window/TaskFragmentCreationParams.java @@ -71,20 +71,42 @@ public final class TaskFragmentCreationParams implements Parcelable { * * This is needed in case we need to launch a placeholder Activity to split below a transparent * always-expand Activity. + * + * This should not be used with {@link #mPairedActivityToken}. */ @Nullable private final IBinder mPairedPrimaryFragmentToken; + /** + * The Activity token to place the new TaskFragment on top of. + * When it is set, the new TaskFragment will be positioned right above the target Activity. + * Otherwise, the new TaskFragment will be positioned on the top of the Task by default. + * + * This is needed in case we need to place an Activity into TaskFragment to launch placeholder + * below a transparent always-expand Activity, or when there is another Intent being started in + * a TaskFragment above. + * + * This should not be used with {@link #mPairedPrimaryFragmentToken}. + */ + @Nullable + private final IBinder mPairedActivityToken; + private TaskFragmentCreationParams( @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect initialBounds, - @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) { + @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken, + @Nullable IBinder pairedActivityToken) { + if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) { + throw new IllegalArgumentException("pairedPrimaryFragmentToken and" + + " pairedActivityToken should not be set at the same time."); + } mOrganizer = organizer; mFragmentToken = fragmentToken; mOwnerToken = ownerToken; mInitialBounds.set(initialBounds); mWindowingMode = windowingMode; mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken; + mPairedActivityToken = pairedActivityToken; } @NonNull @@ -121,6 +143,15 @@ public final class TaskFragmentCreationParams implements Parcelable { return mPairedPrimaryFragmentToken; } + /** + * TODO(b/232476698): remove the hide with adding CTS for this in next release. + * @hide + */ + @Nullable + public IBinder getPairedActivityToken() { + return mPairedActivityToken; + } + private TaskFragmentCreationParams(Parcel in) { mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in); mFragmentToken = in.readStrongBinder(); @@ -128,6 +159,7 @@ public final class TaskFragmentCreationParams implements Parcelable { mInitialBounds.readFromParcel(in); mWindowingMode = in.readInt(); mPairedPrimaryFragmentToken = in.readStrongBinder(); + mPairedActivityToken = in.readStrongBinder(); } /** @hide */ @@ -139,6 +171,7 @@ public final class TaskFragmentCreationParams implements Parcelable { mInitialBounds.writeToParcel(dest, flags); dest.writeInt(mWindowingMode); dest.writeStrongBinder(mPairedPrimaryFragmentToken); + dest.writeStrongBinder(mPairedActivityToken); } @NonNull @@ -164,6 +197,7 @@ public final class TaskFragmentCreationParams implements Parcelable { + " initialBounds=" + mInitialBounds + " windowingMode=" + mWindowingMode + " pairedFragmentToken=" + mPairedPrimaryFragmentToken + + " pairedActivityToken=" + mPairedActivityToken + "}"; } @@ -194,6 +228,9 @@ public final class TaskFragmentCreationParams implements Parcelable { @Nullable private IBinder mPairedPrimaryFragmentToken; + @Nullable + private IBinder mPairedActivityToken; + public Builder(@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) { mOrganizer = organizer; @@ -224,6 +261,8 @@ public final class TaskFragmentCreationParams implements Parcelable { * This is needed in case we need to launch a placeholder Activity to split below a * transparent always-expand Activity. * + * This should not be used with {@link #setPairedActivityToken}. + * * TODO(b/232476698): remove the hide with adding CTS for this in next release. * @hide */ @@ -233,11 +272,32 @@ public final class TaskFragmentCreationParams implements Parcelable { return this; } + /** + * Sets the Activity token to place the new TaskFragment on top of. + * When it is set, the new TaskFragment will be positioned right above the target Activity. + * Otherwise, the new TaskFragment will be positioned on the top of the Task by default. + * + * This is needed in case we need to place an Activity into TaskFragment to launch + * placeholder below a transparent always-expand Activity, or when there is another Intent + * being started in a TaskFragment above. + * + * This should not be used with {@link #setPairedPrimaryFragmentToken}. + * + * TODO(b/232476698): remove the hide with adding CTS for this in next release. + * @hide + */ + @NonNull + public Builder setPairedActivityToken(@Nullable IBinder activityToken) { + mPairedActivityToken = activityToken; + return this; + } + /** Constructs the options to create TaskFragment with. */ @NonNull public TaskFragmentCreationParams build() { return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken, - mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken); + mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken, + mPairedActivityToken); } } } diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index bffd4e437dfa..02878f8ae72b 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -152,17 +152,33 @@ public class TaskOrganizer extends WindowOrganizer { * @param windowingMode Windowing mode to put the root task in. * @param launchCookie Launch cookie to associate with the task so that is can be identified * when the {@link ITaskOrganizer#onTaskAppeared} callback is called. + * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed. + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) - @Nullable - public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) { + public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie, + boolean removeWithTaskOrganizer) { try { - mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie); + mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie, + removeWithTaskOrganizer); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /** + * Creates a persistent root task in WM for a particular windowing-mode. + * @param displayId The display to create the root task on. + * @param windowingMode Windowing mode to put the root task in. + * @param launchCookie Launch cookie to associate with the task so that is can be identified + * when the {@link ITaskOrganizer#onTaskAppeared} callback is called. + */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) + @Nullable + public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) { + createRootTask(displayId, windowingMode, launchCookie, false /* removeWithTaskOrganizer */); + } + /** Deletes a persistent root task in WM */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull WindowContainerToken task) { diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index fda39c14dac7..dd9483a9c759 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -229,19 +229,21 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } @Override - public void onBackStarted(BackEvent backEvent) { + public void onBackStarted(BackMotionEvent backEvent) { Handler.getMain().post(() -> { final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback != null) { mProgressAnimator.onBackStarted(backEvent, event -> callback.onBackProgressed(event)); - callback.onBackStarted(backEvent); + callback.onBackStarted(new BackEvent( + backEvent.getTouchX(), backEvent.getTouchY(), + backEvent.getProgress(), backEvent.getSwipeEdge())); } }); } @Override - public void onBackProgressed(BackEvent backEvent) { + public void onBackProgressed(BackMotionEvent backEvent) { Handler.getMain().post(() -> { final OnBackAnimationCallback callback = getBackAnimationCallback(); if (callback != null) { diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index f370ebd94545..9d6b29e5c072 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -17,6 +17,7 @@ package android.window; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; @@ -60,8 +61,8 @@ public class WindowOnBackInvokedDispatcherTest { private OnBackAnimationCallback mCallback1; @Mock private OnBackAnimationCallback mCallback2; - @Mock - private BackEvent mBackEvent; + private final BackMotionEvent mBackEvent = new BackMotionEvent( + 0, 0, 0, BackEvent.EDGE_LEFT, null); @Before public void setUp() throws Exception { @@ -89,12 +90,12 @@ public class WindowOnBackInvokedDispatcherTest { captor.capture()); captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback1).onBackStarted(mBackEvent); + verify(mCallback1).onBackStarted(any(BackEvent.class)); verifyZeroInteractions(mCallback2); captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback2).onBackStarted(mBackEvent); + verify(mCallback2).onBackStarted(any(BackEvent.class)); verifyNoMoreInteractions(mCallback1); } @@ -114,7 +115,7 @@ public class WindowOnBackInvokedDispatcherTest { assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY); captor.getValue().getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback1).onBackStarted(mBackEvent); + verify(mCallback1).onBackStarted(any(BackEvent.class)); } @Test @@ -152,6 +153,6 @@ public class WindowOnBackInvokedDispatcherTest { verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture()); captor.getValue().getCallback().onBackStarted(mBackEvent); waitForIdle(); - verify(mCallback2).onBackStarted(mBackEvent); + verify(mCallback2).onBackStarted(any(BackEvent.class)); } } diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index dc4b5636a246..a5b192cd7ceb 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -63,6 +63,12 @@ android_library_import { sdk_version: "current", } +android_library_import { + name: "window-extensions-core", + aars: ["window-extensions-core-release.aar"], + sdk_version: "current", +} + java_library { name: "androidx.window.extensions", srcs: [ @@ -70,7 +76,10 @@ java_library { "src/androidx/window/util/**/*.java", "src/androidx/window/common/**/*.java", ], - static_libs: ["window-extensions"], + static_libs: [ + "window-extensions", + "window-extensions-core", + ], installable: true, sdk_version: "core_platform", system_ext_specific: true, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index 54edd9ec4335..666b472c3716 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -48,7 +48,7 @@ public class WindowExtensionsImpl implements WindowExtensions { // TODO(b/241126279) Introduce constants to better version functionality @Override public int getVendorApiLevel() { - return 1; + return 2; } @NonNull diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 87fa63d7fe14..00e13c94ea90 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -191,10 +191,25 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { */ void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) { + createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode, + null /* pairedActivityToken */); + } + + /** + * @param ownerToken The token of the activity that creates this task fragment. It does not + * have to be a child of this task fragment, but must belong to the same task. + * @param pairedActivityToken The token of the activity that will be reparented to this task + * fragment. When it is not {@code null}, the task fragment will be + * positioned right above it. + */ + void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, + @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode, + @Nullable IBinder pairedActivityToken) { final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder( getOrganizerToken(), fragmentToken, ownerToken) .setInitialBounds(bounds) .setWindowingMode(windowingMode) + .setPairedActivityToken(pairedActivityToken) .build(); createTaskFragment(wct, fragmentOptions); } @@ -216,8 +231,10 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode, @NonNull Activity activity) { - createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode); - wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken()); + final IBinder reparentActivityToken = activity.getActivityToken(); + createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode, + reparentActivityToken); + wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken); } void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 1cd3ea5592e3..8b3a471ea306 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -77,6 +77,9 @@ import androidx.annotation.Nullable; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; +import androidx.window.extensions.WindowExtensionsImpl; +import androidx.window.extensions.core.util.function.Consumer; +import androidx.window.extensions.core.util.function.Function; import androidx.window.extensions.embedding.TransactionManager.TransactionRecord; import androidx.window.extensions.layout.WindowLayoutComponentImpl; @@ -87,7 +90,6 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; -import java.util.function.Consumer; /** * Main controller class that manages split states and presentation. @@ -113,7 +115,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * A developer-defined {@link SplitAttributes} calculator to compute the current * {@link SplitAttributes} with the current device and window states. - * It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)} + * It is registered via {@link #setSplitAttributesCalculator(Function)} * and unregistered via {@link #clearSplitAttributesCalculator()}. * This is called when: * <ul> @@ -126,7 +128,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @GuardedBy("mLock") @Nullable - private SplitAttributesCalculator mSplitAttributesCalculator; + private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator; /** * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info @@ -139,6 +141,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>(); /** Callback to Jetpack to notify about changes to split states. */ + @GuardedBy("mLock") @Nullable private Consumer<List<SplitInfo>> mEmbeddingCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); @@ -164,7 +167,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen foldingFeatureProducer.addDataChangedCallback(new FoldingFeatureListener()); } - private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> { + private class FoldingFeatureListener + implements java.util.function.Consumer<List<CommonFoldingFeature>> { @Override public void accept(List<CommonFoldingFeature> foldingFeatures) { synchronized (mLock) { @@ -205,7 +209,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) { + public void setSplitAttributesCalculator( + @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) { synchronized (mLock) { mSplitAttributesCalculator = calculator; } @@ -220,7 +225,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") @Nullable - SplitAttributesCalculator getSplitAttributesCalculator() { + Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() { return mSplitAttributesCalculator; } @@ -233,9 +238,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Registers the split organizer callback to notify about changes to active splits. + * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with + * {@link WindowExtensionsImpl#getVendorApiLevel()} 2. */ + @Deprecated @Override - public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) { + public void setSplitInfoCallback( + @NonNull java.util.function.Consumer<List<SplitInfo>> callback) { + Consumer<List<SplitInfo>> oemConsumer = callback::accept; + setSplitInfoCallback(oemConsumer); + } + + /** + * Registers the split organizer callback to notify about changes to active splits. + * @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2 + */ + public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) { synchronized (mLock) { mEmbeddingCallback = callback; updateCallbackIfNecessary(); @@ -1481,7 +1499,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns the active split that has the provided containers as primary and secondary or as * secondary and primary, if available. */ - @VisibleForTesting + @GuardedBy("mLock") @Nullable SplitContainer getActiveSplitForContainers( @NonNull TaskFragmentContainer firstContainer, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 14d244bbb6ce..668a7d5aa9b6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -43,11 +43,11 @@ import android.window.WindowContainerTransaction; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.window.extensions.core.util.function.Function; import androidx.window.extensions.embedding.SplitAttributes.SplitType; import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType; import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType; import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType; -import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttributesCalculatorParams; import androidx.window.extensions.embedding.TaskContainer.TaskProperties; import androidx.window.extensions.layout.DisplayFeature; import androidx.window.extensions.layout.FoldingFeature; @@ -268,10 +268,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { container = mController.newContainer(activity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(bounds); - createTaskFragment(wct, container.getTaskFragmentToken(), activity.getActivityToken(), - bounds, windowingMode); + final IBinder reparentActivityToken = activity.getActivityToken(); + createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken, + bounds, windowingMode, reparentActivityToken); wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(), - activity.getActivityToken()); + reparentActivityToken); } else { resizeTaskFragmentIfRegistered(wct, container, bounds); final int windowingMode = mController.getTaskContainer(taskId) @@ -551,11 +552,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) { final Configuration taskConfiguration = taskProperties.getConfiguration(); final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration); - final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator(); + final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator = + mController.getSplitAttributesCalculator(); final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes(); - final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics); + final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics); if (calculator == null) { - if (!isDefaultMinSizeSatisfied) { + if (!areDefaultConstraintsSatisfied) { return EXPAND_CONTAINERS_ATTRIBUTES; } return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes, @@ -565,9 +567,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(), taskConfiguration.windowConfiguration); final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams( - taskWindowMetrics, taskConfiguration, defaultSplitAttributes, - isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag()); - final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params); + taskWindowMetrics, taskConfiguration, windowLayoutInfo, defaultSplitAttributes, + areDefaultConstraintsSatisfied, rule.getTag()); + final SplitAttributes splitAttributes = calculator.apply(params); return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 076856c373d6..17814c65e791 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -141,12 +141,26 @@ class TaskFragmentContainer { mToken = new Binder("TaskFragmentContainer"); mTaskContainer = taskContainer; if (pairedPrimaryContainer != null) { + // The TaskFragment will be positioned right above the paired container. if (pairedPrimaryContainer.getTaskContainer() != taskContainer) { throw new IllegalArgumentException( "pairedPrimaryContainer must be in the same Task"); } final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer); taskContainer.mContainers.add(primaryIndex + 1, this); + } else if (pendingAppearedActivity != null) { + // The TaskFragment will be positioned right above the pending appeared Activity. If any + // existing TaskFragment is empty with pending Intent, it is likely that the Activity of + // the pending Intent hasn't been created yet, so the new Activity should be below the + // empty TaskFragment. + int i = taskContainer.mContainers.size() - 1; + for (; i >= 0; i--) { + final TaskFragmentContainer container = taskContainer.mContainers.get(i); + if (!container.isEmpty() || container.getPendingAppearedIntent() == null) { + break; + } + } + taskContainer.mContainers.add(i + 1, this); } else { taskContainer.mContainers.add(this); } @@ -500,6 +514,8 @@ class TaskFragmentContainer { } if (!shouldFinishDependent) { + // Always finish the placeholder when the primary is finished. + finishPlaceholderIfAny(wct, presenter); return; } @@ -526,6 +542,28 @@ class TaskFragmentContainer { mActivitiesToFinishOnExit.clear(); } + @GuardedBy("mController.mLock") + private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct, + @NonNull SplitPresenter presenter) { + final List<TaskFragmentContainer> containersToRemove = new ArrayList<>(); + for (TaskFragmentContainer container : mContainersToFinishOnExit) { + if (container.mIsFinished) { + continue; + } + final SplitContainer splitContainer = mController.getActiveSplitForContainers( + this, container); + if (splitContainer != null && splitContainer.isPlaceholderContainer() + && splitContainer.getSecondaryContainer() == container) { + // Remove the placeholder secondary TaskFragment. + containersToRemove.add(container); + } + } + mContainersToFinishOnExit.removeAll(containersToRemove); + for (TaskFragmentContainer container : containersToRemove) { + container.finish(false /* shouldFinishDependent */, presenter, wct, mController); + } + } + boolean isFinished() { return mIsFinished; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index c9f870005eb9..8386131b177d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -45,6 +45,7 @@ import androidx.annotation.UiContext; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; +import androidx.window.extensions.core.util.function.Consumer; import androidx.window.util.DataProducer; import java.util.ArrayList; @@ -53,7 +54,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; /** * Reference implementation of androidx.window.extensions.layout OEM interface for use with @@ -82,6 +82,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners = new ArrayMap<>(); + @GuardedBy("mLock") + private final Map<java.util.function.Consumer<WindowLayoutInfo>, Consumer<WindowLayoutInfo>> + mJavaToExtConsumers = new ArrayMap<>(); + private final TaskFragmentOrganizer mTaskFragmentOrganizer; public WindowLayoutComponentImpl(@NonNull Context context, @@ -95,7 +99,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } /** Registers to listen to {@link CommonFoldingFeature} changes */ - public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) { + public void addFoldingStateChangedCallback( + java.util.function.Consumer<List<CommonFoldingFeature>> consumer) { synchronized (mLock) { mFoldingFeatureProducer.addDataChangedCallback(consumer); } @@ -109,13 +114,27 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { */ @Override public void addWindowLayoutInfoListener(@NonNull Activity activity, - @NonNull Consumer<WindowLayoutInfo> consumer) { - addWindowLayoutInfoListener((Context) activity, consumer); + @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) { + final Consumer<WindowLayoutInfo> extConsumer = consumer::accept; + synchronized (mLock) { + mJavaToExtConsumers.put(consumer, extConsumer); + } + addWindowLayoutInfoListener(activity, extConsumer); + } + + @Override + public void addWindowLayoutInfoListener(@NonNull @UiContext Context context, + @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) { + final Consumer<WindowLayoutInfo> extConsumer = consumer::accept; + synchronized (mLock) { + mJavaToExtConsumers.put(consumer, extConsumer); + } + addWindowLayoutInfoListener(context, extConsumer); } /** - * Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context - * as a parameter. + * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but + * takes a UI Context as a parameter. * * Jetpack {@link androidx.window.layout.ExtensionWindowLayoutInfoBackend} makes sure all * consumers related to the same {@link Context} gets updated {@link WindowLayoutInfo} @@ -156,6 +175,18 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } + @Override + public void removeWindowLayoutInfoListener( + @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) { + final Consumer<WindowLayoutInfo> extConsumer; + synchronized (mLock) { + extConsumer = mJavaToExtConsumers.remove(consumer); + } + if (extConsumer != null) { + removeWindowLayoutInfoListener(extConsumer); + } + } + /** * Removes a listener no longer interested in receiving updates. * diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index 2f92a577baa2..459ec9f89c4a 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -34,9 +34,11 @@ import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.util.Pair; +import android.view.WindowMetrics; import android.window.TaskFragmentInfo; import android.window.WindowContainerToken; +import androidx.window.extensions.core.util.function.Predicate; import androidx.window.extensions.embedding.SplitAttributes.SplitType; import androidx.window.extensions.layout.DisplayFeature; import androidx.window.extensions.layout.FoldingFeature; @@ -107,7 +109,7 @@ public class EmbeddingTestUtils { static SplitRule createSplitRule(@NonNull Activity primaryActivity, @NonNull Intent secondaryIntent, boolean clearTop) { final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent); - return new SplitPairRule.Builder( + return createSplitPairRuleBuilder( activityPair -> false, targetPair::equals, w -> true) @@ -144,7 +146,7 @@ public class EmbeddingTestUtils { @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary, int finishSecondaryWithPrimary, boolean clearTop) { final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity); - return new SplitPairRule.Builder( + return createSplitPairRuleBuilder( targetPair::equals, activityIntentPair -> false, w -> true) @@ -223,4 +225,26 @@ public class EmbeddingTestUtils { displayFeatures.add(foldingFeature); return new WindowLayoutInfo(displayFeatures); } + + static ActivityRule.Builder createActivityBuilder( + @NonNull Predicate<Activity> activityPredicate, + @NonNull Predicate<Intent> intentPredicate) { + return new ActivityRule.Builder(activityPredicate, intentPredicate); + } + + static SplitPairRule.Builder createSplitPairRuleBuilder( + @NonNull Predicate<Pair<Activity, Activity>> activitiesPairPredicate, + @NonNull Predicate<Pair<Activity, Intent>> activityIntentPairPredicate, + @NonNull Predicate<WindowMetrics> windowMetricsPredicate) { + return new SplitPairRule.Builder(activitiesPairPredicate, activityIntentPairPredicate, + windowMetricsPredicate); + } + + static SplitPlaceholderRule.Builder createSplitPlaceholderRuleBuilder( + @NonNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate, + @NonNull Predicate<Intent> intentPredicate, + @NonNull Predicate<WindowMetrics> windowMetricsPredicate) { + return new SplitPlaceholderRule.Builder(placeholderIntent, activityPredicate, + intentPredicate, windowMetricsPredicate); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 81c39571bffa..0bf0bc85b511 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -34,8 +34,11 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTR import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; @@ -432,7 +435,7 @@ public class SplitControllerTest { @Test public void testResolveStartActivityIntent_withoutLaunchingActivity() { final Intent intent = new Intent(); - final ActivityRule expandRule = new ActivityRule.Builder(r -> false, i -> i == intent) + final ActivityRule expandRule = createActivityBuilder(r -> false, i -> i == intent) .setShouldAlwaysExpand(true) .build(); mSplitController.setEmbeddingRules(Collections.singleton(expandRule)); @@ -1170,7 +1173,7 @@ public class SplitControllerTest { @Test public void testHasSamePresentation() { - SplitPairRule splitRule1 = new SplitPairRule.Builder( + SplitPairRule splitRule1 = createSplitPairRuleBuilder( activityPair -> true, activityIntentPair -> true, windowMetrics -> true) @@ -1178,7 +1181,7 @@ public class SplitControllerTest { .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) .build(); - SplitPairRule splitRule2 = new SplitPairRule.Builder( + SplitPairRule splitRule2 = createSplitPairRuleBuilder( activityPair -> true, activityIntentPair -> true, windowMetrics -> true) @@ -1191,7 +1194,7 @@ public class SplitControllerTest { SplitController.haveSamePresentation(splitRule1, splitRule2, new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); - splitRule2 = new SplitPairRule.Builder( + splitRule2 = createSplitPairRuleBuilder( activityPair -> true, activityIntentPair -> true, windowMetrics -> true) @@ -1355,7 +1358,7 @@ public class SplitControllerTest { /** Setups a rule to always expand the given intent. */ private void setupExpandRule(@NonNull Intent expandIntent) { - final ActivityRule expandRule = new ActivityRule.Builder(r -> false, expandIntent::equals) + final ActivityRule expandRule = createActivityBuilder(r -> false, expandIntent::equals) .setShouldAlwaysExpand(true) .build(); mSplitController.setEmbeddingRules(Collections.singleton(expandRule)); @@ -1363,7 +1366,7 @@ public class SplitControllerTest { /** Setups a rule to always expand the given activity. */ private void setupExpandRule(@NonNull Activity expandActivity) { - final ActivityRule expandRule = new ActivityRule.Builder(expandActivity::equals, i -> false) + final ActivityRule expandRule = createActivityBuilder(expandActivity::equals, i -> false) .setShouldAlwaysExpand(true) .build(); mSplitController.setEmbeddingRules(Collections.singleton(expandRule)); @@ -1371,7 +1374,7 @@ public class SplitControllerTest { /** Setups a rule to launch placeholder for the given activity. */ private void setupPlaceholderRule(@NonNull Activity primaryActivity) { - final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT, + final SplitRule placeholderRule = createSplitPlaceholderRuleBuilder(PLACEHOLDER_INTENT, primaryActivity::equals, i -> false, w -> true) .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) .build(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 121e81394b2d..ff1256b47429 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -28,6 +28,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUND import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; @@ -511,7 +512,7 @@ public class SplitPresenterTest { final Activity secondaryActivity = createMockActivity(); final TaskFragmentContainer bottomTf = mController.newContainer(secondaryActivity, TASK_ID); final TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); - final SplitPairRule rule = new SplitPairRule.Builder(pair -> + final SplitPairRule rule = createSplitPairRuleBuilder(pair -> pair.first == mActivity && pair.second == secondaryActivity, pair -> false, metrics -> true) .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) @@ -529,7 +530,7 @@ public class SplitPresenterTest { @Test public void testComputeSplitAttributes() { - final SplitPairRule splitPairRule = new SplitPairRule.Builder( + final SplitPairRule splitPairRule = createSplitPairRuleBuilder( activityPair -> true, activityIntentPair -> true, windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS)) diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 7d9d8b0f3a06..78b85e642c13 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -154,17 +154,52 @@ public class TaskFragmentContainerTest { null /* pendingAppearedIntent */, taskContainer, mController, null /* pairedPrimaryContainer */); doReturn(container1).when(mController).getContainerWithActivity(mActivity); - final WindowContainerTransaction wct = new WindowContainerTransaction(); // The activity is requested to be reparented, so don't finish it. - container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); + container0.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController); verify(mTransaction, never()).finishActivity(any()); - verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken()); + verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken()); verify(mController).removeContainer(container0); } @Test + public void testFinish_alwaysFinishPlaceholder() { + // Register container1 as a placeholder + final TaskContainer taskContainer = createTestTaskContainer(); + final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, + null /* pendingAppearedIntent */, taskContainer, mController, + null /* pairedPrimaryContainer */); + final TaskFragmentInfo info0 = createMockTaskFragmentInfo(container0, mActivity); + container0.setInfo(mTransaction, info0); + final Activity placeholderActivity = createMockActivity(); + final TaskFragmentContainer container1 = new TaskFragmentContainer(placeholderActivity, + null /* pendingAppearedIntent */, taskContainer, mController, + null /* pairedPrimaryContainer */); + final TaskFragmentInfo info1 = createMockTaskFragmentInfo(container1, placeholderActivity); + container1.setInfo(mTransaction, info1); + final SplitAttributes splitAttributes = new SplitAttributes.Builder().build(); + final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(new Intent(), + mActivity::equals, (java.util.function.Predicate) i -> false, + (java.util.function.Predicate) w -> true) + .setDefaultSplitAttributes(splitAttributes) + .build(); + mController.registerSplit(mTransaction, container0, mActivity, container1, rule, + splitAttributes); + + // The placeholder TaskFragment should be finished even if the primary is finished with + // shouldFinishDependent = false. + container0.finish(false /* shouldFinishDependent */, mPresenter, mTransaction, mController); + + assertTrue(container0.isFinished()); + assertTrue(container1.isFinished()); + verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken()); + verify(mPresenter).deleteTaskFragment(mTransaction, container1.getTaskFragmentToken()); + verify(mController).removeContainer(container0); + verify(mController).removeContainer(container1); + } + + @Test public void testSetInfo() { final TaskContainer taskContainer = createTestTaskContainer(); // Pending activity should be cleared when it has appeared on server side. @@ -493,8 +528,6 @@ public class TaskFragmentContainerTest { final TaskFragmentContainer tf1 = new TaskFragmentContainer( null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, null /* pairedPrimaryTaskFragment */); - taskContainer.mContainers.add(tf0); - taskContainer.mContainers.add(tf1); // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted // right above tf0. @@ -506,6 +539,26 @@ public class TaskFragmentContainerTest { } @Test + public void testNewContainerWithPairedPendingAppearedActivity() { + final TaskContainer taskContainer = createTestTaskContainer(); + final TaskFragmentContainer tf0 = new TaskFragmentContainer( + createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController, + null /* pairedPrimaryTaskFragment */); + final TaskFragmentContainer tf1 = new TaskFragmentContainer( + null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, + null /* pairedPrimaryTaskFragment */); + + // When tf2 is created with pendingAppearedActivity, tf2 should be inserted below any + // TaskFragment without any Activity. + final TaskFragmentContainer tf2 = new TaskFragmentContainer( + createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController, + null /* pairedPrimaryTaskFragment */); + assertEquals(0, taskContainer.indexOf(tf0)); + assertEquals(1, taskContainer.indexOf(tf2)); + assertEquals(2, taskContainer.indexOf(tf1)); + } + + @Test public void testIsVisible() { final TaskContainer taskContainer = createTestTaskContainer(); final TaskFragmentContainer container = new TaskFragmentContainer( diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar Binary files differnew file mode 100644 index 000000000000..96ff840b984b --- /dev/null +++ b/libs/WindowManager/Jetpack/window-extensions-core-release.aar diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 84ab4487feee..5de5365e4d03 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml index 0bcaa530dc80..b7ff96e64eec 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_minimize_button_dark.xml @@ -18,7 +18,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:tint="@color/decor_button_dark_color"> <path android:fillColor="@android:color/white" android:pathData="M6,21V19H18V21Z"/> </vector> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index e58e785850fa..97a9fede22d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -256,12 +256,30 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } + /** + * Creates a persistent root task in WM for a particular windowing-mode. + * @param displayId The display to create the root task on. + * @param windowingMode Windowing mode to put the root task in. + * @param listener The listener to get the created task callback. + */ public void createRootTask(int displayId, int windowingMode, TaskListener listener) { - ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s", + createRootTask(displayId, windowingMode, listener, false /* removeWithTaskOrganizer */); + } + + /** + * Creates a persistent root task in WM for a particular windowing-mode. + * @param displayId The display to create the root task on. + * @param windowingMode Windowing mode to put the root task in. + * @param listener The listener to get the created task callback. + * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed. + */ + public void createRootTask(int displayId, int windowingMode, TaskListener listener, + boolean removeWithTaskOrganizer) { + ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s" , displayId, windowingMode, listener.toString()); final IBinder cookie = new Binder(); setPendingLaunchCookieListener(cookie, listener); - super.createRootTask(displayId, windowingMode, cookie); + super.createRootTask(displayId, windowingMode, cookie, removeWithTaskOrganizer); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index cbcd9498fe55..236309207b4f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -51,6 +51,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.BackAnimationAdaptor; import android.window.BackEvent; +import android.window.BackMotionEvent; import android.window.BackNavigationInfo; import android.window.IBackAnimationRunner; import android.window.IBackNaviAnimationController; @@ -173,11 +174,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont boolean consumed = false; if (mWaitingAnimation && mOnBackCallback != null) { if (mTriggerBack) { - final BackEvent backFinish = mTouchTracker.createProgressEvent(1); + final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(1); dispatchOnBackProgressed(mBackToLauncherCallback, backFinish); dispatchOnBackInvoked(mOnBackCallback); } else { - final BackEvent backFinish = mTouchTracker.createProgressEvent(0); + final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(0); dispatchOnBackProgressed(mBackToLauncherCallback, backFinish); dispatchOnBackCancelled(mOnBackCallback); } @@ -480,7 +481,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (!mBackGestureStarted || mBackNavigationInfo == null) { return; } - final BackEvent backEvent = mTouchTracker.createProgressEvent(); + final BackMotionEvent backEvent = mTouchTracker.createProgressEvent(); if (USE_TRANSITION && mBackAnimationController != null && mAnimationTarget != null) { dispatchOnBackProgressed(mBackToLauncherCallback, backEvent); } else if (mEnableAnimations.get()) { @@ -573,7 +574,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void dispatchOnBackStarted(IOnBackInvokedCallback callback, - BackEvent backEvent) { + BackMotionEvent backEvent) { if (callback == null) { return; } @@ -611,7 +612,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void dispatchOnBackProgressed(IOnBackInvokedCallback callback, - BackEvent backEvent) { + BackMotionEvent backEvent) { if (callback == null) { return; } @@ -730,7 +731,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } dispatchOnBackStarted(mBackToLauncherCallback, mTouchTracker.createStartEvent(mAnimationTarget)); - final BackEvent backInit = mTouchTracker.createProgressEvent(); + final BackMotionEvent backInit = mTouchTracker.createProgressEvent(); if (!mCachingBackDispatcher.consume()) { dispatchOnBackProgressed(mBackToLauncherCallback, backInit); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java index ccfac65d6342..695ef4e66302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -19,6 +19,7 @@ package com.android.wm.shell.back; import android.os.SystemProperties; import android.view.RemoteAnimationTarget; import android.window.BackEvent; +import android.window.BackMotionEvent; /** * Helper class to record the touch location for gesture and generate back events. @@ -82,11 +83,11 @@ class TouchTracker { mSwipeEdge = BackEvent.EDGE_LEFT; } - BackEvent createStartEvent(RemoteAnimationTarget target) { - return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target); + BackMotionEvent createStartEvent(RemoteAnimationTarget target) { + return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target); } - BackEvent createProgressEvent() { + BackMotionEvent createProgressEvent() { float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; progressThreshold = progressThreshold == 0 ? 1 : progressThreshold; @@ -109,8 +110,8 @@ class TouchTracker { return createProgressEvent(progress); } - BackEvent createProgressEvent(float progress) { - return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null); + BackMotionEvent createProgressEvent(float progress) { + return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null); } public void setProgressThreshold(float progressThreshold) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index dd8afff0df2c..71e15c12b9c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -973,21 +973,59 @@ public class BubbleController implements ConfigurationChangeListener { } /** - * Adds and expands bubble for a specific intent. These bubbles are <b>not</b> backed by a n - * otification and remain until the user dismisses the bubble or bubble stack. Only one intent - * bubble is supported at a time. + * This method has different behavior depending on: + * - if an app bubble exists + * - if an app bubble is expanded + * + * If no app bubble exists, this will add and expand a bubble with the provided intent. The + * intent must be explicit (i.e. include a package name or fully qualified component class name) + * and the activity for it should be resizable. + * + * If an app bubble exists, this will toggle the visibility of it, i.e. if the app bubble is + * expanded, calling this method will collapse it. If the app bubble is not expanded, calling + * this method will expand it. + * + * These bubbles are <b>not</b> backed by a notification and remain until the user dismisses + * the bubble or bubble stack. + * + * Some notes: + * - Only one app bubble is supported at a time + * - Calling this method with a different intent than the existing app bubble will do nothing * * @param intent the intent to display in the bubble expanded view. */ - public void showAppBubble(Intent intent) { - if (intent == null || intent.getPackage() == null) return; + public void showOrHideAppBubble(Intent intent) { + if (intent == null || intent.getPackage() == null) { + Log.w(TAG, "App bubble failed to show, invalid intent: " + intent + + ((intent != null) ? " with package: " + intent.getPackage() : " ")); + return; + } PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId); if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return; - Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor); - b.setShouldAutoExpand(true); - inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); + Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE); + if (existingAppBubble != null) { + BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); + if (isStackExpanded()) { + if (selectedBubble != null && KEY_APP_BUBBLE.equals(selectedBubble.getKey())) { + // App bubble is expanded, lets collapse + collapseStack(); + } else { + // App bubble is not selected, select it + mBubbleData.setSelectedBubble(existingAppBubble); + } + } else { + // App bubble is not selected, select it & expand + mBubbleData.setSelectedBubble(existingAppBubble); + mBubbleData.setExpanded(true); + } + } else { + // App bubble does not exist, lets add and expand it + Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor); + b.setShouldAutoExpand(true); + inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); + } } /** @@ -1697,9 +1735,9 @@ public class BubbleController implements ConfigurationChangeListener { } @Override - public void showAppBubble(Intent intent) { + public void showOrHideAppBubble(Intent intent) { mMainExecutor.execute(() -> { - BubbleController.this.showAppBubble(intent); + BubbleController.this.showOrHideAppBubble(intent); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index af31391fec96..6230d22ebe12 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -684,7 +685,8 @@ public class BubbleData { if (bubble.getPendingIntentCanceled() || !(reason == Bubbles.DISMISS_AGED || reason == Bubbles.DISMISS_USER_GESTURE - || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) { + || reason == Bubbles.DISMISS_RELOAD_FROM_DISK) + || KEY_APP_BUBBLE.equals(bubble.getKey())) { return; } if (DEBUG_BUBBLE_DATA) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 465d1abe0a3d..df4325763a17 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -109,13 +109,28 @@ public interface Bubbles { void expandStackAndSelectBubble(Bubble bubble); /** - * Adds and expands bubble that is not notification based, but instead based on an intent from - * the app. The intent must be explicit (i.e. include a package name or fully qualified - * component class name) and the activity for it should be resizable. + * This method has different behavior depending on: + * - if an app bubble exists + * - if an app bubble is expanded * - * @param intent the intent to populate the bubble. + * If no app bubble exists, this will add and expand a bubble with the provided intent. The + * intent must be explicit (i.e. include a package name or fully qualified component class name) + * and the activity for it should be resizable. + * + * If an app bubble exists, this will toggle the visibility of it, i.e. if the app bubble is + * expanded, calling this method will collapse it. If the app bubble is not expanded, calling + * this method will expand it. + * + * These bubbles are <b>not</b> backed by a notification and remain until the user dismisses + * the bubble or bubble stack. + * + * Some notes: + * - Only one app bubble is supported at a time + * - Calling this method with a different intent than the existing app bubble will do nothing + * + * @param intent the intent to display in the bubble expanded view. */ - void showAppBubble(Intent intent); + void showOrHideAppBubble(Intent intent); /** * @return a bubble that matches the provided shortcutId, if one exists. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java index b77ac8a2b951..e46ee28b3ddb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java @@ -29,6 +29,9 @@ import java.util.function.Consumer; * Manages the lifecycle of a single instance of a remote listener, including the clean up if the * remote process dies. All calls on this class should happen on the main shell thread. * + * Any external interface using this listener should also unregister the listener when it is + * invalidated, otherwise it may leak binder death recipients. + * * @param <C> The controller (must be RemoteCallable) * @param <L> The remote listener interface type */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index f5f3573252ec..63b03ab76aac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -251,7 +251,8 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll * Show apps on desktop */ void showDesktopApps() { - WindowContainerTransaction wct = bringDesktopAppsToFront(); + // Bring apps to front, ignoring their visibility status to always ensure they are on top. + WindowContainerTransaction wct = bringDesktopAppsToFront(true /* ignoreVisibility */); if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */); @@ -261,7 +262,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll } @NonNull - private WindowContainerTransaction bringDesktopAppsToFront() { + private WindowContainerTransaction bringDesktopAppsToFront(boolean force) { final WindowContainerTransaction wct = new WindowContainerTransaction(); final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(); ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size()); @@ -278,12 +279,14 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll return wct; } - final boolean allActiveTasksAreVisible = taskInfos.stream() - .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId)); - if (allActiveTasksAreVisible) { - ProtoLog.d(WM_SHELL_DESKTOP_MODE, - "bringDesktopAppsToFront: active tasks are already in front, skipping."); - return wct; + if (!force) { + final boolean allActiveTasksAreVisible = taskInfos.stream() + .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId)); + if (allActiveTasksAreVisible) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "bringDesktopAppsToFront: active tasks are already in front, skipping."); + return wct; + } } ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: reordering all active tasks to the front"); @@ -354,7 +357,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll if (wct == null) { wct = new WindowContainerTransaction(); } - wct.merge(bringDesktopAppsToFront(), true /* transfer */); + wct.merge(bringDesktopAppsToFront(false /* ignoreVisibility */), true /* transfer */); wct.reorder(request.getTriggerTask().token, true /* onTop */); return wct; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 3341470efe4d..9165f7012bb5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -84,8 +84,7 @@ class DesktopTasksController( fun showDesktopApps() { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "showDesktopApps") val wct = WindowContainerTransaction() - - bringDesktopAppsToFront(wct) + bringDesktopAppsToFront(wct, force = true) // Execute transaction if there are pending operations if (!wct.isEmpty) { @@ -150,11 +149,11 @@ class DesktopTasksController( ?: WINDOWING_MODE_UNDEFINED } - private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) { + private fun bringDesktopAppsToFront(wct: WindowContainerTransaction, force: Boolean = false) { val activeTasks = desktopModeTaskRepository.getActiveTasks() // Skip if all tasks are already visible - if (activeTasks.isNotEmpty() && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) { + if (!force && activeTasks.all(desktopModeTaskRepository::isVisibleTask)) { ProtoLog.d( WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: active tasks are already in front, skipping." 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 e6c7e101d078..83158ffafa7e 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 @@ -662,8 +662,8 @@ public class PipTransition extends PipTransitionController { } // Please file a bug to handle the unexpected transition type. - throw new IllegalStateException("Entering PIP with unexpected transition type=" - + transitTypeToString(transitType)); + android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type=" + + transitTypeToString(transitType), new Throwable()); } return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 281ea530e9e1..431bd7b08142 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -333,6 +333,9 @@ public class PhonePipMenuController implements PipMenuController { mTmpDestinationRectF.set(destinationBounds); mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); final SurfaceControl surfaceControl = getSurfaceControl(); + if (surfaceControl == null) { + return; + } final SurfaceControl.Transaction menuTx = mSurfaceControlTransactionFactory.getTransaction(); menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform); @@ -359,6 +362,9 @@ public class PhonePipMenuController implements PipMenuController { } final SurfaceControl surfaceControl = getSurfaceControl(); + if (surfaceControl == null) { + return; + } final SurfaceControl.Transaction menuTx = mSurfaceControlTransactionFactory.getTransaction(); menuTx.setCrop(surfaceControl, destinationBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index efe938f0a274..3153313de42f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -207,7 +207,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb private Consumer<Boolean> mOnIsInPipStateChangedListener; - private interface PipAnimationListener { + @VisibleForTesting + interface PipAnimationListener { /** * Notifies the listener that the Pip animation is started. */ @@ -434,11 +435,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb Optional<OneHandedController> oneHandedController, ShellExecutor mainExecutor ) { - // Ensure that we are the primary user's SystemUI. - final int processUser = UserManager.get(context).getProcessUserId(); - if (processUser != UserHandle.USER_SYSTEM) { - throw new IllegalStateException("Non-primary Pip component not currently supported."); - } + mContext = context; mShellCommandHandler = shellCommandHandler; @@ -817,7 +814,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Override public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) { - if (!mPipTaskOrganizer.isInPip()) { + if (!mPipTransitionState.hasEnteredPip()) { return; } if (visible) { @@ -872,11 +869,17 @@ public class PipController implements PipTransitionController.PipTransitionCallb animationType == PipAnimationController.ANIM_TYPE_BOUNDS); } - private void setPinnedStackAnimationListener(PipAnimationListener callback) { + @VisibleForTesting + void setPinnedStackAnimationListener(PipAnimationListener callback) { mPinnedStackAnimationRecentsCallback = callback; onPipResourceDimensionsChanged(); } + @VisibleForTesting + boolean hasPinnedStackAnimationListener() { + return mPinnedStackAnimationRecentsCallback != null; + } + private void onPipResourceDimensionsChanged() { if (mPinnedStackAnimationRecentsCallback != null) { mPinnedStackAnimationRecentsCallback.onPipResourceDimensionsChanged( @@ -1166,6 +1169,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Override public void invalidate() { mController = null; + // Unregister the listener to ensure any registered binder death recipients are unlinked + mListener.unregister(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index db0f0bf6fda8..8490f9f156c7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -280,15 +280,22 @@ public class RecentTasksController implements TaskStackListenerCallback, } } - private void registerRecentTasksListener(IRecentTasksListener listener) { + @VisibleForTesting + void registerRecentTasksListener(IRecentTasksListener listener) { mListener = listener; } - private void unregisterRecentTasksListener() { + @VisibleForTesting + void unregisterRecentTasksListener() { mListener = null; } @VisibleForTesting + boolean hasRecentTasksListener() { + return mListener != null; + } + + @VisibleForTesting ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { // Note: the returned task list is from the most-recent to least-recent order final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( @@ -442,6 +449,8 @@ public class RecentTasksController implements TaskStackListenerCallback, @Override public void invalidate() { mController = null; + // Unregister the listener to ensure any registered binder death recipients are unlinked + mListener.unregister(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index ef70d9bd84ee..38099fc51d81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -954,6 +954,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void invalidate() { mController = null; + // Unregister the listener to ensure any registered binder death recipients are unlinked + mListener.unregister(); } @Override 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 8ddc3c04d991..1488469759cd 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 @@ -605,9 +605,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); + if (taskId2 == INVALID_TASK_ID) { + // Launching a solo task. + ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); + activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); + options1 = activityOptions.toBundle(); + addActivityOptions(options1, null /* launchTarget */); + wct.startTask(taskId1, options1); + mSyncQueue.queue(wct); + return; + } + addActivityOptions(options1, mSideStage); wct.startTask(taskId1, options1); - startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter, instanceId); } @@ -632,9 +642,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); + if (taskId == INVALID_TASK_ID) { + // Launching a solo task. + ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); + activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); + options1 = activityOptions.toBundle(); + addActivityOptions(options1, null /* launchTarget */); + wct.sendPendingIntent(pendingIntent, fillInIntent, options1); + mSyncQueue.queue(wct); + return; + } + addActivityOptions(options1, mSideStage); wct.sendPendingIntent(pendingIntent, fillInIntent, options1); - startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter, instanceId); } @@ -696,6 +716,34 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mShouldUpdateRecents = false; mIsSplitEntering = true; + setSideStagePosition(sidePosition, wct); + if (!mMainStage.isActive()) { + mMainStage.activate(wct, false /* reparent */); + } + + if (mainOptions == null) mainOptions = new Bundle(); + addActivityOptions(mainOptions, mMainStage); + mainOptions = wrapAsSplitRemoteAnimation(adapter, mainOptions); + + updateWindowBounds(mSplitLayout, wct); + if (mainTaskId == INVALID_TASK_ID) { + wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions); + } else { + wct.startTask(mainTaskId, mainOptions); + } + + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); + + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> { + setDividerVisibility(true, t); + }); + + setEnterInstanceId(instanceId); + } + + private Bundle wrapAsSplitRemoteAnimation(RemoteAnimationAdapter adapter, Bundle options) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); if (isSplitScreenVisible()) { mMainStage.evictAllChildren(evictWct); @@ -739,37 +787,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }; RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter( wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay()); - - if (mainOptions == null) { - mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle(); - } else { - ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions); - mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); - mainOptions = mainActivityOptions.toBundle(); - } - - setSideStagePosition(sidePosition, wct); - if (!mMainStage.isActive()) { - mMainStage.activate(wct, false /* reparent */); - } - - if (mainOptions == null) mainOptions = new Bundle(); - addActivityOptions(mainOptions, mMainStage); - updateWindowBounds(mSplitLayout, wct); - if (mainTaskId == INVALID_TASK_ID) { - wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions); - } else { - wct.startTask(mainTaskId, mainOptions); - } - wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); - - mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> { - setDividerVisibility(true, t); - }); - - setEnterInstanceId(instanceId); + ActivityOptions activityOptions = ActivityOptions.fromBundle(options); + activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); + return activityOptions.toBundle(); } private void setEnterInstanceId(InstanceId instanceId) { @@ -1228,8 +1248,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - private void addActivityOptions(Bundle opts, StageTaskListener stage) { - opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); + private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) { + if (launchTarget != null) { + opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token); + } // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split // will be canceled. opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index 0c23f109feaf..be2e79342d07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -39,6 +39,7 @@ import android.window.TaskOrganizer; import android.window.TaskSnapshot; import androidx.annotation.BinderThread; +import androidx.annotation.VisibleForTesting; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.TriConsumer; @@ -138,10 +139,16 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo * * @param listener The callback when need a starting window. */ + @VisibleForTesting void setStartingWindowListener(TriConsumer<Integer, Integer, Integer> listener) { mTaskLaunchingCallback = listener; } + @VisibleForTesting + boolean hasStartingWindowListener() { + return mTaskLaunchingCallback != null; + } + /** * Called when a task need a starting window. */ @@ -281,6 +288,8 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo @Override public void invalidate() { mController = null; + // Unregister the listener to ensure any registered binder death recipients are unlinked + mListener.unregister(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index fdf073f0bf26..3f944cb6d628 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -164,7 +164,8 @@ public class ShellController { * Updates the given bundle with the set of external interfaces, invalidating the old set of * binders. */ - private void createExternalInterfaces(Bundle output) { + @VisibleForTesting + public void createExternalInterfaces(Bundle output) { // Invalidate the old binders for (int i = 0; i < mExternalInterfaces.size(); i++) { mExternalInterfaces.valueAt(i).invalidate(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 2e328b0736dd..2754496a6f3f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -53,6 +53,7 @@ import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.BackEvent; +import android.window.BackMotionEvent; import android.window.BackNavigationInfo; import android.window.IBackNaviAnimationController; import android.window.IOnBackInvokedCallback; @@ -246,10 +247,11 @@ public class BackAnimationControllerTest extends ShellTestCase { // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); + ArgumentCaptor<BackMotionEvent> backEventCaptor = + ArgumentCaptor.forClass(BackMotionEvent.class); verify(mIOnBackInvokedCallback).onBackStarted(backEventCaptor.capture()); assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget()); - verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackEvent.class)); + verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(any(BackMotionEvent.class)); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back @@ -271,17 +273,18 @@ public class BackAnimationControllerTest extends ShellTestCase { RemoteAnimationTarget animationTarget = createAnimationTarget(); IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class); - ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); + ArgumentCaptor<BackMotionEvent> backEventCaptor = + ArgumentCaptor.forClass(BackMotionEvent.class); createNavigationInfo(animationTarget, null, null, BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback, false); triggerBackGesture(); - verify(appCallback, never()).onBackStarted(any(BackEvent.class)); + verify(appCallback, never()).onBackStarted(any(BackMotionEvent.class)); verify(appCallback, never()).onBackProgressed(backEventCaptor.capture()); verify(appCallback, times(1)).onBackInvoked(); - verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackEvent.class)); + verify(mIOnBackInvokedCallback, never()).onBackStarted(any(BackMotionEvent.class)); verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture()); verify(mIOnBackInvokedCallback, never()).onBackInvoked(); } @@ -314,7 +317,7 @@ public class BackAnimationControllerTest extends ShellTestCase { doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class)); + verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class)); } @Test @@ -333,7 +336,7 @@ public class BackAnimationControllerTest extends ShellTestCase { doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class)); + verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class)); } @@ -349,7 +352,7 @@ public class BackAnimationControllerTest extends ShellTestCase { // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget); - verify(mIOnBackInvokedCallback).onBackStarted(any(BackEvent.class)); + verify(mIOnBackInvokedCallback).onBackStarted(any(BackMotionEvent.class)); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java index 3aefc3f03a8a..ba9c159bad28 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java @@ -19,6 +19,7 @@ package com.android.wm.shell.back; import static org.junit.Assert.assertEquals; import android.window.BackEvent; +import android.window.BackMotionEvent; import org.junit.Before; import org.junit.Test; @@ -38,7 +39,7 @@ public class TouchTrackerTest { @Test public void generatesProgress_onStart() { mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT); - BackEvent event = mTouchTracker.createStartEvent(null); + BackMotionEvent event = mTouchTracker.createStartEvent(null); assertEquals(event.getProgress(), 0f, 0f); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index e6711aca19c1..8b025cd7c246 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles; +import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -32,6 +34,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; +import android.content.Intent; import android.content.LocusId; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -94,6 +97,7 @@ public class BubbleDataTest extends ShellTestCase { private Bubble mBubbleInterruptive; private Bubble mBubbleDismissed; private Bubble mBubbleLocusId; + private Bubble mAppBubble; private BubbleData mBubbleData; private TestableBubblePositioner mPositioner; @@ -178,6 +182,11 @@ public class BubbleDataTest extends ShellTestCase { mBubbleMetadataFlagListener, mPendingIntentCanceledListener, mMainExecutor); + + Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class); + appBubbleIntent.setPackage(mContext.getPackageName()); + mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mMainExecutor); + mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class)); mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, @@ -1089,6 +1098,18 @@ public class BubbleDataTest extends ShellTestCase { assertOverflowChangedTo(ImmutableList.of()); } + @Test + public void test_removeAppBubble_skipsOverflow() { + mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/, + false /* showInShade */); + assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isEqualTo(mAppBubble); + + mBubbleData.dismissBubbleWithKey(KEY_APP_BUBBLE, Bubbles.DISMISS_USER_GESTURE); + + assertThat(mBubbleData.getOverflowBubbleWithKey(KEY_APP_BUBBLE)).isNull(); + assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull(); + } + private void verifyUpdateReceived() { verify(mListener).applyUpdate(mUpdateCaptor.capture()); reset(mListener); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index 08af3d3eecfe..35cc16852b9f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -279,7 +279,7 @@ public class DesktopModeControllerTest extends ShellTestCase { } @Test - public void testShowDesktopApps_appsAlreadyVisible_doesNothing() { + public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() { final RunningTaskInfo task1 = createFreeformTask(); mDesktopModeTaskRepository.addActiveTask(task1.taskId); mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId); @@ -294,8 +294,17 @@ public class DesktopModeControllerTest extends ShellTestCase { mController.showDesktopApps(); final WindowContainerTransaction wct = getBringAppsToFrontTransaction(); - // No reordering needed. - assertThat(wct.getHierarchyOps()).isEmpty(); + // Check wct has reorder calls + assertThat(wct.getHierarchyOps()).hasSize(2); + // Task 1 appeared first, must be first reorder to top. + HierarchyOp op1 = wct.getHierarchyOps().get(0); + assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); + assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder()); + + // Task 2 appeared last, must be last reorder to top. + HierarchyOp op2 = wct.getHierarchyOps().get(1); + assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER); + assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 9a92879bde1f..4011d080564d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -150,8 +150,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun showDesktopApps_appsAlreadyVisible_doesNothing() { - setUpHomeTask() + fun showDesktopApps_appsAlreadyVisible_bringsToFront() { + val homeTask = setUpHomeTask() val task1 = setUpFreeformTask() val task2 = setUpFreeformTask() markTaskVisible(task1) @@ -159,7 +159,12 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.showDesktopApps() - verifyWCTNotExecuted() + val wct = getLatestWct() + assertThat(wct.hierarchyOps).hasSize(3) + // Expect order to be from bottom: home, task1, task2 + wct.assertReorderAt(index = 0, homeTask) + wct.assertReorderAt(index = 1, task1) + wct.assertReorderAt(index = 2, task2) } @Test @@ -207,6 +212,23 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_otherFreeformTasksBroughtToFront() { + val homeTask = setUpHomeTask() + val freeformTask = setUpFreeformTask() + val fullscreenTask = setUpFullscreenTask() + markTaskHidden(freeformTask) + + controller.moveToDesktop(fullscreenTask) + + with(getLatestWct()) { + assertThat(hierarchyOps).hasSize(3) + assertReorderSequence(homeTask, freeformTask, fullscreenTask) + assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + } + + @Test fun moveToFullscreen() { val task = setUpFreeformTask() controller.moveToFullscreen(task) @@ -406,3 +428,9 @@ private fun WindowContainerTransaction.assertReorderAt(index: Int, task: Running assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER) assertThat(op.container).isEqualTo(task.token.asBinder()) } + +private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: RunningTaskInfo) { + for (i in tasks.indices) { + assertReorderAt(i, tasks[i]) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 7ec4e21bcfcc..35c09a121a1c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -18,7 +18,9 @@ package com.android.wm.shell.pip.phone; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; @@ -38,6 +40,7 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; +import android.os.Bundle; import android.os.RemoteException; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -62,6 +65,7 @@ import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; +import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -188,6 +192,24 @@ public class PipControllerTest extends ShellTestCase { } @Test + public void testInvalidateExternalInterface_unregistersListener() { + mPipController.setPinnedStackAnimationListener(new PipController.PipAnimationListener() { + @Override + public void onPipAnimationStarted() {} + @Override + public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) {} + @Override + public void onExpandPip() {} + }); + assertTrue(mPipController.hasPinnedStackAnimationListener()); + // Create initial interface + mShellController.createExternalInterfaces(new Bundle()); + // Recreate the interface to trigger invalidation of the previous instance + mShellController.createExternalInterfaces(new Bundle()); + assertFalse(mPipController.hasPinnedStackAnimationListener()); + } + + @Test public void createPip_notSupported_returnsNull() { Context spyContext = spy(mContext); PackageManager mockPackageManager = mock(PackageManager.class); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index f6ac3ee0a8e4..82392ad9a3eb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -45,6 +46,7 @@ import android.app.ActivityTaskManager; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Rect; +import android.os.Bundle; import android.view.SurfaceControl; import androidx.test.filters.SmallTest; @@ -87,8 +89,6 @@ public class RecentTasksControllerTest extends ShellTestCase { @Mock private TaskStackListenerImpl mTaskStackListener; @Mock - private ShellController mShellController; - @Mock private ShellCommandHandler mShellCommandHandler; @Mock private DesktopModeTaskRepository mDesktopModeTaskRepository; @@ -97,7 +97,9 @@ public class RecentTasksControllerTest extends ShellTestCase { private ShellTaskOrganizer mShellTaskOrganizer; private RecentTasksController mRecentTasksController; + private RecentTasksController mRecentTasksControllerReal; private ShellInit mShellInit; + private ShellController mShellController; private TestShellExecutor mMainExecutor; @Before @@ -105,9 +107,12 @@ public class RecentTasksControllerTest extends ShellTestCase { mMainExecutor = new TestShellExecutor(); when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); mShellInit = spy(new ShellInit(mMainExecutor)); - mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit, + mShellController = spy(new ShellController(mShellInit, mShellCommandHandler, + mMainExecutor)); + mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, - Optional.of(mDesktopModeTaskRepository), mMainExecutor)); + Optional.of(mDesktopModeTaskRepository), mMainExecutor); + mRecentTasksController = spy(mRecentTasksControllerReal); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController), mMainExecutor); @@ -132,6 +137,20 @@ public class RecentTasksControllerTest extends ShellTestCase { } @Test + public void testInvalidateExternalInterface_unregistersListener() { + // Note: We have to use the real instance of the controller here since that is the instance + // that is passed to ShellController internally, and the instance that the listener will be + // unregistered from + mRecentTasksControllerReal.registerRecentTasksListener(new IRecentTasksListener.Default()); + assertTrue(mRecentTasksControllerReal.hasRecentTasksListener()); + // Create initial interface + mShellController.createExternalInterfaces(new Bundle()); + // Recreate the interface to trigger invalidation of the previous instance + mShellController.createExternalInterfaces(new Bundle()); + assertFalse(mRecentTasksControllerReal.hasRecentTasksListener()); + } + + @Test public void testAddRemoveSplitNotifyChange() { ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index f8ded7709c68..ea3af9d96aa4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -27,11 +27,14 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -46,6 +49,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.os.Bundle; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -86,7 +90,6 @@ import org.mockito.MockitoAnnotations; public class SplitScreenControllerTests extends ShellTestCase { @Mock ShellInit mShellInit; - @Mock ShellController mShellController; @Mock ShellCommandHandler mShellCommandHandler; @Mock ShellTaskOrganizer mTaskOrganizer; @Mock SyncTransactionQueue mSyncQueue; @@ -103,12 +106,15 @@ public class SplitScreenControllerTests extends ShellTestCase { @Mock RecentTasksController mRecentTasks; @Captor ArgumentCaptor<Intent> mIntentCaptor; + private ShellController mShellController; private SplitScreenController mSplitScreenController; @Before public void setup() { assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)); MockitoAnnotations.initMocks(this); + mShellController = spy(new ShellController(mShellInit, mShellCommandHandler, + mMainExecutor)); mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit, mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController, mDisplayImeController, @@ -118,7 +124,7 @@ public class SplitScreenControllerTests extends ShellTestCase { @Test public void instantiateController_addInitCallback() { - verify(mShellInit, times(1)).addInitCallback(any(), any()); + verify(mShellInit, times(1)).addInitCallback(any(), isA(SplitScreenController.class)); } @Test @@ -159,6 +165,19 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test + public void testInvalidateExternalInterface_unregistersListener() { + mSplitScreenController.onInit(); + mSplitScreenController.registerSplitScreenListener( + new SplitScreen.SplitScreenListener() {}); + verify(mStageCoordinator).registerSplitScreenListener(any()); + // Create initial interface + mShellController.createExternalInterfaces(new Bundle()); + // Recreate the interface to trigger invalidation of the previous instance + mShellController.createExternalInterfaces(new Bundle()); + verify(mStageCoordinator).unregisterSplitScreenListener(any()); + } + + @Test public void testStartIntent_appendsNoUserActionFlag() { Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java index 90165d1cd1b2..10dec9ef12f9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java @@ -16,9 +16,12 @@ package com.android.wm.shell.startingsurface; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -27,16 +30,19 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.hardware.display.DisplayManager; +import android.os.Bundle; import android.view.Display; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.internal.util.function.TriConsumer; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellSharedConstants; @@ -59,7 +65,7 @@ public class StartingWindowControllerTests extends ShellTestCase { private @Mock Context mContext; private @Mock DisplayManager mDisplayManager; - private @Mock ShellController mShellController; + private @Mock ShellCommandHandler mShellCommandHandler; private @Mock ShellTaskOrganizer mTaskOrganizer; private @Mock ShellExecutor mMainExecutor; private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm; @@ -67,6 +73,7 @@ public class StartingWindowControllerTests extends ShellTestCase { private @Mock TransactionPool mTransactionPool; private StartingWindowController mController; private ShellInit mShellInit; + private ShellController mShellController; @Before public void setUp() { @@ -74,6 +81,8 @@ public class StartingWindowControllerTests extends ShellTestCase { doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt()); doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class)); mShellInit = spy(new ShellInit(mMainExecutor)); + mShellController = spy(new ShellController(mShellInit, mShellCommandHandler, + mMainExecutor)); mController = new StartingWindowController(mContext, mShellInit, mShellController, mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool); mShellInit.init(); @@ -81,7 +90,7 @@ public class StartingWindowControllerTests extends ShellTestCase { @Test public void instantiateController_addInitCallback() { - verify(mShellInit, times(1)).addInitCallback(any(), any()); + verify(mShellInit, times(1)).addInitCallback(any(), isA(StartingWindowController.class)); } @Test @@ -89,4 +98,18 @@ public class StartingWindowControllerTests extends ShellTestCase { verify(mShellController, times(1)).addExternalInterface( eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any()); } + + @Test + public void testInvalidateExternalInterface_unregistersListener() { + mController.setStartingWindowListener(new TriConsumer<Integer, Integer, Integer>() { + @Override + public void accept(Integer integer, Integer integer2, Integer integer3) {} + }); + assertTrue(mController.hasStartingWindowListener()); + // Create initial interface + mShellController.createExternalInterfaces(new Bundle()); + // Recreate the interface to trigger invalidation of the previous instance + mShellController.createExternalInterfaces(new Bundle()); + assertFalse(mController.hasStartingWindowListener()); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 7ec0fcdfeb64..d2e615a77471 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -190,6 +190,10 @@ public class InfoMediaManager extends MediaManager { return !isGroup; } + boolean preferRouteListingOrdering() { + return false; + } + /** * Remove a {@code device} from current media. * diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index f4355c39819f..7458f0106283 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -213,6 +213,15 @@ public class LocalMediaManager implements BluetoothCallback { } /** + * Returns if media app establishes a preferred route listing order. + * + * @return True if route list ordering exist and not using system ordering, false otherwise. + */ + public boolean isPreferenceRouteListingExist() { + return mInfoMediaManager.preferRouteListingOrdering(); + } + + /** * Start scan connected MediaDevice */ public void startScan() { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index c829bc316246..d6586db1b50a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -184,6 +184,15 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { */ public abstract String getId(); + /** + * Checks if device is suggested device from application + * + * @return true if device is suggested device + */ + public boolean isSuggestedDevice() { + return false; + } + void setConnectedRecord() { mConnectedRecord++; ConnectionRecordManager.getInstance().setConnectionRecord(mContext, getId(), diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 1b0b6b4ff796..211030a90c47 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -123,7 +123,7 @@ public class SecureSettings { Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, - Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, + Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED, Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 4fa490ffaed5..0539f09e20d3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -178,7 +178,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR); - VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.SFPS_PERFORMANT_AUTH_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index fe349f21e36e..8f70dcc02289 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -37,6 +37,8 @@ import android.view.ViewGroup import android.view.WindowManager import android.view.animation.Interpolator import android.view.animation.PathInterpolator +import androidx.annotation.BinderThread +import androidx.annotation.UiThread import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import kotlin.math.roundToInt @@ -226,7 +228,7 @@ class ActivityLaunchAnimator( // If we expect an animation, post a timeout to cancel it in case the remote animation is // never started. if (willAnimate) { - runner.postTimeout() + runner.delegate.postTimeout() // Hide the keyguard using the launch animation instead of the default unlock animation. if (hideKeyguardWithAnimation) { @@ -389,14 +391,51 @@ class ActivityLaunchAnimator( fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} } - class Runner( + @VisibleForTesting + inner class Runner( + controller: Controller, + callback: Callback, + /** The animator to use to animate the window launch. */ + launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR, + /** Listener for animation lifecycle events. */ + listener: Listener? = null + ) : IRemoteAnimationRunner.Stub() { + private val context = controller.launchContainer.context + internal val delegate: AnimationDelegate + + init { + delegate = AnimationDelegate(controller, callback, launchAnimator, listener) + } + + @BinderThread + override fun onAnimationStart( + transit: Int, + apps: Array<out RemoteAnimationTarget>?, + wallpapers: Array<out RemoteAnimationTarget>?, + nonApps: Array<out RemoteAnimationTarget>?, + finishedCallback: IRemoteAnimationFinishedCallback? + ) { + context.mainExecutor.execute { + delegate.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback) + } + } + + @BinderThread + override fun onAnimationCancelled(isKeyguardOccluded: Boolean) { + context.mainExecutor.execute { delegate.onAnimationCancelled(isKeyguardOccluded) } + } + } + + class AnimationDelegate + @JvmOverloads + constructor( private val controller: Controller, private val callback: Callback, /** The animator to use to animate the window launch. */ private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR, /** Listener for animation lifecycle events. */ private val listener: Listener? = null - ) : IRemoteAnimationRunner.Stub() { + ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> { private val launchContainer = controller.launchContainer private val context = launchContainer.context private val transactionApplierView = @@ -419,6 +458,7 @@ class ActivityLaunchAnimator( // posting it. private var onTimeout = Runnable { onAnimationTimedOut() } + @UiThread internal fun postTimeout() { launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT) } @@ -427,19 +467,20 @@ class ActivityLaunchAnimator( launchContainer.removeCallbacks(onTimeout) } + @UiThread override fun onAnimationStart( @WindowManager.TransitionOldType transit: Int, apps: Array<out RemoteAnimationTarget>?, wallpapers: Array<out RemoteAnimationTarget>?, nonApps: Array<out RemoteAnimationTarget>?, - iCallback: IRemoteAnimationFinishedCallback? + callback: IRemoteAnimationFinishedCallback? ) { removeTimeout() // The animation was started too late and we already notified the controller that it // timed out. if (timedOut) { - iCallback?.invoke() + callback?.invoke() return } @@ -449,7 +490,7 @@ class ActivityLaunchAnimator( return } - context.mainExecutor.execute { startAnimation(apps, nonApps, iCallback) } + startAnimation(apps, nonApps, callback) } private fun startAnimation( @@ -687,6 +728,7 @@ class ActivityLaunchAnimator( controller.onLaunchAnimationCancelled() } + @UiThread override fun onAnimationCancelled(isKeyguardOccluded: Boolean) { if (timedOut) { return @@ -695,10 +737,9 @@ class ActivityLaunchAnimator( Log.i(TAG, "Remote animation was cancelled") cancelled = true removeTimeout() - context.mainExecutor.execute { - animation?.cancel() - controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded) - } + + animation?.cancel() + controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded) } private fun IRemoteAnimationFinishedCallback.invoke() { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 54aa3516d867..a450d3af334b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -366,7 +366,7 @@ constructor( val dialog = animatedDialog.dialog // Don't animate if the dialog is not showing or if we are locked and going to show the - // bouncer. + // primary bouncer. if ( !dialog.isShowing || (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock()) diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt new file mode 100644 index 000000000000..337408bb9c5d --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt @@ -0,0 +1,30 @@ +package com.android.systemui.animation + +import android.annotation.UiThread +import android.view.IRemoteAnimationFinishedCallback +import android.view.RemoteAnimationTarget +import android.view.WindowManager + +/** + * A component capable of running remote animations. + * + * Expands the IRemoteAnimationRunner API by allowing for different types of more specialized + * callbacks. + */ +interface RemoteAnimationDelegate<in T : IRemoteAnimationFinishedCallback> { + /** + * Called on the UI thread when the animation targets are received. Sets up and kicks off the + * animation. + */ + @UiThread + fun onAnimationStart( + @WindowManager.TransitionOldType transit: Int, + apps: Array<out RemoteAnimationTarget>?, + wallpapers: Array<out RemoteAnimationTarget>?, + nonApps: Array<out RemoteAnimationTarget>?, + callback: T? + ) + + /** Called on the UI thread when a signal is received to cancel the animation. */ + @UiThread fun onAnimationCancelled(isKeyguardOccluded: Boolean) +} 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 462b90a10aee..86bd5f2bff5a 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 @@ -54,7 +54,6 @@ class AnimatableClockView @JvmOverloads constructor( defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - var tag: String = "UnnamedClockView" var logBuffer: LogBuffer? = null private val time = Calendar.getInstance() @@ -132,7 +131,7 @@ class AnimatableClockView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - logBuffer?.log(tag, DEBUG, "onAttachedToWindow") + logBuffer?.log(TAG, DEBUG, "onAttachedToWindow") refreshFormat() } @@ -148,7 +147,7 @@ class AnimatableClockView @JvmOverloads constructor( time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis() contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) - logBuffer?.log(tag, DEBUG, + logBuffer?.log(TAG, DEBUG, { str1 = formattedText?.toString() }, { "refreshTime: new formattedText=$str1" } ) @@ -157,7 +156,7 @@ class AnimatableClockView @JvmOverloads constructor( // relayout if the text didn't actually change. if (!TextUtils.equals(text, formattedText)) { text = formattedText - logBuffer?.log(tag, DEBUG, + logBuffer?.log(TAG, DEBUG, { str1 = formattedText?.toString() }, { "refreshTime: done setting new time text to: $str1" } ) @@ -167,17 +166,17 @@ class AnimatableClockView @JvmOverloads constructor( // without being notified TextInterpolator being notified. if (layout != null) { textAnimator?.updateLayout(layout) - logBuffer?.log(tag, DEBUG, "refreshTime: done updating textAnimator layout") + logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout") } requestLayout() - logBuffer?.log(tag, DEBUG, "refreshTime: after requestLayout") + logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout") } } fun onTimeZoneChanged(timeZone: TimeZone?) { time.timeZone = timeZone refreshFormat() - logBuffer?.log(tag, DEBUG, + logBuffer?.log(TAG, DEBUG, { str1 = timeZone?.toString() }, { "onTimeZoneChanged newTimeZone=$str1" } ) @@ -194,7 +193,7 @@ class AnimatableClockView @JvmOverloads constructor( } else { animator.updateLayout(layout) } - logBuffer?.log(tag, DEBUG, "onMeasure") + logBuffer?.log(TAG, DEBUG, "onMeasure") } override fun onDraw(canvas: Canvas) { @@ -206,12 +205,12 @@ class AnimatableClockView @JvmOverloads constructor( } else { super.onDraw(canvas) } - logBuffer?.log(tag, DEBUG, "onDraw lastDraw") + logBuffer?.log(TAG, DEBUG, "onDraw") } override fun invalidate() { super.invalidate() - logBuffer?.log(tag, DEBUG, "invalidate") + logBuffer?.log(TAG, DEBUG, "invalidate") } override fun onTextChanged( @@ -221,7 +220,7 @@ class AnimatableClockView @JvmOverloads constructor( lengthAfter: Int ) { super.onTextChanged(text, start, lengthBefore, lengthAfter) - logBuffer?.log(tag, DEBUG, + logBuffer?.log(TAG, DEBUG, { str1 = text.toString() }, { "onTextChanged text=$str1" } ) @@ -238,7 +237,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateColorChange() { - logBuffer?.log(tag, DEBUG, "animateColorChange") + logBuffer?.log(TAG, DEBUG, "animateColorChange") setTextStyle( weight = lockScreenWeight, textSize = -1f, @@ -260,7 +259,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateAppearOnLockscreen() { - logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen") + logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen") setTextStyle( weight = dozingWeight, textSize = -1f, @@ -285,7 +284,7 @@ class AnimatableClockView @JvmOverloads constructor( if (isAnimationEnabled && textAnimator == null) { return } - logBuffer?.log(tag, DEBUG, "animateFoldAppear") + logBuffer?.log(TAG, DEBUG, "animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, textSize = -1f, @@ -312,7 +311,7 @@ class AnimatableClockView @JvmOverloads constructor( // Skip charge animation if dozing animation is already playing. return } - logBuffer?.log(tag, DEBUG, "animateCharge") + logBuffer?.log(TAG, DEBUG, "animateCharge") val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, @@ -336,7 +335,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateDoze(isDozing: Boolean, animate: Boolean) { - logBuffer?.log(tag, DEBUG, "animateDoze") + logBuffer?.log(TAG, DEBUG, "animateDoze") setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, textSize = -1f, @@ -455,7 +454,7 @@ class AnimatableClockView @JvmOverloads constructor( isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 else -> DOUBLE_LINE_FORMAT_12_HOUR } - logBuffer?.log(tag, DEBUG, + logBuffer?.log(TAG, DEBUG, { str1 = format?.toString() }, { "refreshFormat format=$str1" } ) @@ -466,6 +465,7 @@ class AnimatableClockView @JvmOverloads constructor( fun dump(pw: PrintWriter) { pw.println("$this") + pw.println(" alpha=$alpha") pw.println(" measuredWidth=$measuredWidth") pw.println(" measuredHeight=$measuredHeight") pw.println(" singleLineInternal=$isSingleLineInternal") @@ -626,7 +626,7 @@ class AnimatableClockView @JvmOverloads constructor( } companion object { - private val TAG = AnimatableClockView::class.simpleName + private val TAG = AnimatableClockView::class.simpleName!! const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600 private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm" private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm" diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index e138ef8a1ea8..7645decfde24 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -88,13 +88,6 @@ class DefaultClockController( events.onTimeTick() } - override fun setLogBuffer(logBuffer: LogBuffer) { - smallClock.view.tag = "smallClockView" - largeClock.view.tag = "largeClockView" - smallClock.view.logBuffer = logBuffer - largeClock.view.logBuffer = logBuffer - } - open inner class DefaultClockFaceController( override val view: AnimatableClockView, ) : ClockFaceController { @@ -104,6 +97,12 @@ class DefaultClockController( private var isRegionDark = false protected var targetRegion: Rect? = null + override var logBuffer: LogBuffer? + get() = view.logBuffer + set(value) { + view.logBuffer = value + } + init { view.setColors(currentColor, currentColor) } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 66e44b9005de..a2a07095c16c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -71,9 +71,6 @@ interface ClockController { /** Optional method for dumping debug information */ fun dump(pw: PrintWriter) {} - - /** Optional method for debug logging */ - fun setLogBuffer(logBuffer: LogBuffer) {} } /** Interface for a specific clock face version rendered by the clock */ @@ -83,6 +80,9 @@ interface ClockFaceController { /** Events specific to this clock face */ val events: ClockFaceEvents + + /** Some clocks may log debug information */ + var logBuffer: LogBuffer? } /** Events that should call when various rendering parameters change */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt index 6436dcb5f613..e99b2149bc1d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt @@ -159,8 +159,13 @@ constructor( * bug report more actionable, so using the [log] with a messagePrinter to add more detail to * every log may do more to improve overall logging than adding more logs with this method. */ - fun log(tag: String, level: LogLevel, @CompileTimeConstant message: String) = - log(tag, level, { str1 = message }, { str1!! }) + @JvmOverloads + fun log( + tag: String, + level: LogLevel, + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(tag, level, { str1 = message }, { str1!! }, exception) /** * You should call [log] instead of this method. diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index f96644fbd667..030eaa660c9f 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -16,6 +16,18 @@ public <init>(); } +# Needed to ensure callback field references are kept in their respective +# owning classes when the downstream callback registrars only store weak refs. +# TODO(b/264686688): Handle these cases with more targeted annotations. +-keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { + private com.android.keyguard.KeyguardUpdateMonitorCallback *; + private com.android.systemui.privacy.PrivacyItemController$Callback *; + private com.android.systemui.settings.UserTracker$Callback *; + private com.android.systemui.statusbar.phone.StatusBarWindowCallback *; + private com.android.systemui.util.service.Observer$Callback *; + private com.android.systemui.util.service.ObservableServiceConnection$Callback *; +} + -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); } diff --git a/packages/SystemUI/res/drawable/overlay_badge_background.xml b/packages/SystemUI/res/drawable/overlay_badge_background.xml index 857632edcf0d..53122c17e320 100644 --- a/packages/SystemUI/res/drawable/overlay_badge_background.xml +++ b/packages/SystemUI/res/drawable/overlay_badge_background.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2020 The Android Open Source Project + ~ 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. @@ -14,8 +14,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="oval"> - <solid android:color="?androidprv:attr/colorSurface"/> -</shape> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:pathData="M0,0M48,48"/> +</vector> diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml index bc97e511e7f4..8cf4f4de27da 100644 --- a/packages/SystemUI/res/layout/chipbar.xml +++ b/packages/SystemUI/res/layout/chipbar.xml @@ -23,6 +23,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> + <!-- Extra marginBottom to give room for the drop shadow. --> <LinearLayout android:id="@+id/chipbar_inner" android:orientation="horizontal" @@ -33,6 +34,8 @@ android:layout_marginTop="20dp" android:layout_marginStart="@dimen/notification_side_paddings" android:layout_marginEnd="@dimen/notification_side_paddings" + android:translationZ="4dp" + android:layout_marginBottom="8dp" android:clipToPadding="false" android:gravity="center_vertical" android:alpha="0.0" diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 9134f96f59e1..eec3b11519b1 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -32,26 +32,26 @@ android:elevation="4dp" android:background="@drawable/action_chip_container_background" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" - app:layout_constraintBottom_toBottomOf="@+id/actions_container" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/actions_container" - app:layout_constraintEnd_toEndOf="@+id/actions_container"/> + app:layout_constraintEnd_toEndOf="@+id/actions_container" + app:layout_constraintBottom_toBottomOf="parent"/> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" - android:paddingEnd="@dimen/overlay_action_container_padding_right" + android:paddingEnd="@dimen/overlay_action_container_padding_end" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:scrollbars="none" - android:layout_marginBottom="4dp" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_percent="1.0" app:layout_constraintWidth_max="wrap" - app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/preview_border" - app:layout_constraintEnd_toEndOf="parent"> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"> <LinearLayout android:id="@+id/actions" android:layout_width="wrap_content" @@ -69,44 +69,30 @@ android:id="@+id/preview_border" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginStart="@dimen/overlay_offset_x" - android:layout_marginBottom="12dp" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginStart="@dimen/overlay_preview_container_margin" + android:layout_marginTop="@dimen/overlay_border_width_neg" + android:layout_marginEnd="@dimen/overlay_border_width_neg" + android:layout_marginBottom="@dimen/overlay_preview_container_margin" android:elevation="7dp" - app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end" - app:layout_constraintTop_toTopOf="@id/clipboard_preview_top" - android:background="@drawable/overlay_border"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/clipboard_preview_end" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierMargin="@dimen/overlay_border_width" - app:barrierDirection="end" - app:constraint_referenced_ids="clipboard_preview"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/clipboard_preview_top" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierDirection="top" - app:barrierMargin="@dimen/overlay_border_width_neg" - app:constraint_referenced_ids="clipboard_preview"/> + android:background="@drawable/overlay_border" + app:layout_constraintStart_toStartOf="@id/actions_container_background" + app:layout_constraintTop_toTopOf="@id/clipboard_preview" + app:layout_constraintEnd_toEndOf="@id/clipboard_preview" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/> <FrameLayout android:id="@+id/clipboard_preview" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/overlay_border_width" + android:layout_marginBottom="@dimen/overlay_border_width" + android:layout_gravity="center" android:elevation="7dp" android:background="@drawable/overlay_preview_background" android:clipChildren="true" android:clipToOutline="true" android:clipToPadding="true" - android:layout_width="@dimen/clipboard_preview_size" - android:layout_margin="@dimen/overlay_border_width" - android:layout_height="wrap_content" - android:layout_gravity="center" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintBottom_toBottomOf="@id/preview_border" app:layout_constraintStart_toStartOf="@id/preview_border" - app:layout_constraintEnd_toEndOf="@id/preview_border" - app:layout_constraintTop_toTopOf="@id/preview_border"> + app:layout_constraintBottom_toBottomOf="@id/preview_border"> <TextView android:id="@+id/text_preview" android:textFontWeight="500" android:padding="8dp" diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index a565988c14ad..d68982876448 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -148,9 +148,4 @@ <include layout="@layout/ongoing_privacy_chip"/> </FrameLayout> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:id="@+id/space" - /> </com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 9add32c6ee0a..885e5e2d4441 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -57,6 +57,7 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" + android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_alarm" android:tint="@android:color/white" android:visibility="gone" @@ -67,6 +68,7 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" + android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_qs_dnd_on" android:tint="@android:color/white" android:visibility="gone" @@ -77,6 +79,7 @@ android:layout_width="@dimen/dream_overlay_status_bar_icon_size" android:layout_height="match_parent" android:layout_marginStart="@dimen/dream_overlay_status_icon_margin" + android:layout_marginTop="@dimen/dream_overlay_status_bar_marginTop" android:src="@drawable/ic_signal_wifi_off" android:visibility="gone" android:contentDescription="@string/dream_overlay_status_bar_wifi_off" /> diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml index e4e0bd45a2db..496eb6e6130e 100644 --- a/packages/SystemUI/res/layout/screenshot_static.xml +++ b/packages/SystemUI/res/layout/screenshot_static.xml @@ -27,26 +27,26 @@ android:elevation="4dp" android:background="@drawable/action_chip_container_background" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" - app:layout_constraintBottom_toBottomOf="@+id/actions_container" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/actions_container" - app:layout_constraintEnd_toEndOf="@+id/actions_container"/> + app:layout_constraintEnd_toEndOf="@+id/actions_container" + app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"/> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" - android:layout_marginBottom="4dp" - android:paddingEnd="@dimen/overlay_action_container_padding_right" + android:paddingEnd="@dimen/overlay_action_container_padding_end" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:scrollbars="none" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_percent="1.0" app:layout_constraintWidth_max="wrap" - app:layout_constraintBottom_toTopOf="@id/screenshot_message_container" app:layout_constraintStart_toEndOf="@+id/screenshot_preview_border" - app:layout_constraintEnd_toEndOf="parent"> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"> <LinearLayout android:id="@+id/screenshot_actions" android:layout_width="wrap_content" @@ -64,35 +64,24 @@ android:id="@+id/screenshot_preview_border" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginStart="@dimen/overlay_offset_x" - android:layout_marginBottom="12dp" + android:layout_marginStart="@dimen/overlay_preview_container_margin" + android:layout_marginTop="@dimen/overlay_border_width_neg" + android:layout_marginEnd="@dimen/overlay_border_width_neg" + android:layout_marginBottom="@dimen/overlay_preview_container_margin" android:elevation="7dp" android:alpha="0" android:background="@drawable/overlay_border" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toTopOf="@id/screenshot_message_container" - app:layout_constraintEnd_toEndOf="@id/screenshot_preview_end" - app:layout_constraintTop_toTopOf="@id/screenshot_preview_top"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/screenshot_preview_end" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierMargin="@dimen/overlay_border_width" - app:barrierDirection="end" - app:constraint_referenced_ids="screenshot_preview"/> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/screenshot_preview_top" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierDirection="top" - app:barrierMargin="@dimen/overlay_border_width_neg" - app:constraint_referenced_ids="screenshot_preview"/> + app:layout_constraintStart_toStartOf="@id/actions_container_background" + app:layout_constraintTop_toTopOf="@id/screenshot_preview" + app:layout_constraintEnd_toEndOf="@id/screenshot_preview" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/> <ImageView android:id="@+id/screenshot_preview" android:visibility="invisible" android:layout_width="@dimen/overlay_x_scale" - android:layout_margin="@dimen/overlay_border_width" android:layout_height="wrap_content" + android:layout_marginStart="@dimen/overlay_border_width" + android:layout_marginBottom="@dimen/overlay_border_width" android:layout_gravity="center" android:elevation="7dp" android:contentDescription="@string/screenshot_edit_description" @@ -100,20 +89,14 @@ android:background="@drawable/overlay_preview_background" android:adjustViewBounds="true" android:clickable="true" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border" app:layout_constraintStart_toStartOf="@id/screenshot_preview_border" - app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border" - app:layout_constraintTop_toTopOf="@id/screenshot_preview_border"/> + app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/> <ImageView android:id="@+id/screenshot_badge" - android:layout_width="24dp" - android:layout_height="24dp" - android:padding="4dp" + android:layout_width="48dp" + android:layout_height="48dp" android:visibility="gone" - android:background="@drawable/overlay_badge_background" android:elevation="8dp" - android:src="@drawable/overlay_cancel" app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border" app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/> <FrameLayout @@ -150,7 +133,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal" android:layout_marginVertical="4dp" - android:paddingHorizontal="@dimen/overlay_action_container_padding_right" + android:paddingHorizontal="@dimen/overlay_action_container_padding_end" android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:background="@drawable/action_chip_container_background" diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml index fa9d7390dcf8..7eaed4356f46 100644 --- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml +++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml @@ -46,7 +46,7 @@ app:layout_constraintEnd_toEndOf="parent" app:flow_horizontalBias="0.5" app:flow_verticalAlign="center" - app:flow_wrapMode="chain" + app:flow_wrapMode="chain2" app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap" app:flow_verticalGap="44dp" app:flow_horizontalStyle="packed"/> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 1ab7594ca0fb..67efa7570e4f 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -377,7 +377,7 @@ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kada dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se vidi na ekranu ili što se reproducira na uređaju. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string> <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kada aplikaciju dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string> <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string> - <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijelite ili snimite aplikaciju"</string> + <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijelite ili snimajte aplikaciju"</string> <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Dozvoliti aplikaciji da dijeli ili snima?"</string> <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kada dijelite, snimate ili emitirate, aplikacija ima pristup svemu što je vidljivo na ekranu ili što se reproducira na uređaju. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string> <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kada dijelite, snimate ili emitirate aplikaciju, ona ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 2c960c62478c..7c902807860b 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -853,8 +853,7 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string> - <!-- no translation found for media_transfer_loading (5544017127027152422) --> - <skip /> + <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index d09f71f944d1..f195c640bd41 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -853,8 +853,7 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string> - <!-- no translation found for media_transfer_loading (5544017127027152422) --> - <skip /> + <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 2c960c62478c..7c902807860b 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -853,8 +853,7 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string> - <!-- no translation found for media_transfer_loading (5544017127027152422) --> - <skip /> + <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 2c960c62478c..7c902807860b 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -853,8 +853,7 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string> - <!-- no translation found for media_transfer_loading (5544017127027152422) --> - <skip /> + <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 7b0837d1a77a..03659b4f7c69 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -853,8 +853,7 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string> - <!-- no translation found for media_transfer_loading (5544017127027152422) --> - <skip /> + <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string> <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 55303e8b8f2e..2dc01f110b2c 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -648,7 +648,7 @@ <string name="right_keycode" msgid="2480715509844798438">"Clave de código derecho"</string> <string name="left_icon" msgid="5036278531966897006">"Ícono izquierdo"</string> <string name="right_icon" msgid="1103955040645237425">"Ícono derecho"</string> - <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén presionado y arrastra para agregar mosaicos"</string> + <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén presionado y arrastra para agregar tarjetas"</string> <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Mantén presionado y arrastra para reorganizar los mosaicos"</string> <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Arrastra aquí para quitar"</string> <string name="drag_to_remove_disabled" msgid="933046987838658850">"Necesitas al menos <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> tarjetas"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 3872b0dc26cc..98fe304a06b4 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -372,10 +372,10 @@ <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string> <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> मार्फत रेकर्ड गर्न वा cast गर्न थाल्ने हो?"</string> <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> लाई सेयर गर्न वा रेकर्ड गर्न दिने हो?"</string> - <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूर्ण स्क्रिन"</string> + <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"सबै स्क्रिन"</string> <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"एकल एप"</string> - <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले सेयर, रेकर्ड वा कास्ट गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string> - <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string> + <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले सेयर, रेकर्ड वा कास्ट गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string> <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी राख्नुहोस्"</string> <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"सेयर वा रेकर्ड गर्नका लागि एप चयन गर्नुहोस्"</string> <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"यो एपलाई सेयर गर्न वा रेकर्ड गर्न दिने हो?"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index d4996c9f0079..df5dcb9059df 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -375,7 +375,7 @@ <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Celoten zaslon"</string> <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Posamezna aplikacija"</string> <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Pri deljenju, snemanju ali predvajanju ima aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string> - <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Pri deljenju, snemanju ali predvajanju aplikacije ima aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string> + <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Pri deljenju, snemanju ali predvajanju aplikacije ima <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string> <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Naprej"</string> <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deljenje ali snemanje aplikacije"</string> <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Ali tej aplikaciji dovolite deljenje ali snemanje?"</string> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 7d72598c34cf..077ef0f16fe2 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -437,6 +437,11 @@ This name is in the ComponentName flattened format (package/class) --> <string name="config_screenshotEditor" translatable="false"></string> + <!-- ComponentName for the file browsing app that the system would expect to be used in work + profile. The icon for this app will be shown to the user when informing them that a + screenshot has been saved to work profile. If blank, a default icon will be shown. --> + <string name="config_sceenshotWorkProfileFilesApp" translatable="false"></string> + <!-- Remote copy default activity. Must handle REMOTE_COPY_ACTION intents. This name is in the ComponentName flattened format (package/class) --> <string name="config_remoteCopyPackage" translatable="false"></string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ecb656091478..0fc1d13ffdbe 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -334,15 +334,22 @@ <dimen name="overlay_action_chip_spacing">8dp</dimen> <dimen name="overlay_action_chip_text_size">14sp</dimen> <dimen name="overlay_offset_x">16dp</dimen> + <!-- Used for both start and bottom margin of the preview, relative to the action container --> + <dimen name="overlay_preview_container_margin">8dp</dimen> <dimen name="overlay_action_container_margin_horizontal">8dp</dimen> + <dimen name="overlay_action_container_margin_bottom">4dp</dimen> <dimen name="overlay_bg_protection_height">242dp</dimen> <dimen name="overlay_action_container_corner_radius">18dp</dimen> <dimen name="overlay_action_container_padding_vertical">4dp</dimen> <dimen name="overlay_action_container_padding_right">8dp</dimen> + <dimen name="overlay_action_container_padding_end">8dp</dimen> <dimen name="overlay_dismiss_button_tappable_size">48dp</dimen> <dimen name="overlay_dismiss_button_margin">8dp</dimen> + <!-- must be kept aligned with overlay_border_width_neg, below; + overlay_border_width = overlay_border_width_neg * -1 --> <dimen name="overlay_border_width">4dp</dimen> - <!-- need a negative margin for some of the constraints. should be overlay_border_width * -1 --> + <!-- some constraints use a negative margin. must be aligned with overlay_border_width, above; + overlay_border_width_neg = overlay_border_width * -1 --> <dimen name="overlay_border_width_neg">-4dp</dimen> <dimen name="clipboard_preview_size">@dimen/overlay_x_scale</dimen> @@ -1030,8 +1037,6 @@ <dimen name="ongoing_appops_dialog_side_padding">16dp</dimen> - <!-- Size of the RAT type for CellularTile --> - <!-- Size of media cards in the QSPanel carousel --> <dimen name="qs_media_padding">16dp</dimen> <dimen name="qs_media_album_radius">14dp</dimen> @@ -1279,6 +1284,12 @@ <!-- OCCLUDED -> LOCKSCREEN transition: Amount to shift lockscreen content on entering --> <dimen name="occluded_to_lockscreen_transition_lockscreen_translation_y">40dp</dimen> + <!-- LOCKSCREEN -> DREAMING transition: Amount to shift lockscreen content on entering --> + <dimen name="lockscreen_to_dreaming_transition_lockscreen_translation_y">-40dp</dimen> + + <!-- LOCKSCREEN -> OCCLUDED transition: Amount to shift lockscreen content on entering --> + <dimen name="lockscreen_to_occluded_transition_lockscreen_translation_y">-40dp</dimen> + <!-- The amount of vertical offset for the keyguard during the full shade transition. --> <dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen> @@ -1617,6 +1628,8 @@ <dimen name="dream_overlay_status_bar_ambient_text_shadow_dx">0.5dp</dimen> <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">2dp</dimen> + <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen> + <dimen name="dream_overlay_status_bar_marginTop">22dp</dimen> <!-- Default device corner radius, used for assist UI --> <dimen name="config_rounded_mask_size">0px</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 977adde635aa..22d4c6d651e2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -240,7 +240,9 @@ <!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] --> <string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string> <!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] --> - <string name="screenshot_work_profile_notification" translatable="false">Work screenshots are saved in the work <xliff:g id="app" example="Files">%1$s</xliff:g> app</string> + <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string> + <!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] --> + <string name="screenshot_default_files_app_name">Files</string> <!-- Notification title displayed for screen recording [CHAR LIMIT=50]--> <string name="screenrecord_name">Screen Recorder</string> @@ -2364,6 +2366,8 @@ <string name="media_transfer_failed">Something went wrong. Try again.</string> <!-- Text to indicate that a media transfer is currently in-progress, aka loading. [CHAR LIMIT=NONE] --> <string name="media_transfer_loading">Loading</string> + <!-- Default name of the device. [CHAR LIMIT=30] --> + <string name="media_ttt_default_device_type">tablet</string> <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] --> <string name="controls_error_timeout">Inactive, check app</string> @@ -2412,6 +2416,8 @@ <string name="media_output_dialog_volume_percentage"><xliff:g id="percentage" example="10">%1$d</xliff:g>%%</string> <!-- Title for Speakers and Displays group. [CHAR LIMIT=NONE] --> <string name="media_output_group_title_speakers_and_displays">Speakers & Displays</string> + <!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] --> + <string name="media_output_group_title_suggested_device">Suggested Devices</string> <!-- Media Output Broadcast Dialog --> <!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] --> @@ -2766,6 +2772,9 @@ <!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] --> <string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string> - <!-- Title for notification of low stylus battery. [CHAR_LIMIT=NONE] --> - <string name="stylus_battery_low">Stylus battery low</string> + <!-- Title for notification of low stylus battery with percentage. "percentage" is + the value of the battery capacity remaining [CHAR LIMIT=none]--> + <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string> + <!-- Subtitle for the notification sent when a stylus battery is low. [CHAR LIMIT=none]--> + <string name="stylus_battery_low_subtitle">Connect your stylus to a charger</string> </resources> diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml index eca2b2acb079..d97031f35d6b 100644 --- a/packages/SystemUI/res/xml/qs_header.xml +++ b/packages/SystemUI/res/xml/qs_header.xml @@ -56,13 +56,9 @@ <Layout android:layout_width="wrap_content" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constrainedWidth="true" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/space" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/carrier_group" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintHorizontal_chainStyle="spread_inside" /> </Constraint> @@ -87,39 +83,27 @@ <Constraint android:id="@+id/statusIcons"> <Layout - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constraintStart_toEndOf="@id/space" + app:layout_constraintWidth_default="wrap" + app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" app:layout_constraintTop_toTopOf="@id/date" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintHorizontal_bias="1" + app:layout_constraintBottom_toBottomOf="@id/date" /> </Constraint> <Constraint android:id="@+id/batteryRemainingIcon"> <Layout - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="@dimen/new_qs_header_non_clickable_element_height" + app:layout_constraintWidth_default="wrap" app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height" - app:layout_constraintStart_toEndOf="@id/statusIcons" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/date" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintHorizontal_bias="1" - app:layout_constraintHorizontal_chainStyle="spread_inside" + app:layout_constraintBottom_toBottomOf="@id/date" /> </Constraint> - - <Constraint - android:id="@id/space"> - <Layout - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintStart_toEndOf="@id/date" - app:layout_constraintEnd_toStartOf="@id/statusIcons" - /> - </Constraint> </ConstraintSet>
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt index 25d272185bc0..9b73cc3ea9f8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt @@ -48,48 +48,28 @@ constructor( val drawableInsetSize: Int try { val keyShadowBlur = - attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextView_keyShadowBlur, 0) + attributes.getDimension(R.styleable.DoubleShadowTextView_keyShadowBlur, 0f) val keyShadowOffsetX = - attributes.getDimensionPixelSize( - R.styleable.DoubleShadowTextView_keyShadowOffsetX, - 0 - ) + attributes.getDimension(R.styleable.DoubleShadowTextView_keyShadowOffsetX, 0f) val keyShadowOffsetY = - attributes.getDimensionPixelSize( - R.styleable.DoubleShadowTextView_keyShadowOffsetY, - 0 - ) + attributes.getDimension(R.styleable.DoubleShadowTextView_keyShadowOffsetY, 0f) val keyShadowAlpha = attributes.getFloat(R.styleable.DoubleShadowTextView_keyShadowAlpha, 0f) mKeyShadowInfo = - ShadowInfo( - keyShadowBlur.toFloat(), - keyShadowOffsetX.toFloat(), - keyShadowOffsetY.toFloat(), - keyShadowAlpha - ) + ShadowInfo(keyShadowBlur, keyShadowOffsetX, keyShadowOffsetY, keyShadowAlpha) val ambientShadowBlur = - attributes.getDimensionPixelSize( - R.styleable.DoubleShadowTextView_ambientShadowBlur, - 0 - ) + attributes.getDimension(R.styleable.DoubleShadowTextView_ambientShadowBlur, 0f) val ambientShadowOffsetX = - attributes.getDimensionPixelSize( - R.styleable.DoubleShadowTextView_ambientShadowOffsetX, - 0 - ) + attributes.getDimension(R.styleable.DoubleShadowTextView_ambientShadowOffsetX, 0f) val ambientShadowOffsetY = - attributes.getDimensionPixelSize( - R.styleable.DoubleShadowTextView_ambientShadowOffsetY, - 0 - ) + attributes.getDimension(R.styleable.DoubleShadowTextView_ambientShadowOffsetY, 0f) val ambientShadowAlpha = attributes.getFloat(R.styleable.DoubleShadowTextView_ambientShadowAlpha, 0f) mAmbientShadowInfo = ShadowInfo( - ambientShadowBlur.toFloat(), - ambientShadowOffsetX.toFloat(), - ambientShadowOffsetY.toFloat(), + ambientShadowBlur, + ambientShadowOffsetX, + ambientShadowOffsetY, ambientShadowAlpha ) drawableSize = diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 8f38e5800015..a45ce422dca5 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -38,9 +38,11 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.log.dagger.KeyguardClockLog +import com.android.systemui.log.dagger.KeyguardSmallClockLog +import com.android.systemui.log.dagger.KeyguardLargeClockLog import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback @@ -73,16 +75,18 @@ open class ClockEventController @Inject constructor( private val context: Context, @Main private val mainExecutor: Executor, @Background private val bgExecutor: Executor, - @KeyguardClockLog private val logBuffer: LogBuffer?, + @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?, + @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?, private val featureFlags: FeatureFlags ) { var clock: ClockController? = null set(value) { field = value if (value != null) { - if (logBuffer != null) { - value.setLogBuffer(logBuffer) - } + smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) + value.smallClock.logBuffer = smallLogBuffer + largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) + value.largeClock.logBuffer = largeLogBuffer value.initialize(resources, dozeAmount, 0f) updateRegionSamplers(value) @@ -325,4 +329,8 @@ open class ClockEventController @Inject constructor( } } } + + companion object { + private val TAG = ClockEventController::class.simpleName!! + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 62babadc45d8..4acbb0aaf1d8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -7,7 +7,6 @@ import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -20,11 +19,15 @@ import com.android.keyguard.dagger.KeyguardStatusViewScope; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.ClockController; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogLevel; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import kotlin.Unit; + /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. */ @@ -87,6 +90,7 @@ public class KeyguardClockSwitch extends RelativeLayout { private int mClockSwitchYAmount; @VisibleForTesting boolean mChildrenAreLaidOut = false; @VisibleForTesting boolean mAnimateOnLayout = true; + private LogBuffer mLogBuffer = null; public KeyguardClockSwitch(Context context, AttributeSet attrs) { super(context, attrs); @@ -113,6 +117,14 @@ public class KeyguardClockSwitch extends RelativeLayout { onDensityOrFontScaleChanged(); } + public void setLogBuffer(LogBuffer logBuffer) { + mLogBuffer = logBuffer; + } + + public LogBuffer getLogBuffer() { + return mLogBuffer; + } + void setClock(ClockController clock, int statusBarState) { mClock = clock; @@ -121,12 +133,16 @@ public class KeyguardClockSwitch extends RelativeLayout { mLargeClockFrame.removeAllViews(); if (clock == null) { - Log.e(TAG, "No clock being shown"); + if (mLogBuffer != null) { + mLogBuffer.log(TAG, LogLevel.ERROR, "No clock being shown"); + } return; } // Attach small and big clock views to hierarchy. - Log.i(TAG, "Attached new clock views to switch"); + if (mLogBuffer != null) { + mLogBuffer.log(TAG, LogLevel.INFO, "Attached new clock views to switch"); + } mSmallClockFrame.addView(clock.getSmallClock().getView()); mLargeClockFrame.addView(clock.getLargeClock().getView()); updateClockTargetRegions(); @@ -152,8 +168,18 @@ public class KeyguardClockSwitch extends RelativeLayout { } private void updateClockViews(boolean useLargeClock, boolean animate) { - Log.i(TAG, "updateClockViews; useLargeClock=" + useLargeClock + "; animate=" + animate - + "; mChildrenAreLaidOut=" + mChildrenAreLaidOut); + if (mLogBuffer != null) { + mLogBuffer.log(TAG, LogLevel.DEBUG, (msg) -> { + msg.setBool1(useLargeClock); + msg.setBool2(animate); + msg.setBool3(mChildrenAreLaidOut); + return Unit.INSTANCE; + }, (msg) -> "updateClockViews" + + "; useLargeClock=" + msg.getBool1() + + "; animate=" + msg.getBool2() + + "; mChildrenAreLaidOut=" + msg.getBool3()); + } + if (mClockInAnim != null) mClockInAnim.cancel(); if (mClockOutAnim != null) mClockOutAnim.cancel(); if (mStatusAreaAnim != null) mStatusAreaAnim.cancel(); @@ -183,6 +209,7 @@ public class KeyguardClockSwitch extends RelativeLayout { if (!animate) { out.setAlpha(0f); + out.setVisibility(INVISIBLE); in.setAlpha(1f); in.setVisibility(VISIBLE); mStatusArea.setTranslationY(statusAreaYTranslation); @@ -198,7 +225,10 @@ public class KeyguardClockSwitch extends RelativeLayout { direction * -mClockSwitchYAmount)); mClockOutAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { - mClockOutAnim = null; + if (mClockOutAnim == animation) { + out.setVisibility(INVISIBLE); + mClockOutAnim = null; + } } }); @@ -212,7 +242,9 @@ public class KeyguardClockSwitch extends RelativeLayout { mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2); mClockInAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { - mClockInAnim = null; + if (mClockInAnim == animation) { + mClockInAnim = null; + } } }); @@ -225,7 +257,9 @@ public class KeyguardClockSwitch extends RelativeLayout { mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); mStatusAreaAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { - mStatusAreaAnim = null; + if (mStatusAreaAnim == animation) { + mStatusAreaAnim = null; + } } }); mStatusAreaAnim.start(); @@ -269,7 +303,9 @@ public class KeyguardClockSwitch extends RelativeLayout { public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardClockSwitch:"); pw.println(" mSmallClockFrame: " + mSmallClockFrame); + pw.println(" mSmallClockFrame.alpha: " + mSmallClockFrame.getAlpha()); pw.println(" mLargeClockFrame: " + mLargeClockFrame); + pw.println(" mLargeClockFrame.alpha: " + mLargeClockFrame.getAlpha()); pw.println(" mStatusArea: " + mStatusArea); pw.println(" mDisplayedClockSize: " + mDisplayedClockSize); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 788f1200d603..88ce2a74c99d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -38,8 +38,11 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.log.dagger.KeyguardClockLog; import com.android.systemui.plugins.ClockAnimations; import com.android.systemui.plugins.ClockController; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; @@ -62,6 +65,8 @@ import javax.inject.Inject; */ public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch> implements Dumpable { + private static final String TAG = "KeyguardClockSwitchController"; + private final StatusBarStateController mStatusBarStateController; private final ClockRegistry mClockRegistry; private final KeyguardSliceViewController mKeyguardSliceViewController; @@ -70,6 +75,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final SecureSettings mSecureSettings; private final DumpManager mDumpManager; private final ClockEventController mClockEventController; + private final LogBuffer mLogBuffer; private FrameLayout mSmallClockFrame; // top aligned clock private FrameLayout mLargeClockFrame; // centered clock @@ -119,7 +125,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS SecureSettings secureSettings, @Main Executor uiExecutor, DumpManager dumpManager, - ClockEventController clockEventController) { + ClockEventController clockEventController, + @KeyguardClockLog LogBuffer logBuffer) { super(keyguardClockSwitch); mStatusBarStateController = statusBarStateController; mClockRegistry = clockRegistry; @@ -131,6 +138,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mDumpManager = dumpManager; mClockEventController = clockEventController; + mLogBuffer = logBuffer; + mView.setLogBuffer(mLogBuffer); mClockChangedListener = () -> { setClock(mClockRegistry.createCurrentClock()); @@ -337,10 +346,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS int clockHeight = clock.getLargeClock().getView().getHeight(); return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2; } else { - // This is only called if we've never shown the large clock as the frame is inflated - // with 'gone', but then the visibility is never set when it is animated away by - // KeyguardClockSwitch, instead it is removed from the view hierarchy. - // TODO(b/261755021): Cleanup Large Frame Visibility int clockHeight = clock.getSmallClock().getView().getHeight(); return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin; } @@ -358,15 +363,11 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (mLargeClockFrame.getVisibility() == View.VISIBLE) { return clock.getLargeClock().getView().getHeight(); } else { - // Is not called except in certain edge cases, see comment in getClockBottom - // TODO(b/261755021): Cleanup Large Frame Visibility return clock.getSmallClock().getView().getHeight(); } } boolean isClockTopAligned() { - // Returns false except certain edge cases, see comment in getClockBottom - // TODO(b/261755021): Cleanup Large Frame Visibility return mLargeClockFrame.getVisibility() != View.VISIBLE; } @@ -378,6 +379,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void setClock(ClockController clock) { + if (clock != null && mLogBuffer != null) { + mLogBuffer.log(TAG, LogLevel.INFO, "New Clock"); + } + mClockEventController.setClock(clock); mView.setClock(clock, mStatusBarStateController.getState()); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index aec30632c41e..b53b868025e8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -20,6 +20,7 @@ import android.graphics.Rect; import android.util.Slog; import com.android.keyguard.KeyguardClockSwitch.ClockSize; +import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ClockAnimations; @@ -62,14 +63,16 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConfigurationController configurationController, DozeParameters dozeParameters, FeatureFlags featureFlags, - ScreenOffAnimationController screenOffAnimationController) { + ScreenOffAnimationController screenOffAnimationController, + KeyguardLogger logger) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; mKeyguardClockSwitchController = keyguardClockSwitchController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mConfigurationController = configurationController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, - dozeParameters, screenOffAnimationController, /* animateYPos= */ true); + dozeParameters, screenOffAnimationController, /* animateYPos= */ true, + logger.getBuffer()); mKeyguardVisibilityHelper.setOcclusionTransitionFlagEnabled( featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 93027c1914ee..d5669b52cf8c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -141,6 +141,7 @@ import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -170,6 +171,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.Executor; @@ -345,13 +347,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>> mCallbacks = Lists.newArrayList(); private ContentObserver mDeviceProvisionedObserver; - private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver; private final ContentObserver mTimeFormatChangeObserver; private boolean mSwitchingUser; private boolean mDeviceInteractive; - private boolean mSfpsRequireScreenOnToAuthPrefEnabled; private final SubscriptionManager mSubscriptionManager; private final TelephonyListenerManager mTelephonyListenerManager; private final TrustManager mTrustManager; @@ -384,6 +384,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLogoutEnabled; private boolean mIsFaceEnrolled; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; /** * Short delay before restarting fingerprint authentication after a successful try. This should @@ -711,8 +712,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void setKeyguardGoingAway(boolean goingAway) { mKeyguardGoingAway = goingAway; - // This is set specifically to stop face authentication from running. - updateBiometricListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY); + if (mKeyguardGoingAway) { + updateFaceListeningState(BIOMETRIC_ACTION_STOP, + FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY); + } + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); + } + + /** + * Whether keyguard is going away due to screen off or device entry. + */ + public boolean isKeyguardGoingAway() { + return mKeyguardGoingAway; } /** @@ -1943,9 +1954,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onFinishedGoingToSleep(arg1); } } - // This is set specifically to stop face authentication from running. - updateBiometricListeningState(BIOMETRIC_ACTION_STOP, + updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } private void handleScreenTurnedOff() { @@ -2048,7 +2059,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Nullable FaceManager faceManager, @Nullable FingerprintManager fingerprintManager, @Nullable BiometricManager biometricManager, - FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) { + FaceWakeUpTriggersConfig faceWakeUpTriggersConfig, + Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2311,30 +2323,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Settings.System.getUriFor(Settings.System.TIME_12_24), false, mTimeFormatChangeObserver, UserHandle.USER_ALL); - updateSfpsRequireScreenOnToAuthPref(); - mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - updateSfpsRequireScreenOnToAuthPref(); - } - }; - - mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor( - Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED), - false, - mSfpsRequireScreenOnToAuthPrefObserver, - getCurrentUser()); - } - - protected void updateSfpsRequireScreenOnToAuthPref() { - final int defaultSfpsRequireScreenOnToAuthValue = - mContext.getResources().getBoolean( - com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0; - mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser( - Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, - defaultSfpsRequireScreenOnToAuthValue, - getCurrentUser()) != 0; + mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null); } private void initializeSimState() { @@ -2729,8 +2718,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean shouldListenSideFpsState = true; if (isSideFps) { + final boolean interactiveToAuthEnabled = + mFingerprintInteractiveToAuthProvider != null && + mFingerprintInteractiveToAuthProvider.isEnabled(getCurrentUser()); shouldListenSideFpsState = - mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true; + interactiveToAuthEnabled ? isDeviceInteractive() && !mGoingToSleep : true; } boolean shouldListen = shouldListenKeyguardState && shouldListenUserState @@ -3845,11 +3837,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver); } - if (mSfpsRequireScreenOnToAuthPrefObserver != null) { - mContext.getContentResolver().unregisterContentObserver( - mSfpsRequireScreenOnToAuthPrefObserver); - } - try { ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); } catch (RemoteException e) { @@ -3926,8 +3913,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else if (isSfpsSupported()) { pw.println(" sfpsEnrolled=" + isSfpsEnrolled()); pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false)); - pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled=" - + mSfpsRequireScreenOnToAuthPrefEnabled); + if (isSfpsEnrolled()) { + final boolean interactiveToAuthEnabled = + mFingerprintInteractiveToAuthProvider != null && + mFingerprintInteractiveToAuthProvider + .isEnabled(getCurrentUser()); + pw.println(" interactiveToAuthEnabled=" + + interactiveToAuthEnabled); + } } new DumpsysTableLogger( "KeyguardFingerprintListen", diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index bde06929a3d1..7e48193bfc62 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -22,6 +22,8 @@ import android.view.View; import android.view.ViewPropertyAnimator; import com.android.systemui.animation.Interpolators; +import com.android.systemui.plugins.log.LogBuffer; +import com.android.systemui.plugins.log.LogLevel; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -31,11 +33,14 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.google.errorprone.annotations.CompileTimeConstant; + /** * Helper class for updating visibility of keyguard views based on keyguard and status bar state. * This logic is shared by both the keyguard status view and the keyguard user switcher. */ public class KeyguardVisibilityHelper { + private static final String TAG = "KeyguardVisibilityHelper"; private View mView; private final KeyguardStateController mKeyguardStateController; @@ -46,17 +51,26 @@ public class KeyguardVisibilityHelper { private boolean mLastOccludedState = false; private boolean mIsUnoccludeTransitionFlagEnabled = false; private final AnimationProperties mAnimationProperties = new AnimationProperties(); + private final LogBuffer mLogBuffer; public KeyguardVisibilityHelper(View view, KeyguardStateController keyguardStateController, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, - boolean animateYPos) { + boolean animateYPos, + LogBuffer logBuffer) { mView = view; mKeyguardStateController = keyguardStateController; mDozeParameters = dozeParameters; mScreenOffAnimationController = screenOffAnimationController; mAnimateYPos = animateYPos; + mLogBuffer = logBuffer; + } + + private void log(@CompileTimeConstant String message) { + if (mLogBuffer != null) { + mLogBuffer.log(TAG, LogLevel.DEBUG, message); + } } public boolean isVisibilityAnimating() { @@ -94,6 +108,9 @@ public class KeyguardVisibilityHelper { .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay()) .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration()) .start(); + log("goingToFullShade && keyguardFadingAway"); + } else { + log("goingToFullShade && !keyguardFadingAway"); } } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) { mView.setVisibility(View.VISIBLE); @@ -105,6 +122,7 @@ public class KeyguardVisibilityHelper { .setDuration(320) .setInterpolator(Interpolators.ALPHA_IN) .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); + log("keyguardFadingAway transition w/ Y Aniamtion"); } else if (statusBarState == KEYGUARD) { if (keyguardFadingAway) { mKeyguardViewVisibilityAnimating = true; @@ -125,9 +143,13 @@ public class KeyguardVisibilityHelper { true /* animate */); animator.setDuration(duration) .setStartDelay(delay); + log("keyguardFadingAway transition w/ Y Aniamtion"); + } else { + log("keyguardFadingAway transition w/o Y Animation"); } animator.start(); } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { + log("ScreenOff transition"); mKeyguardViewVisibilityAnimating = true; // Ask the screen off animation controller to animate the keyguard visibility for us @@ -136,6 +158,7 @@ public class KeyguardVisibilityHelper { mView, mAnimateKeyguardStatusViewVisibleEndRunnable); } else if (!mIsUnoccludeTransitionFlagEnabled && mLastOccludedState && !isOccluded) { // An activity was displayed over the lock screen, and has now gone away + log("Unoccluded transition"); mView.setVisibility(View.VISIBLE); mView.setAlpha(0f); @@ -146,12 +169,14 @@ public class KeyguardVisibilityHelper { .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) .start(); } else { + log("Direct set Visibility to VISIBLE"); mView.setVisibility(View.VISIBLE); if (!mIsUnoccludeTransitionFlagEnabled) { mView.setAlpha(1f); } } } else { + log("Direct set Visibility to GONE"); mView.setVisibility(View.GONE); mView.setAlpha(1f); } @@ -162,14 +187,18 @@ public class KeyguardVisibilityHelper { private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> { mKeyguardViewVisibilityAnimating = false; mView.setVisibility(View.INVISIBLE); + log("Callback Set Visibility to INVISIBLE"); }; private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> { mKeyguardViewVisibilityAnimating = false; mView.setVisibility(View.GONE); + log("CallbackSet Visibility to GONE"); }; private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> { mKeyguardViewVisibilityAnimating = false; + mView.setVisibility(View.VISIBLE); + log("Callback Set Visibility to VISIBLE"); }; } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index b84fb08d53a8..2c7eceba48a2 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -17,36 +17,46 @@ package com.android.keyguard.logging import com.android.systemui.log.dagger.KeyguardLog -import com.android.systemui.plugins.log.ConstantStringsLogger -import com.android.systemui.plugins.log.ConstantStringsLoggerImpl import com.android.systemui.plugins.log.LogBuffer -import com.android.systemui.plugins.log.LogLevel.DEBUG -import com.android.systemui.plugins.log.LogLevel.ERROR -import com.android.systemui.plugins.log.LogLevel.INFO -import com.android.systemui.plugins.log.LogLevel.VERBOSE +import com.android.systemui.plugins.log.LogLevel import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject -private const val TAG = "KeyguardLog" +private const val BIO_TAG = "KeyguardLog" /** * Generic logger for keyguard that's wrapping [LogBuffer]. This class should be used for adding * temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be * an overkill. */ -class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) : - ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { - - fun logException(ex: Exception, @CompileTimeConstant logMsg: String) { - buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex) - } - - fun v(msg: String, arg: Any) { - buffer.log(TAG, VERBOSE, { str1 = arg.toString() }, { "$msg: $str1" }) - } +class KeyguardLogger +@Inject +constructor( + @KeyguardLog val buffer: LogBuffer, +) { + @JvmOverloads + fun log( + tag: String, + level: LogLevel, + @CompileTimeConstant msg: String, + ex: Throwable? = null, + ) = buffer.log(tag, level, msg, ex) - fun i(msg: String, arg: Any) { - buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" }) + fun log( + tag: String, + level: LogLevel, + @CompileTimeConstant msg: String, + arg: Any, + ) { + buffer.log( + tag, + level, + { + str1 = msg + str2 = arg.toString() + }, + { "$str1: $str2" } + ) } @JvmOverloads @@ -56,8 +66,8 @@ class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuf msg: String? = null ) { buffer.log( - TAG, - DEBUG, + BIO_TAG, + LogLevel.DEBUG, { str1 = context str2 = "$msgId" diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 092339a74e3f..2dc0cd34adf4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -18,6 +18,7 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP; @@ -76,6 +77,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.data.repository.BiometricType; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.VibratorHelper; @@ -85,8 +87,10 @@ import com.android.systemui.util.concurrency.Execution; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; @@ -150,6 +154,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps; @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; + @NonNull private final Map<Integer, Boolean> mFpEnrolledForUser = new HashMap<>(); @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; @NonNull private final SparseBooleanArray mSfpsEnrolledForUser; @NonNull private final SensorPrivacyManager mSensorPrivacyManager; @@ -161,7 +166,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, private final @Background DelayableExecutor mBackgroundExecutor; private final DisplayInfo mCachedDisplayInfo = new DisplayInfo(); - private final VibratorHelper mVibratorHelper; private void vibrateSuccess(int modality) { @@ -331,27 +335,35 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mExecution.assertIsMainThread(); Log.d(TAG, "handleEnrollmentsChanged, userId: " + userId + ", sensorId: " + sensorId + ", hasEnrollments: " + hasEnrollments); - if (mUdfpsProps == null) { - Log.d(TAG, "handleEnrollmentsChanged, mUdfpsProps is null"); - } else { - for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) { + BiometricType sensorBiometricType = BiometricType.UNKNOWN; + if (mFpProps != null) { + for (FingerprintSensorPropertiesInternal prop: mFpProps) { if (prop.sensorId == sensorId) { - mUdfpsEnrolledForUser.put(userId, hasEnrollments); + mFpEnrolledForUser.put(userId, hasEnrollments); + if (prop.isAnyUdfpsType()) { + sensorBiometricType = BiometricType.UNDER_DISPLAY_FINGERPRINT; + mUdfpsEnrolledForUser.put(userId, hasEnrollments); + } else if (prop.isAnySidefpsType()) { + sensorBiometricType = BiometricType.SIDE_FINGERPRINT; + mSfpsEnrolledForUser.put(userId, hasEnrollments); + } else if (prop.sensorType == TYPE_REAR) { + sensorBiometricType = BiometricType.REAR_FINGERPRINT; + } + break; } } } - - if (mSidefpsProps == null) { - Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null"); - } else { - for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) { + if (mFaceProps != null && sensorBiometricType == BiometricType.UNKNOWN) { + for (FaceSensorPropertiesInternal prop : mFaceProps) { if (prop.sensorId == sensorId) { - mSfpsEnrolledForUser.put(userId, hasEnrollments); + sensorBiometricType = BiometricType.FACE; + break; } } } for (Callback cb : mCallbacks) { cb.onEnrollmentsChanged(); + cb.onEnrollmentsChanged(sensorBiometricType, userId, hasEnrollments); } } @@ -604,6 +616,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } } + /** Get FP sensor properties */ + public @Nullable List<FingerprintSensorPropertiesInternal> getFingerprintProperties() { + return mFpProps; + } + /** * @return where the face sensor exists in pixels in the current device orientation. Returns * null if no face sensor exists. @@ -828,7 +845,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } @Override - public void setBiometicContextListener(IBiometricContextListener listener) { + public void setBiometricContextListener(IBiometricContextListener listener) { mBiometricContextListener = listener; notifyDozeChanged(mStatusBarStateController.isDozing(), mWakefulnessLifecycle.getWakefulness()); @@ -1081,6 +1098,13 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, return mSfpsEnrolledForUser.get(userId); } + /** + * Whether the passed userId has enrolled at least one fingerprint. + */ + public boolean isFingerprintEnrolled(int userId) { + return mFpEnrolledForUser.getOrDefault(userId, false); + } + private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) { mCurrentDialogArgs = args; @@ -1263,6 +1287,16 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, default void onEnrollmentsChanged() {} /** + * Called when UDFPS enrollments have changed. This is called after boot and on changes to + * enrollment. + */ + default void onEnrollmentsChanged( + @NonNull BiometricType biometricType, + int userId, + boolean hasEnrollments + ) {} + + /** * Called when the biometric prompt starts showing. */ default void onBiometricPromptShown() {} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java new file mode 100644 index 000000000000..902bb18d17b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** Provides the status of the interactive to auth feature. */ +public interface FingerprintInteractiveToAuthProvider { + /** + * + * @param userId the user Id. + * @return true if the InteractiveToAuthFeature is enabled, false if disabled. + */ + boolean isEnabled(int userId); +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt index 4130cf589310..ef7dcb7aac93 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt @@ -190,11 +190,6 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>( open fun listenForTouchesOutsideView(): Boolean = false /** - * Called on touches outside of the view if listenForTouchesOutsideView returns true - */ - open fun onTouchOutsideView() {} - - /** * Called when a view should announce an accessibility event. */ open fun doAnnounceForAccessibility(str: String) {} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index f3136babada6..cea1779e39b8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -73,6 +73,7 @@ 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.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -149,6 +150,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator; @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Nullable private final TouchProcessor mTouchProcessor; + @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @@ -232,12 +234,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { mShadeExpansionStateManager, mKeyguardViewManager, mKeyguardUpdateMonitor, mDialogManager, mDumpManager, mLockscreenShadeTransitionController, mConfigurationController, - mSystemClock, mKeyguardStateController, + mKeyguardStateController, mUnlockedScreenOffAnimationController, mUdfpsDisplayMode, requestId, reason, callback, (view, event, fromUdfpsView) -> onTouch(requestId, event, fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags, - mPrimaryBouncerInteractor))); + mPrimaryBouncerInteractor, mAlternateBouncerInteractor))); } @Override @@ -329,13 +331,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!mOverlayParams.equals(overlayParams)) { mOverlayParams = overlayParams; - final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer(); + final boolean wasShowingAlternateBouncer = mAlternateBouncerInteractor.isVisibleState(); // When the bounds change it's always necessary to re-create the overlay's window with // new LayoutParams. If the overlay needs to be shown, this will re-create and show the // overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden. redrawOverlay(); - if (wasShowingAltAuth) { + if (wasShowingAlternateBouncer) { mKeyguardViewManager.showBouncer(true); } } @@ -543,9 +545,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { final UdfpsView udfpsView = mOverlay.getOverlayView(); boolean handled = false; switch (event.getActionMasked()) { - case MotionEvent.ACTION_OUTSIDE: - udfpsView.onTouchOutsideView(); - return true; case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_HOVER_ENTER: Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN"); @@ -719,7 +718,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider, @NonNull @BiometricsBackground Executor biometricsExecutor, @NonNull PrimaryBouncerInteractor primaryBouncerInteractor, - @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) { + @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor, + @NonNull AlternateBouncerInteractor alternateBouncerInteractor) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -759,6 +759,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mBiometricExecutor = biometricsExecutor; mPrimaryBouncerInteractor = primaryBouncerInteractor; + mAlternateBouncerInteractor = alternateBouncerInteractor; mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) ? singlePointerTouchProcessor : null; @@ -853,9 +854,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { onFingerUp(mOverlay.getRequestId(), oldView); } final boolean removed = mOverlay.hide(); - if (mKeyguardViewManager.isShowingAlternateBouncer()) { - mKeyguardViewManager.hideAlternateBouncer(true); - } + mKeyguardViewManager.hideAlternateBouncer(true); Log.v(TAG, "hideUdfpsOverlay | removing window: " + removed); } else { Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden"); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 8db4927ee059..a3c4985fd5cc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -50,6 +50,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator 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.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager @@ -59,7 +60,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager 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.time.SystemClock private const val TAG = "UdfpsControllerOverlay" @@ -86,7 +86,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val dumpManager: DumpManager, private val transitionController: LockscreenShadeTransitionController, private val configurationController: ConfigurationController, - private val systemClock: SystemClock, private val keyguardStateController: KeyguardStateController, private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, @@ -97,7 +96,8 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val activityLaunchAnimator: ActivityLaunchAnimator, private val featureFlags: FeatureFlags, private val primaryBouncerInteractor: PrimaryBouncerInteractor, - private val isDebuggable: Boolean = Build.IS_DEBUGGABLE + private val alternateBouncerInteractor: AlternateBouncerInteractor, + private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, ) { /** The view, when [isShowing], or null. */ var overlayView: UdfpsView? = null @@ -255,14 +255,14 @@ class UdfpsControllerOverlay @JvmOverloads constructor( dumpManager, transitionController, configurationController, - systemClock, keyguardStateController, unlockedScreenOffAnimationController, dialogManager, controller, activityLaunchAnimator, featureFlags, - primaryBouncerInteractor + primaryBouncerInteractor, + alternateBouncerInteractor, ) } REASON_AUTH_BP -> { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt index 63144fcea761..583ee3ac8e60 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt @@ -31,6 +31,7 @@ import com.android.systemui.animation.Interpolators 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.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -42,13 +43,13 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.KeyguardBouncer import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateBouncer import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI import com.android.systemui.statusbar.phone.SystemUIDialogManager 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.time.SystemClock import java.io.PrintWriter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -65,25 +66,27 @@ constructor( dumpManager: DumpManager, private val lockScreenShadeTransitionController: LockscreenShadeTransitionController, private val configurationController: ConfigurationController, - private val systemClock: SystemClock, private val keyguardStateController: KeyguardStateController, private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, systemUIDialogManager: SystemUIDialogManager, private val udfpsController: UdfpsController, private val activityLaunchAnimator: ActivityLaunchAnimator, featureFlags: FeatureFlags, - private val primaryBouncerInteractor: PrimaryBouncerInteractor + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val alternateBouncerInteractor: AlternateBouncerInteractor, ) : UdfpsAnimationViewController<UdfpsKeyguardView>( view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager, - dumpManager + dumpManager, ) { private val useExpandedOverlay: Boolean = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER) + private val isModernAlternateBouncerEnabled: Boolean = + featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER) private var showingUdfpsBouncer = false private var udfpsRequested = false private var qsExpansion = 0f @@ -91,7 +94,6 @@ constructor( private var statusBarState = 0 private var transitionToFullShadeProgress = 0f private var lastDozeAmount = 0f - private var lastUdfpsBouncerShowTime: Long = -1 private var panelExpansionFraction = 0f private var launchTransitionFadingAway = false private var isLaunchingActivity = false @@ -244,20 +246,8 @@ constructor( } } - private val mAlternateBouncer: AlternateBouncer = - object : AlternateBouncer { - override fun showAlternateBouncer(): Boolean { - return showUdfpsBouncer(true) - } - - override fun hideAlternateBouncer(): Boolean { - return showUdfpsBouncer(false) - } - - override fun isShowingAlternateBouncer(): Boolean { - return showingUdfpsBouncer - } - + private val occludingAppBiometricUI: OccludingAppBiometricUI = + object : OccludingAppBiometricUI { override fun requestUdfps(request: Boolean, color: Int) { udfpsRequested = request view.requestUdfps(request, color) @@ -275,16 +265,19 @@ constructor( override fun onInit() { super.onInit() - keyguardViewManager.setAlternateBouncer(mAlternateBouncer) + keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) } init { - if (isModernBouncerEnabled) { + if (isModernBouncerEnabled || isModernAlternateBouncerEnabled) { view.repeatWhenAttached { // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion // can make the view not visible; and we still want to listen for events // that may make the view visible again. - repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) } + repeatOnLifecycle(Lifecycle.State.CREATED) { + if (isModernBouncerEnabled) listenForBouncerExpansion(this) + if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this) + } } } } @@ -300,8 +293,18 @@ constructor( } } + @VisibleForTesting + internal suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job { + return scope.launch { + alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> + showUdfpsBouncer(isVisible) + } + } + } + public override fun onViewAttached() { super.onViewAttached() + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) val dozeAmount = statusBarStateController.dozeAmount lastDozeAmount = dozeAmount stateListener.onDozeAmountChanged(dozeAmount, dozeAmount) @@ -326,7 +329,8 @@ constructor( view.updatePadding() updateAlpha() updatePauseAuth() - keyguardViewManager.setAlternateBouncer(mAlternateBouncer) + keyguardViewManager.setLegacyAlternateBouncer(legacyAlternateBouncer) + keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) lockScreenShadeTransitionController.udfpsKeyguardViewController = this activityLaunchAnimator.addListener(activityLaunchAnimatorListener) view.mUseExpandedOverlay = useExpandedOverlay @@ -334,10 +338,12 @@ constructor( override fun onViewDetached() { super.onViewDetached() + alternateBouncerInteractor.setAlternateBouncerUIAvailable(false) faceDetectRunning = false keyguardStateController.removeCallback(keyguardStateControllerCallback) statusBarStateController.removeCallback(stateListener) - keyguardViewManager.removeAlternateAuthInterceptor(mAlternateBouncer) + keyguardViewManager.removeLegacyAlternateBouncer(legacyAlternateBouncer) + keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI) keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false) configurationController.removeCallback(configurationListener) shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener) @@ -356,7 +362,16 @@ constructor( override fun dump(pw: PrintWriter, args: Array<String>) { super.dump(pw, args) pw.println("isModernBouncerEnabled=$isModernBouncerEnabled") + pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled") pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer") + pw.println( + "altBouncerInteractor#isAlternateBouncerVisible=" + + "${alternateBouncerInteractor.isVisibleState()}" + ) + pw.println( + "altBouncerInteractor#canShowAlternateBouncerForFingerprint=" + + "${alternateBouncerInteractor.canShowAlternateBouncerForFingerprint()}" + ) pw.println("faceDetectRunning=$faceDetectRunning") pw.println("statusBarState=" + StatusBarState.toString(statusBarState)) pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress") @@ -385,9 +400,6 @@ constructor( val udfpsAffordanceWasNotShowing = shouldPauseAuth() showingUdfpsBouncer = show if (showingUdfpsBouncer) { - lastUdfpsBouncerShowTime = systemClock.uptimeMillis() - } - if (showingUdfpsBouncer) { if (udfpsAffordanceWasNotShowing) { view.animateInUdfpsBouncer(null) } @@ -452,7 +464,7 @@ constructor( return if (isModernBouncerEnabled) { inputBouncerExpansion == 1f } else { - keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateBouncer + keyguardViewManager.isBouncerShowing && !alternateBouncerInteractor.isVisibleState() } } @@ -460,30 +472,6 @@ constructor( return true } - override fun onTouchOutsideView() { - maybeShowInputBouncer() - } - - /** - * If we were previously showing the udfps bouncer, hide it and instead show the regular - * (pin/pattern/password) bouncer. - * - * Does nothing if we weren't previously showing the UDFPS bouncer. - */ - private fun maybeShowInputBouncer() { - if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) { - keyguardViewManager.showPrimaryBouncer(true) - } - } - - /** - * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the - * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer. - */ - private fun hasUdfpsBouncerShownWithMinTime(): Boolean { - return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200 - } - /** * Set the progress we're currently transitioning to the full shade. 0.0f means we're not * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down @@ -545,7 +533,7 @@ constructor( if (isModernBouncerEnabled) { return } - val altBouncerShowing = keyguardViewManager.isShowingAlternateBouncer + val altBouncerShowing = alternateBouncerInteractor.isVisibleState() if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) { inputBouncerHiddenAmount = 1f } else if (keyguardViewManager.isBouncerShowing) { @@ -554,6 +542,21 @@ constructor( } } + private val legacyAlternateBouncer: LegacyAlternateBouncer = + object : LegacyAlternateBouncer { + override fun showAlternateBouncer(): Boolean { + return showUdfpsBouncer(true) + } + + override fun hideAlternateBouncer(): Boolean { + return showUdfpsBouncer(false) + } + + override fun isShowingAlternateBouncer(): Boolean { + return showingUdfpsBouncer + } + } + companion object { const val TAG = "UdfpsKeyguardViewController" } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt index 4a8877edfa53..e61c614f0292 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt @@ -111,10 +111,6 @@ class UdfpsView( } } - fun onTouchOutsideView() { - animationViewController?.onTouchOutsideView() - } - override fun onAttachedToWindow() { super.onAttachedToWindow() Log.v(TAG, "onAttachedToWindow") diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt index 857224290752..682d38a8f1a8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt @@ -18,6 +18,7 @@ package com.android.systemui.biometrics.udfps import android.graphics.Point import android.graphics.Rect +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import kotlin.math.cos import kotlin.math.pow @@ -50,7 +51,8 @@ class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetecto return result <= 1 } - private fun calculateSensorPoints(sensorBounds: Rect): List<Point> { + @VisibleForTesting + fun calculateSensorPoints(sensorBounds: Rect): List<Point> { val sensorX = sensorBounds.centerX() val sensorY = sensorBounds.centerY() val cornerOffset: Int = sensorBounds.width() / 4 diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt index 338bf66d197e..693f64a1f93d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt @@ -27,6 +27,8 @@ import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject +private val SUPPORTED_ROTATIONS = setOf(Surface.ROTATION_90, Surface.ROTATION_270) + /** * TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations. */ @@ -129,19 +131,27 @@ private fun MotionEvent.normalize( val nativeY = naturalTouch.y / overlayParams.scaleFactor val nativeMinor: Float = getTouchMinor(pointerIndex) / overlayParams.scaleFactor val nativeMajor: Float = getTouchMajor(pointerIndex) / overlayParams.scaleFactor + var nativeOrientation: Float = getOrientation(pointerIndex) + if (SUPPORTED_ROTATIONS.contains(overlayParams.rotation)) { + nativeOrientation = toRadVerticalFromRotated(nativeOrientation.toDouble()).toFloat() + } return NormalizedTouchData( pointerId = getPointerId(pointerIndex), x = nativeX, y = nativeY, minor = nativeMinor, major = nativeMajor, - // TODO(b/259311354): touch orientation should be reported relative to Surface.ROTATION_O. - orientation = getOrientation(pointerIndex), + orientation = nativeOrientation, time = eventTime, gestureStart = downTime, ) } +private fun toRadVerticalFromRotated(rad: Double): Double { + val piBound = ((rad % Math.PI) + Math.PI / 2) % Math.PI + return if (piBound < Math.PI / 2.0) piBound else piBound - Math.PI +} + /** * Returns the [MotionEvent.getRawX] and [MotionEvent.getRawY] of the given pointer as if the device * is in the [Surface.ROTATION_0] orientation. @@ -152,7 +162,7 @@ private fun MotionEvent.rotateToNaturalOrientation( ): PointF { val touchPoint = PointF(getRawX(pointerIndex), getRawY(pointerIndex)) val rot = overlayParams.rotation - if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { + if (SUPPORTED_ROTATIONS.contains(rot)) { RotationUtils.rotatePointF( touchPoint, RotationUtils.deltaRotation(rot, Surface.ROTATION_0), diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index e8e1f2e95f5d..e9ac840cf4f4 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -176,7 +176,8 @@ public class BrightLineFalsingManager implements FalsingManager { private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC; @Inject - public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider, + public BrightLineFalsingManager( + FalsingDataProvider falsingDataProvider, MetricsLogger metricsLogger, @Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers, SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier, @@ -399,7 +400,9 @@ public class BrightLineFalsingManager implements FalsingManager { || mDataProvider.isJustUnlockedWithFace() || mDataProvider.isDocked() || mAccessibilityManager.isTouchExplorationEnabled() - || mDataProvider.isA11yAction(); + || mDataProvider.isA11yAction() + || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED) + && !mDataProvider.isFolded()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 09ebeeac163f..5f347c158818 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -16,6 +16,7 @@ package com.android.systemui.classifier; +import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; @@ -42,6 +43,7 @@ public class FalsingDataProvider { private final int mWidthPixels; private final int mHeightPixels; private BatteryController mBatteryController; + private final FoldStateListener mFoldStateListener; private final DockManager mDockManager; private final float mXdpi; private final float mYdpi; @@ -65,12 +67,14 @@ public class FalsingDataProvider { public FalsingDataProvider( DisplayMetrics displayMetrics, BatteryController batteryController, + FoldStateListener foldStateListener, DockManager dockManager) { mXdpi = displayMetrics.xdpi; mYdpi = displayMetrics.ydpi; mWidthPixels = displayMetrics.widthPixels; mHeightPixels = displayMetrics.heightPixels; mBatteryController = batteryController; + mFoldStateListener = foldStateListener; mDockManager = dockManager; FalsingClassifier.logInfo("xdpi, ydpi: " + getXdpi() + ", " + getYdpi()); @@ -376,6 +380,10 @@ public class FalsingDataProvider { return mBatteryController.isWirelessCharging() || mDockManager.isDocked(); } + public boolean isFolded() { + return Boolean.TRUE.equals(mFoldStateListener.getFolded()); + } + /** Implement to be alerted abotu the beginning and ending of falsing tracking. */ public interface SessionListener { /** Called when the lock screen is shown and falsing-tracking begins. */ diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index b8e66735c740..6d13740afe8b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -31,6 +31,7 @@ import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; +import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; import com.android.systemui.biometrics.dagger.UdfpsModule; @@ -221,6 +222,9 @@ public abstract class SystemUIModule { @BindsOptionalOf abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider(); + @BindsOptionalOf + abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider(); + @SysUISingleton @Binds abstract SystemClock bindSystemClock(SystemClockImpl systemClock); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index f244cb009ba4..96bce4cd3cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -19,6 +19,7 @@ package com.android.systemui.dreams; import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -26,6 +27,9 @@ import android.view.ViewGroup; import androidx.constraintlayout.widget.ConstraintLayout; import com.android.systemui.R; +import com.android.systemui.shared.shadow.DoubleShadowIconDrawable; +import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo; +import com.android.systemui.statusbar.AlphaOptimizedImageView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -60,8 +64,15 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { public static final int STATUS_ICON_PRIORITY_MODE_ON = 6; private final Map<Integer, View> mStatusIcons = new HashMap<>(); + private Context mContext; private ViewGroup mSystemStatusViewGroup; private ViewGroup mExtraSystemStatusViewGroup; + private ShadowInfo mKeyShadowInfo; + private ShadowInfo mAmbientShadowInfo; + private int mDrawableSize; + private int mDrawableInsetSize; + private static final float KEY_SHADOW_ALPHA = 0.35f; + private static final float AMBIENT_SHADOW_ALPHA = 0.4f; public DreamOverlayStatusBarView(Context context) { this(context, null); @@ -73,6 +84,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { public DreamOverlayStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); + mContext = context; } public DreamOverlayStatusBarView( @@ -80,14 +92,36 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { super(context, attrs, defStyleAttr, defStyleRes); } + @Override protected void onFinishInflate() { super.onFinishInflate(); + mKeyShadowInfo = createShadowInfo( + R.dimen.dream_overlay_status_bar_key_text_shadow_radius, + R.dimen.dream_overlay_status_bar_key_text_shadow_dx, + R.dimen.dream_overlay_status_bar_key_text_shadow_dy, + KEY_SHADOW_ALPHA + ); + + mAmbientShadowInfo = createShadowInfo( + R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius, + R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx, + R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy, + AMBIENT_SHADOW_ALPHA + ); + + mDrawableSize = mContext + .getResources() + .getDimensionPixelSize(R.dimen.dream_overlay_status_bar_icon_size); + mDrawableInsetSize = mContext + .getResources() + .getDimensionPixelSize(R.dimen.dream_overlay_icon_inset_dimen); + mStatusIcons.put(STATUS_ICON_WIFI_UNAVAILABLE, - fetchStatusIconForResId(R.id.dream_overlay_wifi_status)); + addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_wifi_status))); mStatusIcons.put(STATUS_ICON_ALARM_SET, - fetchStatusIconForResId(R.id.dream_overlay_alarm_set)); + addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_alarm_set))); mStatusIcons.put(STATUS_ICON_CAMERA_DISABLED, fetchStatusIconForResId(R.id.dream_overlay_camera_off)); mStatusIcons.put(STATUS_ICON_MIC_DISABLED, @@ -97,7 +131,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { mStatusIcons.put(STATUS_ICON_NOTIFICATIONS, fetchStatusIconForResId(R.id.dream_overlay_notification_indicator)); mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON, - fetchStatusIconForResId(R.id.dream_overlay_priority_mode)); + addDoubleShadow(fetchStatusIconForResId(R.id.dream_overlay_priority_mode))); mSystemStatusViewGroup = findViewById(R.id.dream_overlay_system_status); mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items); @@ -137,4 +171,34 @@ public class DreamOverlayStatusBarView extends ConstraintLayout { } return false; } + + private View addDoubleShadow(View icon) { + if (icon instanceof AlphaOptimizedImageView) { + AlphaOptimizedImageView i = (AlphaOptimizedImageView) icon; + Drawable drawableIcon = i.getDrawable(); + i.setImageDrawable(new DoubleShadowIconDrawable( + mKeyShadowInfo, + mAmbientShadowInfo, + drawableIcon, + mDrawableSize, + mDrawableInsetSize + )); + } + return icon; + } + + private ShadowInfo createShadowInfo(int blurId, int offsetXId, int offsetYId, float alpha) { + return new ShadowInfo( + fetchDimensionForResId(blurId), + fetchDimensionForResId(offsetXId), + fetchDimensionForResId(offsetYId), + alpha + ); + } + + private Float fetchDimensionForResId(int resId) { + return mContext + .getResources() + .getDimension(resId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 20ae64cf5985..c9a2a0b9b7e9 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -114,8 +114,6 @@ object Flags { // ** Flag retired ** // public static final BooleanFlag KEYGUARD_LAYOUT = // new BooleanFlag(200, true); - // TODO(b/254512713): Tracking Bug - @JvmField val LOCKSCREEN_ANIMATIONS = releasedFlag(201, "lockscreen_animations") // TODO(b/254512750): Tracking Bug val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag(202, "new_unlock_swipe_animation") @@ -165,7 +163,7 @@ object Flags { // TODO(b/255618149): Tracking Bug @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = - unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false) + unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true) /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */ // TODO(b/256513609): Tracking Bug @@ -180,6 +178,13 @@ object Flags { @JvmField val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false) + /** + * Whether to use the new alternate bouncer architecture, a refactor of and eventual replacement + * of the Alternate/Authentication Bouncer. No visual UI changes. + */ + // TODO(b/260619425): Tracking Bug + @JvmField val MODERN_ALTERNATE_BOUNCER = unreleasedFlag(219, "modern_alternate_bouncer") + /** Flag to control the migration of face auth to modern architecture. */ // TODO(b/262838215): Tracking bug @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor") @@ -202,6 +207,9 @@ object Flags { val AUTO_PIN_CONFIRMATION = unreleasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation") + // TODO(b/262859270): Tracking Bug + @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -255,10 +263,11 @@ object Flags { // TODO(b/256614751): Tracking Bug val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = - unreleasedFlag(608, "new_status_bar_mobile_icons_backend") + unreleasedFlag(608, "new_status_bar_mobile_icons_backend", teamfood = true) // TODO(b/256613548): Tracking Bug - val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend") + val NEW_STATUS_BAR_WIFI_ICON_BACKEND = + unreleasedFlag(609, "new_status_bar_wifi_icon_backend", teamfood = true) // TODO(b/256623670): Tracking Bug @JvmField @@ -297,7 +306,7 @@ object Flags { // 900 - media // TODO(b/254512697): Tracking Bug - val MEDIA_TAP_TO_TRANSFER = unreleasedFlag(900, "media_tap_to_transfer", teamfood = true) + val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer") // TODO(b/254512502): Tracking Bug val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions") @@ -333,7 +342,8 @@ object Flags { // TODO(b/254512758): Tracking Bug @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple") - val SHOW_LOWLIGHT_ON_DIRECT_BOOT = unreleasedFlag(1003, "show_lowlight_on_direct_boot") + // TODO(b/265045965): Tracking Bug + val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot") // 1100 - windowing @Keep @@ -431,9 +441,6 @@ object Flags { unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true) // 1300 - screenshots - // TODO(b/254512719): Tracking Bug - @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor") - // TODO(b/254513155): Tracking Bug @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index bb2141d3fa16..8200f259a992 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -34,6 +34,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.LOCKSCREEN_ANIMATION_DURATION_MS; +import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -122,6 +123,8 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -507,6 +510,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private CentralSurfaces mCentralSurfaces; + private boolean mUnocclusionTransitionFlagEnabled = false; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -958,8 +963,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { - setOccluded(true /* isOccluded */, true /* animate */); - + if (!mUnocclusionTransitionFlagEnabled) { + setOccluded(true /* isOccluded */, true /* animate */); + } if (apps == null || apps.length == 0 || apps[0] == null) { if (DEBUG) { Log.d(TAG, "No apps provided to the OccludeByDream runner; " @@ -1001,9 +1007,20 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, applier.scheduleApply(paramsBuilder.build()); }); mOccludeByDreamAnimator.addListener(new AnimatorListenerAdapter() { + private boolean mIsCancelled = false; + @Override + public void onAnimationCancel(Animator animation) { + mIsCancelled = true; + } + @Override public void onAnimationEnd(Animator animation) { try { + if (!mIsCancelled && mUnocclusionTransitionFlagEnabled) { + // We're already on the main thread, don't queue this call + handleSetOccluded(true /* isOccluded */, + false /* animate */); + } finishedCallback.onAnimationFinished(); mOccludeByDreamAnimator = null; } catch (RemoteException e) { @@ -1176,6 +1193,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, + FeatureFlags featureFlags, Lazy<ShadeController> shadeControllerLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, @@ -1230,9 +1248,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, R.dimen.physical_power_button_center_screen_location_y); mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); - mDreamOpenAnimationDuration = context.getResources().getInteger( - com.android.internal.R.integer.config_dreamOpenAnimationDuration); + mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS; mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS; + mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); } public void userActivity() { @@ -1458,16 +1476,16 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void maybeHandlePendingLock() { if (mPendingLock) { - // The screen off animation is playing, so if we lock now, the foreground app will - // vanish and the keyguard will jump-cut in. Delay it, until either: + // The screen off animation is playing or is about to be, so if we lock now, the + // foreground app will vanish and the keyguard will jump-cut in. Delay it, until either: // - The screen off animation ends. We will call maybeHandlePendingLock from // the end action in UnlockedScreenOffAnimationController#animateInKeyguard. // - The screen off animation is cancelled by the device waking back up. We will call // maybeHandlePendingLock from KeyguardViewMediator#onStartedWakingUp. - if (mScreenOffAnimationController.isKeyguardShowDelayed()) { + if (mScreenOffAnimationController.shouldDelayKeyguardShow()) { if (DEBUG) { Log.d(TAG, "#maybeHandlePendingLock: not handling because the screen off " - + "animation's isKeyguardShowDelayed() returned true. This should be " + + "animation's shouldDelayKeyguardShow() returned true. This should be " + "handled soon by #onStartedWakingUp, or by the end actions of the " + "screen off animation."); } @@ -1792,7 +1810,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.beginSection("KeyguardViewMediator#setOccluded"); if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded); - mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD); mHandler.removeMessages(SET_OCCLUDED); Message msg = mHandler.obtainMessage(SET_OCCLUDED, isOccluded ? 1 : 0, animate ? 1 : 0); mHandler.sendMessage(msg); @@ -1825,6 +1842,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private void handleSetOccluded(boolean isOccluded, boolean animate) { Trace.beginSection("KeyguardViewMediator#handleSetOccluded"); Log.d(TAG, "handleSetOccluded(" + isOccluded + ")"); + mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD); + synchronized (KeyguardViewMediator.this) { if (mHiding && isOccluded) { // We're in the process of going away but WindowManager wants to show a diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java index 017b65acd1d2..ffd8a0244a86 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java @@ -33,6 +33,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -63,6 +64,7 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe private final Context mContext; private final DisplayMetrics mDisplayMetrics; + private final SystemClock mSystemClock; @Nullable private final IWallpaperManager mWallpaperManagerService; @@ -71,6 +73,9 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN; + public static final long UNKNOWN_LAST_WAKE_TIME = -1; + private long mLastWakeTime = UNKNOWN_LAST_WAKE_TIME; + @Nullable private Point mLastWakeOriginLocation = null; @@ -84,10 +89,12 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe public WakefulnessLifecycle( Context context, @Nullable IWallpaperManager wallpaperManagerService, + SystemClock systemClock, DumpManager dumpManager) { mContext = context; mDisplayMetrics = context.getResources().getDisplayMetrics(); mWallpaperManagerService = wallpaperManagerService; + mSystemClock = systemClock; dumpManager.registerDumpable(getClass().getSimpleName(), this); } @@ -104,6 +111,14 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe } /** + * Returns the most recent time (in device uptimeMillis) the display woke up. + * Returns {@link UNKNOWN_LAST_WAKE_TIME} if there hasn't been a wakeup yet. + */ + public long getLastWakeTime() { + return mLastWakeTime; + } + + /** * Returns the most recent reason the device went to sleep up. This is one of * PowerManager.GO_TO_SLEEP_REASON_*. */ @@ -117,6 +132,7 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe } setWakefulness(WAKEFULNESS_WAKING); mLastWakeReason = pmWakeReason; + mLastWakeTime = mSystemClock.uptimeMillis(); updateLastWakeOriginLocation(); if (mWallpaperManagerService != null) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 47ef0fac17ab..98d3570106ce 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -112,6 +113,7 @@ public class KeyguardModule { ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor, DreamOverlayStateController dreamOverlayStateController, + FeatureFlags featureFlags, Lazy<ShadeController> shadeController, Lazy<NotificationShadeWindowController> notificationShadeWindowController, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, @@ -142,6 +144,7 @@ public class KeyguardModule { screenOnCoordinator, interactionJankMonitor, dreamOverlayStateController, + featureFlags, shadeController, notificationShadeWindowController, activityLaunchAnimator, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt new file mode 100644 index 000000000000..25d8f4021f87 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.data.repository + +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED +import android.content.Context +import android.content.IntentFilter +import android.os.Looper +import android.os.UserHandle +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.biometrics.AuthController +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.user.data.repository.UserRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transformLatest + +/** + * Acts as source of truth for biometric features. + * + * Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about + * upstream changes. + */ +interface BiometricRepository { + /** Whether any fingerprints are enrolled for the current user. */ + val isFingerprintEnrolled: StateFlow<Boolean> + + /** + * Whether the current user is allowed to use a strong biometric for device entry based on + * Android Security policies. If false, the user may be able to use primary authentication for + * device entry. + */ + val isStrongBiometricAllowed: StateFlow<Boolean> + + /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */ + val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> +} + +@SysUISingleton +class BiometricRepositoryImpl +@Inject +constructor( + context: Context, + lockPatternUtils: LockPatternUtils, + broadcastDispatcher: BroadcastDispatcher, + authController: AuthController, + userRepository: UserRepository, + devicePolicyManager: DevicePolicyManager, + @Application scope: CoroutineScope, + @Background backgroundDispatcher: CoroutineDispatcher, + @Main looper: Looper, +) : BiometricRepository { + + /** UserId of the current selected user. */ + private val selectedUserId: Flow<Int> = + userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged() + + override val isFingerprintEnrolled: StateFlow<Boolean> = + selectedUserId + .flatMapLatest { userId -> + conflatedCallbackFlow { + val callback = + object : AuthController.Callback { + override fun onEnrollmentsChanged( + sensorBiometricType: BiometricType, + userId: Int, + hasEnrollments: Boolean + ) { + if (sensorBiometricType.isFingerprint) { + trySendWithFailureLogging( + hasEnrollments, + TAG, + "update fpEnrollment" + ) + } + } + } + authController.addCallback(callback) + awaitClose { authController.removeCallback(callback) } + } + } + .stateIn( + scope, + started = SharingStarted.Eagerly, + initialValue = + authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id) + ) + + override val isStrongBiometricAllowed: StateFlow<Boolean> = + selectedUserId + .flatMapLatest { currUserId -> + conflatedCallbackFlow { + val callback = + object : LockPatternUtils.StrongAuthTracker(context, looper) { + override fun onStrongAuthRequiredChanged(userId: Int) { + if (currUserId != userId) { + return + } + + trySendWithFailureLogging( + isBiometricAllowedForUser(true, currUserId), + TAG + ) + } + + override fun onIsNonStrongBiometricAllowedChanged(userId: Int) { + // no-op + } + } + lockPatternUtils.registerStrongAuthTracker(callback) + awaitClose { lockPatternUtils.unregisterStrongAuthTracker(callback) } + } + } + .stateIn( + scope, + started = SharingStarted.Eagerly, + initialValue = + lockPatternUtils.isBiometricAllowedForUser( + userRepository.getSelectedUserInfo().id + ) + ) + + override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> = + selectedUserId + .flatMapLatest { userId -> + broadcastDispatcher + .broadcastFlow( + filter = IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + user = UserHandle.ALL + ) + .transformLatest { + emit( + (devicePolicyManager.getKeyguardDisabledFeatures(null, userId) and + DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0 + ) + } + .flowOn(backgroundDispatcher) + .distinctUntilChanged() + } + .stateIn( + scope, + started = SharingStarted.Eagerly, + initialValue = + devicePolicyManager.getKeyguardDisabledFeatures( + null, + userRepository.getSelectedUserInfo().id + ) and DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT == 0 + ) + + companion object { + private const val TAG = "BiometricsRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricType.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricType.kt new file mode 100644 index 000000000000..93c97813d8e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricType.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +enum class BiometricType(val isFingerprint: Boolean) { + // An unsupported biometric type + UNKNOWN(false), + + // Fingerprint sensor that is located on the back (opposite side of the display) of the device + REAR_FINGERPRINT(true), + + // Fingerprint sensor that is located under the display + UNDER_DISPLAY_FINGERPRINT(true), + + // Fingerprint sensor that is located on the side of the device, typically on the power button + SIDE_FINGERPRINT(true), + FACE(false), +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index 90f3c7d88c8f..2e34e9a93dff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -26,9 +26,11 @@ import com.android.systemui.log.dagger.BouncerLog import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.phone.KeyguardBouncer +import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn @@ -44,6 +46,7 @@ class KeyguardBouncerRepository @Inject constructor( private val viewMediatorCallback: ViewMediatorCallback, + private val clock: SystemClock, @Application private val applicationScope: CoroutineScope, @BouncerLog private val buffer: TableLogBuffer, ) { @@ -94,6 +97,14 @@ constructor( setUpLogging() } + /** Values associated with the AlternateBouncer */ + private val _isAlternateBouncerVisible = MutableStateFlow(false) + val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow() + var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE + private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false) + val isAlternateBouncerUIAvailable: StateFlow<Boolean> = + _isAlternateBouncerUIAvailable.asStateFlow() + fun setPrimaryScrimmed(isScrimmed: Boolean) { _primaryBouncerScrimmed.value = isScrimmed } @@ -102,6 +113,19 @@ constructor( _primaryBouncerVisible.value = isVisible } + fun setAlternateVisible(isVisible: Boolean) { + if (isVisible && !_isAlternateBouncerVisible.value) { + lastAlternateBouncerVisibleTime = clock.uptimeMillis() + } else if (!isVisible) { + lastAlternateBouncerVisibleTime = NOT_VISIBLE + } + _isAlternateBouncerVisible.value = isVisible + } + + fun setAlternateBouncerUIAvailable(isAvailable: Boolean) { + _isAlternateBouncerUIAvailable.value = isAvailable + } + fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) { _primaryBouncerShow.value = keyguardBouncerModel } @@ -202,4 +226,8 @@ constructor( .logDiffsForTable(buffer, "", "ResourceUpdateRequests", false) .launchIn(applicationScope) } + + companion object { + private const val NOT_VISIBLE = -1L + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index a4fd087a24b1..d99af90ab6dc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -40,6 +40,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode +import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -88,6 +89,9 @@ interface KeyguardRepository { /** Observable for whether the bouncer is showing. */ val isBouncerShowing: Flow<Boolean> + /** Is the always-on display available to be used? */ + val isAodAvailable: Flow<Boolean> + /** * Observable for whether we are in doze state. * @@ -182,6 +186,7 @@ constructor( private val keyguardStateController: KeyguardStateController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dozeTransitionListener: DozeTransitionListener, + private val dozeParameters: DozeParameters, private val authController: AuthController, private val dreamOverlayCallbackController: DreamOverlayCallbackController, ) : KeyguardRepository { @@ -220,6 +225,31 @@ constructor( } .distinctUntilChanged() + override val isAodAvailable: Flow<Boolean> = + conflatedCallbackFlow { + val callback = + object : DozeParameters.Callback { + override fun onAlwaysOnChange() { + trySendWithFailureLogging( + dozeParameters.getAlwaysOn(), + TAG, + "updated isAodAvailable" + ) + } + } + + dozeParameters.addCallback(callback) + // Adding the callback does not send an initial update. + trySendWithFailureLogging( + dozeParameters.getAlwaysOn(), + TAG, + "initial isAodAvailable" + ) + + awaitClose { dozeParameters.removeCallback(callback) } + } + .distinctUntilChanged() + override val isKeyguardOccluded: Flow<Boolean> = conflatedCallbackFlow { val callback = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 26f853f3ad1c..4639597a9b8c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -30,4 +30,6 @@ interface KeyguardRepositoryModule { @Binds fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository + + @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 343c2dc172fc..d14b66a68f11 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -135,11 +135,14 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio Log.i(TAG, "Duplicate call to start the transition, rejecting: $info") return null } - if (lastStep.transitionState != TransitionState.FINISHED) { - Log.i(TAG, "Transition still active: $lastStep, canceling") - } + val startingValue = + if (lastStep.transitionState != TransitionState.FINISHED) { + Log.i(TAG, "Transition still active: $lastStep, canceling") + lastStep.value + } else { + 0f + } - val startingValue = 1f - lastStep.value lastAnimator?.cancel() lastAnimator = info.animator diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt new file mode 100644 index 000000000000..28c0b288147b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.BiometricRepository +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */ +@SysUISingleton +class AlternateBouncerInteractor +@Inject +constructor( + private val bouncerRepository: KeyguardBouncerRepository, + private val biometricRepository: BiometricRepository, + private val systemClock: SystemClock, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + featureFlags: FeatureFlags, +) { + val isModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER) + var legacyAlternateBouncer: LegacyAlternateBouncer? = null + var legacyAlternateBouncerVisibleTime: Long = NOT_VISIBLE + + val isVisible: Flow<Boolean> = bouncerRepository.isAlternateBouncerVisible + + /** + * Sets the correct bouncer states to show the alternate bouncer if it can show. + * @return whether alternateBouncer is visible + */ + fun show(): Boolean { + return when { + isModernAlternateBouncerEnabled -> { + bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint()) + isVisibleState() + } + canShowAlternateBouncerForFingerprint() -> { + if (legacyAlternateBouncer?.showAlternateBouncer() == true) { + legacyAlternateBouncerVisibleTime = systemClock.uptimeMillis() + true + } else { + false + } + } + else -> false + } + } + + /** + * Sets the correct bouncer states to hide the bouncer. Should only be called through + * StatusBarKeyguardViewManager until ScrimController is refactored to use + * alternateBouncerInteractor. + * @return true if the alternate bouncer was newly hidden, else false. + */ + fun hide(): Boolean { + return if (isModernAlternateBouncerEnabled) { + val wasAlternateBouncerVisible = isVisibleState() + bouncerRepository.setAlternateVisible(false) + wasAlternateBouncerVisible && !isVisibleState() + } else { + legacyAlternateBouncer?.hideAlternateBouncer() ?: false + } + } + + fun isVisibleState(): Boolean { + return if (isModernAlternateBouncerEnabled) { + bouncerRepository.isAlternateBouncerVisible.value + } else { + legacyAlternateBouncer?.isShowingAlternateBouncer ?: false + } + } + + fun setAlternateBouncerUIAvailable(isAvailable: Boolean) { + bouncerRepository.setAlternateBouncerUIAvailable(isAvailable) + } + + fun canShowAlternateBouncerForFingerprint(): Boolean { + return if (isModernAlternateBouncerEnabled) { + bouncerRepository.isAlternateBouncerUIAvailable.value && + biometricRepository.isFingerprintEnrolled.value && + biometricRepository.isStrongBiometricAllowed.value && + biometricRepository.isFingerprintEnabledByDevicePolicy.value + } else { + legacyAlternateBouncer != null && + keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true) + } + } + + /** + * Whether the alt bouncer has shown for a minimum time before allowing touches to dismiss the + * alternate bouncer and show the primary bouncer. + */ + fun hasAlternateBouncerShownWithMinTime(): Boolean { + return if (isModernAlternateBouncerEnabled) { + (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) > + MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS + } else { + systemClock.uptimeMillis() - legacyAlternateBouncerVisibleTime > 200 + } + } + + companion object { + private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L + private const val NOT_VISIBLE = -1L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index fd2d271e40f9..ce61f2fec92f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -21,9 +21,9 @@ import com.android.systemui.animation.Interpolators 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.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration @@ -48,12 +48,11 @@ constructor( private fun listenForDozingToLockscreen() { scope.launch { - keyguardInteractor.dozeTransitionModel + keyguardInteractor.wakefulnessModel .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { pair -> - val (dozeTransitionModel, lastStartedTransition) = pair + .collect { (wakefulnessModel, lastStartedTransition) -> if ( - isDozeOff(dozeTransitionModel.to) && + isWakingOrStartingToWake(wakefulnessModel) && lastStartedTransition.to == KeyguardState.DOZING ) { keyguardTransitionRepository.startTransition( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 553fafeb92c3..9203a9b924a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -26,7 +26,10 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @SysUISingleton @@ -40,7 +43,7 @@ constructor( ) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) { override fun start() { - listenForGoneToAod() + listenForGoneToAodOrDozing() listenForGoneToDreaming() } @@ -56,7 +59,7 @@ constructor( name, KeyguardState.GONE, KeyguardState.DREAMING, - getAnimator(), + getAnimator(TO_DREAMING_DURATION), ) ) } @@ -64,12 +67,18 @@ constructor( } } - private fun listenForGoneToAod() { + private fun listenForGoneToAodOrDozing() { scope.launch { keyguardInteractor.wakefulnessModel - .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair) - .collect { pair -> - val (wakefulnessState, keyguardState) = pair + .sample( + combine( + keyguardTransitionInteractor.finishedKeyguardState, + keyguardInteractor.isAodAvailable, + ::Pair + ), + ::toTriple + ) + .collect { (wakefulnessState, keyguardState, isAodAvailable) -> if ( keyguardState == KeyguardState.GONE && wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP @@ -78,7 +87,11 @@ constructor( TransitionInfo( name, KeyguardState.GONE, - KeyguardState.AOD, + if (isAodAvailable) { + KeyguardState.AOD + } else { + KeyguardState.DOZING + }, getAnimator(), ) ) @@ -87,14 +100,15 @@ constructor( } } - private fun getAnimator(): ValueAnimator { + private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { return ValueAnimator().apply { setInterpolator(Interpolators.LINEAR) - setDuration(TRANSITION_DURATION_MS) + setDuration(duration.inWholeMilliseconds) } } companion object { - private const val TRANSITION_DURATION_MS = 500L + private val DEFAULT_DURATION = 500.milliseconds + val TO_DREAMING_DURATION = 933.milliseconds } } 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 326acc9eb762..64028ceb2fbe 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 @@ -21,15 +21,17 @@ import com.android.systemui.animation.Interpolators 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.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine @@ -51,7 +53,8 @@ constructor( override fun start() { listenForLockscreenToGone() listenForLockscreenToOccluded() - listenForLockscreenToAod() + listenForLockscreenToCamera() + listenForLockscreenToAodOrDozing() listenForLockscreenToBouncer() listenForLockscreenToDreaming() listenForLockscreenToBouncerDragging() @@ -69,7 +72,7 @@ constructor( name, KeyguardState.LOCKSCREEN, KeyguardState.DREAMING, - getAnimator(), + getAnimator(TO_DREAMING_DURATION), ) ) } @@ -184,17 +187,14 @@ constructor( ), ::toTriple ) - .collect { triple -> - val (isOccluded, keyguardState, isDreaming) = triple - // Occlusion signals come from the framework, and should interrupt any - // existing transition - if (isOccluded && !isDreaming) { + .collect { (isOccluded, keyguardState, isDreaming) -> + if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { keyguardTransitionRepository.startTransition( TransitionInfo( name, keyguardState, KeyguardState.OCCLUDED, - getAnimator(), + getAnimator(TO_OCCLUDED_DURATION), ) ) } @@ -202,19 +202,59 @@ constructor( } } - private fun listenForLockscreenToAod() { + /** This signal may come in before the occlusion signal, and can provide a custom transition */ + private fun listenForLockscreenToCamera() { scope.launch { - keyguardInteractor - .dozeTransitionTo(DozeStateModel.DOZE_AOD) + keyguardInteractor.onCameraLaunchDetected .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { pair -> - val (dozeToAod, lastStartedStep) = pair - if (lastStartedStep.to == KeyguardState.LOCKSCREEN) { + .collect { (_, lastStartedStep) -> + // DREAMING/AOD/OFF may trigger on the first power button push, so include this + // state in order to cancel and correct the transition + if ( + lastStartedStep.to == KeyguardState.LOCKSCREEN || + lastStartedStep.to == KeyguardState.DREAMING || + lastStartedStep.to == KeyguardState.DOZING || + lastStartedStep.to == KeyguardState.AOD || + lastStartedStep.to == KeyguardState.OFF + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + getAnimator(TO_OCCLUDED_DURATION), + ) + ) + } + } + } + } + + private fun listenForLockscreenToAodOrDozing() { + scope.launch { + keyguardInteractor.wakefulnessModel + .sample( + combine( + keyguardTransitionInteractor.startedKeyguardTransitionStep, + keyguardInteractor.isAodAvailable, + ::Pair + ), + ::toTriple + ) + .collect { (wakefulnessState, lastStartedStep, isAodAvailable) -> + if ( + lastStartedStep.to == KeyguardState.LOCKSCREEN && + wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP + ) { keyguardTransitionRepository.startTransition( TransitionInfo( name, KeyguardState.LOCKSCREEN, - KeyguardState.AOD, + if (isAodAvailable) { + KeyguardState.AOD + } else { + KeyguardState.DOZING + }, getAnimator(), ) ) @@ -223,14 +263,16 @@ constructor( } } - private fun getAnimator(): ValueAnimator { + private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { return ValueAnimator().apply { setInterpolator(Interpolators.LINEAR) - setDuration(TRANSITION_DURATION_MS) + setDuration(duration.inWholeMilliseconds) } } companion object { - private const val TRANSITION_DURATION_MS = 500L + private val DEFAULT_DURATION = 500.milliseconds + val TO_DREAMING_DURATION = 933.milliseconds + val TO_OCCLUDED_DURATION = 450.milliseconds } } 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 88789019b10f..2dc8fee25379 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 @@ -23,12 +23,14 @@ 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.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @SysUISingleton @@ -44,6 +46,7 @@ constructor( override fun start() { listenForOccludedToLockscreen() listenForOccludedToDreaming() + listenForOccludedToAodOrDozing() } private fun listenForOccludedToDreaming() { @@ -70,8 +73,7 @@ constructor( scope.launch { keyguardInteractor.isKeyguardOccluded .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) - .collect { pair -> - val (isOccluded, lastStartedKeyguardState) = pair + .collect { (isOccluded, lastStartedKeyguardState) -> // Occlusion signals come from the framework, and should interrupt any // existing transition if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) { @@ -88,6 +90,39 @@ constructor( } } + private fun listenForOccludedToAodOrDozing() { + scope.launch { + keyguardInteractor.wakefulnessModel + .sample( + combine( + keyguardTransitionInteractor.startedKeyguardTransitionStep, + keyguardInteractor.isAodAvailable, + ::Pair + ), + ::toTriple + ) + .collect { (wakefulnessState, lastStartedStep, isAodAvailable) -> + if ( + lastStartedStep.to == KeyguardState.OCCLUDED && + wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.OCCLUDED, + if (isAodAvailable) { + KeyguardState.AOD + } else { + KeyguardState.DOZING + }, + getAnimator(), + ) + ) + } + } + } + } + private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator { return ValueAnimator().apply { setInterpolator(Interpolators.LINEAR) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 402c1793f0b2..490d22eb0820 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -17,17 +17,24 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.StatusBarManager import android.graphics.Point +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.CommandQueue.Callbacks import com.android.systemui.util.kotlin.sample import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -41,6 +48,7 @@ class KeyguardInteractor @Inject constructor( private val repository: KeyguardRepository, + private val commandQueue: CommandQueue, ) { /** * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at @@ -49,6 +57,8 @@ constructor( val dozeAmount: Flow<Float> = repository.linearDozeAmount /** Whether the system is in doze mode. */ val isDozing: Flow<Boolean> = repository.isDozing + /** Whether Always-on Display mode is available. */ + val isAodAvailable: Flow<Boolean> = repository.isAodAvailable /** Doze transition information. */ val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel /** @@ -58,6 +68,23 @@ constructor( val isDreaming: Flow<Boolean> = repository.isDreaming /** Whether the system is dreaming with an overlay active */ val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay + /** Event for when the camera gesture is detected */ + val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow { + val callback = + object : CommandQueue.Callbacks { + override fun onCameraLaunchGestureDetected(source: Int) { + trySendWithFailureLogging( + cameraLaunchSourceIntToModel(source), + TAG, + "updated onCameraLaunchGestureDetected" + ) + } + } + + commandQueue.addCallback(callback) + + awaitClose { commandQueue.removeCallback(callback) } + } /** * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means @@ -103,4 +130,21 @@ constructor( fun isKeyguardShowing(): Boolean { return repository.isKeyguardShowing() } + + private fun cameraLaunchSourceIntToModel(value: Int): CameraLaunchSourceModel { + return when (value) { + StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchSourceModel.WIGGLE + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP -> + CameraLaunchSourceModel.POWER_DOUBLE_TAP + StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER -> + CameraLaunchSourceModel.LIFT_TRIGGER + StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE -> + CameraLaunchSourceModel.QUICK_AFFORDANCE + else -> throw IllegalArgumentException("Invalid CameraLaunchSourceModel value: $value") + } + } + + companion object { + private const val TAG = "KeyguardInteractor" + } } 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 a2661d76d90d..d4e2349907bc 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,11 +19,14 @@ 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.plugins.log.LogLevel.VERBOSE import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +private val TAG = KeyguardTransitionAuditLogger::class.simpleName!! + /** Collect flows of interest for auditing keyguard transitions. */ @SysUISingleton class KeyguardTransitionAuditLogger @@ -37,35 +40,47 @@ constructor( fun start() { scope.launch { - keyguardInteractor.wakefulnessModel.collect { logger.v("WakefulnessModel", it) } + keyguardInteractor.wakefulnessModel.collect { + logger.log(TAG, VERBOSE, "WakefulnessModel", it) + } } scope.launch { - keyguardInteractor.isBouncerShowing.collect { logger.v("Bouncer showing", it) } + keyguardInteractor.isBouncerShowing.collect { + logger.log(TAG, VERBOSE, "Bouncer showing", it) + } } - scope.launch { keyguardInteractor.isDozing.collect { logger.v("isDozing", it) } } + scope.launch { + keyguardInteractor.isDozing.collect { logger.log(TAG, VERBOSE, "isDozing", it) } + } - scope.launch { keyguardInteractor.isDreaming.collect { logger.v("isDreaming", it) } } + scope.launch { + keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) } + } scope.launch { interactor.finishedKeyguardTransitionStep.collect { - logger.i("Finished transition", it) + logger.log(TAG, VERBOSE, "Finished transition", it) } } scope.launch { interactor.canceledKeyguardTransitionStep.collect { - logger.i("Canceled transition", it) + logger.log(TAG, VERBOSE, "Canceled transition", it) } } scope.launch { - interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) } + interactor.startedKeyguardTransitionStep.collect { + logger.log(TAG, VERBOSE, "Started transition", it) + } } scope.launch { - keyguardInteractor.dozeTransitionModel.collect { logger.i("Doze transition", it) } + keyguardInteractor.dozeTransitionModel.collect { + logger.log(TAG, VERBOSE, "Doze transition", it) + } } } } 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 04024be571c8..9cdbcda1343d 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 @@ -42,24 +42,32 @@ class KeyguardTransitionInteractor constructor( repository: KeyguardTransitionRepository, ) { + /** (any)->AOD transition information */ + val anyStateToAodTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == KeyguardState.AOD } + /** AOD->LOCKSCREEN transition information. */ val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN) - /** LOCKSCREEN->AOD transition information. */ - val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) - /** DREAMING->LOCKSCREEN transition information. */ val dreamingToLockscreenTransition: Flow<TransitionStep> = repository.transition(DREAMING, LOCKSCREEN) + /** LOCKSCREEN->AOD transition information. */ + val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) + + /** LOCKSCREEN->DREAMING transition information. */ + val lockscreenToDreamingTransition: Flow<TransitionStep> = + repository.transition(LOCKSCREEN, DREAMING) + + /** LOCKSCREEN->OCCLUDED transition information. */ + val lockscreenToOccludedTransition: Flow<TransitionStep> = + repository.transition(LOCKSCREEN, OCCLUDED) + /** OCCLUDED->LOCKSCREEN transition information. */ val occludedToLockscreenTransition: Flow<TransitionStep> = repository.transition(OCCLUDED, LOCKSCREEN) - /** (any)->AOD transition information */ - val anyStateToAodTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.AOD } - /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt new file mode 100644 index 000000000000..19baf7705546 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt @@ -0,0 +1,28 @@ +/* + * 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.shared.model + +/** Camera launch sources */ +enum class CameraLaunchSourceModel { + /** Device is wiggled */ + WIGGLE, + /** Power button has been double tapped */ + POWER_DOUBLE_TAP, + /** Device has been lifted */ + LIFT_TRIGGER, + /** Quick affordance button has been pressed */ + QUICK_AFFORDANCE, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 0e4058bf8f6d..9d8bf7deb03e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -45,7 +45,6 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewMod import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper -import com.android.systemui.util.kotlin.pairwise import kotlin.math.pow import kotlin.math.sqrt import kotlin.time.Duration.Companion.milliseconds @@ -129,18 +128,6 @@ object KeyguardBottomAreaViewBinder { } launch { - viewModel.startButton - .map { it.isActivated } - .pairwise() - .collect { (prev, next) -> - when { - !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated) - prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated) - } - } - } - - launch { viewModel.endButton.collect { buttonModel -> updateButton( view = endButton, @@ -153,18 +140,6 @@ object KeyguardBottomAreaViewBinder { } launch { - viewModel.endButton - .map { it.isActivated } - .pairwise() - .collect { (prev, next) -> - when { - !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated) - prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated) - } - } - } - - launch { viewModel.isOverlayContainerVisible.collect { isVisible -> overlayContainer.visibility = if (isVisible) { @@ -383,6 +358,13 @@ object KeyguardBottomAreaViewBinder { .setDuration(longPressDurationMs) .withEndAction { view.setOnClickListener { + vibratorHelper?.vibrate( + if (viewModel.isActivated) { + Vibrations.Activated + } else { + Vibrations.Deactivated + } + ) viewModel.onClicked( KeyguardQuickAffordanceViewModel.OnClickedParameters( configKey = viewModel.configKey, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt new file mode 100644 index 000000000000..d48f87deaaf4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.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.systemui.keyguard.ui.viewmodel + +import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.AnimationParams +import com.android.systemui.keyguard.shared.model.TransitionState +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to + * consume. + */ +@SysUISingleton +class LockscreenToDreamingTransitionViewModel +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, +) { + + /** Lockscreen views y-translation */ + fun lockscreenTranslationY(translatePx: Int): Flow<Float> { + return merge( + flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> + (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) + }, + // On end, reset the translation to 0 + interactor.lockscreenToDreamingTransition + .filter { step -> step.transitionState == TransitionState.FINISHED } + .map { 0f } + ) + } + + /** Lockscreen views alpha */ + val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } + + private fun flowForAnimation(params: AnimationParams): Flow<Float> { + return interactor.transitionStepAnimation( + interactor.lockscreenToDreamingTransition, + params, + totalDuration = TO_DREAMING_DURATION + ) + } + + companion object { + @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds + + val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds) + val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt new file mode 100644 index 000000000000..22d292e92856 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -0,0 +1,71 @@ +/* + * 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.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.AnimationParams +import com.android.systemui.keyguard.shared.model.TransitionState +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to + * consume. + */ +@SysUISingleton +class LockscreenToOccludedTransitionViewModel +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, +) { + + /** Lockscreen views y-translation */ + fun lockscreenTranslationY(translatePx: Int): Flow<Float> { + return merge( + flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value -> + (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx) + }, + // On end, reset the translation to 0 + interactor.lockscreenToOccludedTransition + .filter { step -> step.transitionState == TransitionState.FINISHED } + .map { 0f } + ) + } + + /** Lockscreen views alpha */ + val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it } + + private fun flowForAnimation(params: AnimationParams): Flow<Float> { + return interactor.transitionStepAnimation( + interactor.lockscreenToOccludedTransition, + params, + totalDuration = TO_OCCLUDED_DURATION + ) + } + + companion object { + val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION) + val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt index 0645236226bd..9f563fe4eae5 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardClockLog.kt @@ -23,3 +23,15 @@ import javax.inject.Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class KeyguardClockLog + +/** A [com.android.systemui.plugins.log.LogBuffer] for small keyguard clock logs. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyguardSmallClockLog + +/** A [com.android.systemui.plugins.log.LogBuffer] for large keyguard clock logs. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyguardLargeClockLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index bc29858c5b92..d7817e1dc4f0 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -335,13 +335,33 @@ public class LogModule { } /** - * Provides a {@link LogBuffer} for keyguard clock logs. + * Provides a {@link LogBuffer} for general keyguard clock logs. */ @Provides @SysUISingleton @KeyguardClockLog public static LogBuffer provideKeyguardClockLog(LogBufferFactory factory) { - return factory.create("KeyguardClockLog", 500); + return factory.create("KeyguardClockLog", 100); + } + + /** + * Provides a {@link LogBuffer} for keyguard small clock logs. + */ + @Provides + @SysUISingleton + @KeyguardSmallClockLog + public static LogBuffer provideKeyguardSmallClockLog(LogBufferFactory factory) { + return factory.create("KeyguardSmallClockLog", 100); + } + + /** + * Provides a {@link LogBuffer} for keyguard large clock logs. + */ + @Provides + @SysUISingleton + @KeyguardLargeClockLog + public static LogBuffer provideKeyguardLargeClockLog(LogBufferFactory factory) { + return factory.create("KeyguardLargeClockLog", 100); } /** diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt index 7a90a7470cd2..7ccc43ce62c2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt @@ -29,6 +29,18 @@ constructor( private val dumpManager: DumpManager, private val systemClock: SystemClock, ) { + private val existingBuffers = mutableMapOf<String, TableLogBuffer>() + + /** + * Creates a new [TableLogBuffer]. This method should only be called from static contexts, where + * it is guaranteed only to be created one time. See [getOrCreate] for a cache-aware method of + * obtaining a buffer. + * + * @param name a unique table name + * @param maxSize the buffer max size. See [adjustMaxSize] + * + * @return a new [TableLogBuffer] registered with [DumpManager] + */ fun create( name: String, maxSize: Int, @@ -37,4 +49,23 @@ constructor( dumpManager.registerNormalDumpable(name, tableBuffer) return tableBuffer } + + /** + * Log buffers are retained indefinitely by [DumpManager], so that they can be represented in + * bugreports. Because of this, many of them are created statically in the Dagger graph. + * + * In the case where you have to create a logbuffer with a name only known at runtime, this + * method can be used to lazily create a table log buffer which is then cached for reuse. + * + * @return a [TableLogBuffer] suitable for reuse + */ + fun getOrCreate( + name: String, + maxSize: Int, + ): TableLogBuffer = + existingBuffers.getOrElse(name) { + val buffer = create(name, maxSize) + existingBuffers[name] = buffer + buffer + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index 1fdbc99333cb..e7f7647797cd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -94,7 +94,7 @@ constructor( private var currentCarouselWidth: Int = 0 /** The current height of the carousel */ - private var currentCarouselHeight: Int = 0 + @VisibleForTesting var currentCarouselHeight: Int = 0 /** Are we currently showing only active players */ private var currentlyShowingOnlyActive: Boolean = false @@ -128,14 +128,14 @@ constructor( /** The measured height of the carousel */ private var carouselMeasureHeight: Int = 0 private var desiredHostState: MediaHostState? = null - private val mediaCarousel: MediaScrollView + @VisibleForTesting var mediaCarousel: MediaScrollView val mediaCarouselScrollHandler: MediaCarouselScrollHandler val mediaFrame: ViewGroup @VisibleForTesting lateinit var settingsButton: View private set private val mediaContent: ViewGroup - @VisibleForTesting val pageIndicator: PageIndicator + @VisibleForTesting var pageIndicator: PageIndicator private val visualStabilityCallback: OnReorderingAllowedListener private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() @@ -160,30 +160,26 @@ constructor( } companion object { - const val ANIMATION_BASE_DURATION = 2200f - const val DURATION = 167f - const val DETAILS_DELAY = 1067f - const val CONTROLS_DELAY = 1400f - const val PAGINATION_DELAY = 1900f - const val MEDIATITLES_DELAY = 1000f - const val MEDIACONTAINERS_DELAY = 967f val TRANSFORM_BEZIER = PathInterpolator(0.68F, 0F, 0F, 1F) - val REVERSE_BEZIER = PathInterpolator(0F, 0.68F, 1F, 0F) - - fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float { - val transformStartFraction = delay / ANIMATION_BASE_DURATION - val transformDurationFraction = duration / ANIMATION_BASE_DURATION - val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction) - return MathUtils.constrain( - (squishinessToTime - transformStartFraction) / transformDurationFraction, - 0F, - 1F - ) + + fun calculateAlpha( + squishinessFraction: Float, + startPosition: Float, + endPosition: Float + ): Float { + val transformFraction = + MathUtils.constrain( + (squishinessFraction - startPosition) / (endPosition - startPosition), + 0F, + 1F + ) + return TRANSFORM_BEZIER.getInterpolation(transformFraction) } } private val configListener = object : ConfigurationController.ConfigurationListener { + var lastOrientation = -1 override fun onDensityOrFontScaleChanged() { // System font changes should only happen when UMO is offscreen or a flicker may @@ -200,7 +196,13 @@ constructor( override fun onConfigChanged(newConfig: Configuration?) { if (newConfig == null) return isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL - updatePlayers(recreateMedia = true) + val newOrientation = newConfig.orientation + if (lastOrientation != newOrientation) { + // The players actually depend on the orientation possibly, so we have to + // recreate them (at least on large screen devices) + lastOrientation = newOrientation + updatePlayers(recreateMedia = true) + } } override fun onUiModeChanged() { @@ -717,6 +719,9 @@ constructor( private fun updatePlayers(recreateMedia: Boolean) { pageIndicator.tintList = ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator)) + val previousVisibleKey = + MediaPlayerData.visiblePlayerKeys() + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) -> if (isSsMediaRec) { @@ -741,6 +746,9 @@ constructor( isSsReactivated = isSsReactivated ) } + if (recreateMedia) { + reorderAllPlayers(previousVisibleKey) + } } } @@ -800,7 +808,12 @@ constructor( val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F val endAlpha = (if (endIsVisible) 1.0f else 0.0f) * - calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION) + calculateAlpha( + squishFraction, + (pageIndicator.translationY + pageIndicator.height) / + mediaCarousel.measuredHeight, + 1F + ) var alpha = 1.0f if (!endIsVisible || !startIsVisible) { var progress = currentTransitionProgress @@ -826,7 +839,8 @@ constructor( pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams pageIndicator.translationY = - (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat() + (mediaCarousel.measuredHeight - pageIndicator.height - layoutParams.bottomMargin) + .toFloat() } /** Update the dimension of this carousel. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index f7a9bc760caf..66f12d6242b0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -41,6 +41,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.dream.MediaDreamComplication import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeStateEvents @@ -93,6 +94,7 @@ constructor( private val keyguardStateController: KeyguardStateController, private val bypassController: KeyguardBypassController, private val mediaCarouselController: MediaCarouselController, + private val mediaManager: MediaDataManager, private val keyguardViewController: KeyguardViewController, private val dreamOverlayStateController: DreamOverlayStateController, configurationController: ConfigurationController, @@ -224,9 +226,9 @@ constructor( private var inSplitShade = false - /** Is there any active media in the carousel? */ - private var hasActiveMedia: Boolean = false - get() = mediaHosts.get(LOCATION_QQS)?.visible == true + /** Is there any active media or recommendation in the carousel? */ + private var hasActiveMediaOrRecommendation: Boolean = false + get() = mediaManager.hasActiveMediaOrRecommendation() /** Are we currently waiting on an animation to start? */ private var animationPending: Boolean = false @@ -582,12 +584,8 @@ constructor( val viewHost = createUniqueObjectHost() mediaObject.hostView = viewHost mediaObject.addVisibilityChangeListener { - // If QQS changes visibility, we need to force an update to ensure the transition - // goes into the correct state - val stateUpdate = mediaObject.location == LOCATION_QQS - // Never animate because of a visibility change, only state changes should do that - updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = stateUpdate) + updateDesiredLocation(forceNoAnimation = true) } mediaHosts[mediaObject.location] = mediaObject if (mediaObject.location == desiredLocation) { @@ -908,7 +906,7 @@ constructor( fun isCurrentlyInGuidedTransformation(): Boolean { return hasValidStartAndEndLocations() && getTransformationProgress() >= 0 && - areGuidedTransitionHostsVisible() + (areGuidedTransitionHostsVisible() || !hasActiveMediaOrRecommendation) } private fun hasValidStartAndEndLocations(): Boolean { @@ -965,7 +963,7 @@ constructor( private fun getQSTransformationProgress(): Float { val currentHost = getHost(desiredLocation) val previousHost = getHost(previousLocation) - if (hasActiveMedia && (currentHost?.location == LOCATION_QS && !inSplitShade)) { + if (currentHost?.location == LOCATION_QS && !inSplitShade) { if (previousHost?.location == LOCATION_QQS) { if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) { return qsExpansion @@ -1028,7 +1026,8 @@ constructor( private fun updateHostAttachment() = traceSection("MediaHierarchyManager#updateHostAttachment") { var newLocation = resolveLocationForFading() - var canUseOverlay = !isCurrentlyFading() + // Don't use the overlay when fading or when we don't have active media + var canUseOverlay = !isCurrentlyFading() && hasActiveMediaOrRecommendation if (isCrossFadeAnimatorRunning) { if ( getHost(newLocation)?.visible == true && @@ -1122,7 +1121,6 @@ constructor( dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS qsExpansion > 0.4f && onLockscreen -> LOCATION_QS - !hasActiveMedia -> LOCATION_QS onLockscreen && isSplitShadeExpanding() -> LOCATION_QS onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 322421318cb8..71cc47a53b52 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -24,11 +24,6 @@ import com.android.systemui.R import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.models.player.MediaViewHolder import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.MeasurementOutput @@ -36,6 +31,8 @@ import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionLayoutController import com.android.systemui.util.animation.TransitionViewState import com.android.systemui.util.traceSection +import java.lang.Float.max +import java.lang.Float.min import javax.inject.Inject /** @@ -304,39 +301,106 @@ constructor( val squishedViewState = viewState.copy() val squishedHeight = (squishedViewState.measureHeight * squishFraction).toInt() squishedViewState.height = squishedHeight - controlIds.forEach { id -> - squishedViewState.widgetStates.get(id)?.let { state -> - state.alpha = calculateAlpha(squishFraction, CONTROLS_DELAY, DURATION) - } - } - - detailIds.forEach { id -> - squishedViewState.widgetStates.get(id)?.let { state -> - state.alpha = calculateAlpha(squishFraction, DETAILS_DELAY, DURATION) - } - } - // We are not overriding the squishedViewStates height but only the children to avoid // them remeasuring the whole view. Instead it just remains as the original size backgroundIds.forEach { id -> - squishedViewState.widgetStates.get(id)?.let { state -> - state.height = squishedHeight - } + squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight } } - RecommendationViewHolder.mediaContainersIds.forEach { id -> + // media player + val controlsTop = + calculateWidgetGroupAlphaForSquishiness( + controlIds, + squishedViewState.measureHeight.toFloat(), + squishedViewState, + squishFraction + ) + calculateWidgetGroupAlphaForSquishiness( + detailIds, + controlsTop, + squishedViewState, + squishFraction + ) + // recommendation card + val titlesTop = + calculateWidgetGroupAlphaForSquishiness( + RecommendationViewHolder.mediaTitlesAndSubtitlesIds, + squishedViewState.measureHeight.toFloat(), + squishedViewState, + squishFraction + ) + calculateWidgetGroupAlphaForSquishiness( + RecommendationViewHolder.mediaContainersIds, + titlesTop, + squishedViewState, + squishFraction + ) + return squishedViewState + } + + /** + * This function is to make each widget in UMO disappear before being clipped by squished UMO + * + * The general rule is that widgets in UMO has been divided into several groups, and widgets in + * one group have the same alpha during squishing It will change from alpha 0.0 when the visible + * bottom of UMO reach the bottom of this group It will change to alpha 1.0 when the visible + * bottom of UMO reach the top of the group below e.g.Album title, artist title and play-pause + * button will change alpha together. + * ``` + * And their alpha becomes 1.0 when the visible bottom of UMO reach the top of controls, + * including progress bar, next button, previous button + * ``` + * widgetGroupIds: a group of widgets have same state during UMO is squished, + * ``` + * e.g. Album title, artist title and play-pause button + * ``` + * groupEndPosition: the height of UMO, when the height reaches this value, + * ``` + * widgets in this group should have 1.0 as alpha + * e.g., the group of album title, artist title and play-pause button will become fully + * visible when the height of UMO reaches the top of controls group + * (progress bar, previous button and next button) + * ``` + * squishedViewState: hold the widgetState of each widget, which will be modified + * squishFraction: the squishFraction of UMO + */ + private fun calculateWidgetGroupAlphaForSquishiness( + widgetGroupIds: Set<Int>, + groupEndPosition: Float, + squishedViewState: TransitionViewState, + squishFraction: Float + ): Float { + val nonsquishedHeight = squishedViewState.measureHeight + var groupTop = squishedViewState.measureHeight.toFloat() + var groupBottom = 0F + widgetGroupIds.forEach { id -> squishedViewState.widgetStates.get(id)?.let { state -> - state.alpha = calculateAlpha(squishFraction, MEDIACONTAINERS_DELAY, DURATION) + groupTop = min(groupTop, state.y) + groupBottom = max(groupBottom, state.y + state.height) } } - - RecommendationViewHolder.mediaTitlesAndSubtitlesIds.forEach { id -> + // startPosition means to the height of squished UMO where the widget alpha should start + // changing from 0.0 + // generally, it equals to the bottom of widgets, so that we can meet the requirement that + // widget should not go beyond the bounds of background + // endPosition means to the height of squished UMO where the widget alpha should finish + // changing alpha to 1.0 + var startPosition = groupBottom + val endPosition = groupEndPosition + if (startPosition == endPosition) { + startPosition = (endPosition - 0.2 * (groupBottom - groupTop)).toFloat() + } + widgetGroupIds.forEach { id -> squishedViewState.widgetStates.get(id)?.let { state -> - state.alpha = calculateAlpha(squishFraction, MEDIATITLES_DELAY, DURATION) + state.alpha = + calculateAlpha( + squishFraction, + startPosition / nonsquishedHeight, + endPosition / nonsquishedHeight + ) } } - - return squishedViewState + return groupTop // used for the widget group above this group } /** @@ -544,11 +608,13 @@ constructor( overrideSize?.let { // To be safe we're using a maximum here. The override size should always be set // properly though. - if (result.measureHeight != it.measuredHeight - || result.measureWidth != it.measuredWidth) { + if ( + result.measureHeight != it.measuredHeight || result.measureWidth != it.measuredWidth + ) { result.measureHeight = Math.max(it.measuredHeight, result.measureHeight) result.measureWidth = Math.max(it.measuredWidth, result.measureWidth) - // The measureHeight and the shown height should both be set to the overridden height + // The measureHeight and the shown height should both be set to the overridden + // height result.height = result.measureHeight result.width = result.measureWidth // Make sure all background views are also resized such that their size is correct diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 8eb25c4c2495..7bc0c0cc614b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -630,50 +630,28 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private void buildMediaItems(List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { - //TODO(b/257851968): do the organization only when there's no suggested sorted order - // we get from application - attachRangeInfo(devices); - Collections.sort(devices, Comparator.naturalOrder()); + if (!isRouteProcessSupported() || (isRouteProcessSupported() + && !mLocalMediaManager.isPreferenceRouteListingExist())) { + attachRangeInfo(devices); + Collections.sort(devices, Comparator.naturalOrder()); + } // For the first time building list, to make sure the top device is the connected // device. + boolean needToHandleMutingExpectedDevice = + hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote(); + final MediaDevice connectedMediaDevice = + needToHandleMutingExpectedDevice ? null + : getCurrentConnectedMediaDevice(); if (mMediaItemList.isEmpty()) { - boolean needToHandleMutingExpectedDevice = - hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote(); - final MediaDevice connectedMediaDevice = - needToHandleMutingExpectedDevice ? null - : getCurrentConnectedMediaDevice(); if (connectedMediaDevice == null) { if (DEBUG) { Log.d(TAG, "No connected media device or muting expected device exist."); } - if (needToHandleMutingExpectedDevice) { - for (MediaDevice device : devices) { - if (device.isMutingExpectedDevice()) { - mMediaItemList.add(0, new MediaItem(device)); - mMediaItemList.add(1, new MediaItem(mContext.getString( - R.string.media_output_group_title_speakers_and_displays), - MediaItem.MediaItemType.TYPE_GROUP_DIVIDER)); - } else { - mMediaItemList.add(new MediaItem(device)); - } - } - mMediaItemList.add(new MediaItem()); - } else { - mMediaItemList.addAll( - devices.stream().map(MediaItem::new).collect(Collectors.toList())); - categorizeMediaItems(null); - } + categorizeMediaItems(null, devices, needToHandleMutingExpectedDevice); return; } // selected device exist - for (MediaDevice device : devices) { - if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) { - mMediaItemList.add(0, new MediaItem(device)); - } else { - mMediaItemList.add(new MediaItem(device)); - } - } - categorizeMediaItems(connectedMediaDevice); + categorizeMediaItems(connectedMediaDevice, devices, false); return; } // To keep the same list order @@ -707,31 +685,46 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } } - private void categorizeMediaItems(MediaDevice connectedMediaDevice) { + private void categorizeMediaItems(MediaDevice connectedMediaDevice, List<MediaDevice> devices, + boolean needToHandleMutingExpectedDevice) { synchronized (mMediaDevicesLock) { Set<String> selectedDevicesIds = getSelectedMediaDevice().stream().map( MediaDevice::getId).collect(Collectors.toSet()); if (connectedMediaDevice != null) { selectedDevicesIds.add(connectedMediaDevice.getId()); } - int latestSelected = 1; - for (MediaItem item : mMediaItemList) { - if (item.getMediaDevice().isPresent()) { - MediaDevice device = item.getMediaDevice().get(); - if (selectedDevicesIds.contains(device.getId())) { - latestSelected = mMediaItemList.indexOf(item) + 1; - } else { - mMediaItemList.add(latestSelected, new MediaItem(mContext.getString( - R.string.media_output_group_title_speakers_and_displays), - MediaItem.MediaItemType.TYPE_GROUP_DIVIDER)); - break; + boolean suggestedDeviceAdded = false; + boolean displayGroupAdded = false; + for (MediaDevice device : devices) { + if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) { + mMediaItemList.add(0, new MediaItem(device)); + } else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains( + device.getId())) { + mMediaItemList.add(0, new MediaItem(device)); + } else { + if (device.isSuggestedDevice() && !suggestedDeviceAdded) { + attachGroupDivider(mContext.getString( + R.string.media_output_group_title_suggested_device)); + suggestedDeviceAdded = true; + } else if (!device.isSuggestedDevice() && !displayGroupAdded) { + attachGroupDivider(mContext.getString( + R.string.media_output_group_title_speakers_and_displays)); + displayGroupAdded = true; } + mMediaItemList.add(new MediaItem(device)); } } mMediaItemList.add(new MediaItem()); } } + private void attachGroupDivider(String title) { + synchronized (mMediaDevicesLock) { + mMediaItemList.add( + new MediaItem(title, MediaItem.MediaItemType.TYPE_GROUP_DIVIDER)); + } + } + private void attachRangeInfo(List<MediaDevice> devices) { for (MediaDevice mediaDevice : devices) { if (mNearbyDeviceInfoMap.containsKey(mediaDevice.getId())) { @@ -751,6 +744,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT); } + public boolean isRouteProcessSupported() { + return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING); + } + List<MediaDevice> getGroupMediaDevices() { final List<MediaDevice> selectedDevices = getSelectedMediaDevice(); final List<MediaDevice> selectableDevices = getSelectableMediaDevice(); diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index 9f44d984124f..935f38de2e4f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -150,7 +150,12 @@ constructor( logger: MediaTttLogger<ChipbarInfo>, ): ChipbarInfo { val packageName = routeInfo.clientPackageName - val otherDeviceName = routeInfo.name.toString() + val otherDeviceName = + if (routeInfo.name.isBlank()) { + context.getString(R.string.media_ttt_default_device_type) + } else { + routeInfo.name.toString() + } return ChipbarInfo( // Display the app's icon as the start icon diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 6dd60d043a06..8356440714e6 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -57,7 +57,9 @@ constructor( * If the keyguard is locked, notes will open as a full screen experience. A locked device has * no contextual information which let us use the whole screen space available. * - * If no in multi-window or the keyguard is unlocked, notes will open as a floating experience. + * If no in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be + * collapsed if the notes bubble is already opened. + * * That will let users open other apps in full screen, and take contextual notes. */ fun showNoteTask(isInMultiWindowMode: Boolean = false) { @@ -75,7 +77,7 @@ constructor( context.startActivity(intent) } else { // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter. - bubbles.showAppBubble(intent) + bubbles.showOrHideAppBubble(intent) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index f49ffb4d930c..774cb3442811 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.util.LifecycleFragment; +import com.android.systemui.util.Utils; import java.io.PrintWriter; import java.util.Arrays; @@ -683,7 +684,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } else { mQsMediaHost.setSquishFraction(mSquishinessFraction); } - + updateMediaPositions(); } private void setAlphaAnimationProgress(float progress) { @@ -758,6 +759,22 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca - mQSPanelController.getPaddingBottom()); } + private void updateMediaPositions() { + if (Utils.useQsMediaPlayer(getContext())) { + View hostView = mQsMediaHost.getHostView(); + // Make sure the media appears a bit from the top to make it look nicer + if (mLastQSExpansion > 0 && !isKeyguardState() && !mQqsMediaHost.getVisible() + && !mQSPanelController.shouldUseHorizontalLayout() && !mInSplitShade) { + float interpolation = 1.0f - mLastQSExpansion; + interpolation = Interpolators.ACCELERATE.getInterpolation(interpolation); + float translationY = -hostView.getHeight() * 1.3f * interpolation; + hostView.setTranslationY(translationY); + } else { + hostView.setTranslationY(0); + } + } + } + private boolean headerWillBeAnimating() { return mStatusBarState == KEYGUARD && mShowCollapsedOnKeyguard && !isKeyguardState(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index 7cf63f678c1d..1da30ade951b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -36,7 +36,6 @@ public interface QSHost { void removeCallback(Callback callback); void removeTile(String tileSpec); void removeTiles(Collection<String> specs); - void unmarkTileAsAutoAdded(String tileSpec); int indexOf(String tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 7bb672ce5880..e85d0a32cfa8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -372,18 +372,18 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr if (mUsingHorizontalLayout) { // Only height remaining parameters.getDisappearSize().set(0.0f, 0.4f); - // Disappearing on the right side on the bottom - parameters.getGonePivot().set(1.0f, 1.0f); + // Disappearing on the right side on the top + parameters.getGonePivot().set(1.0f, 0.0f); // translating a bit horizontal parameters.getContentTranslationFraction().set(0.25f, 1.0f); parameters.setDisappearEnd(0.6f); } else { // Only width remaining parameters.getDisappearSize().set(1.0f, 0.0f); - // Disappearing on the bottom - parameters.getGonePivot().set(0.0f, 1.0f); + // Disappearing on the top + parameters.getGonePivot().set(0.0f, 0.0f); // translating a bit vertical - parameters.getContentTranslationFraction().set(0.0f, 1.05f); + parameters.getContentTranslationFraction().set(0.0f, 1f); parameters.setDisappearEnd(0.95f); } parameters.setFadeStartPosition(0.95f); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index cad296b671b3..100853caa2d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -427,11 +427,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs))); } - @Override - public void unmarkTileAsAutoAdded(String spec) { - if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec); - } - /** * Add a tile to the end * diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 79fcc7d81372..17124901e4de 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -24,6 +24,7 @@ import android.util.AttributeSet; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.widget.LinearLayout; import android.widget.Toolbar; @@ -74,8 +75,8 @@ public class QSCustomizer extends LinearLayout { toolbar.setNavigationIcon( getResources().getDrawable(value.resourceId, mContext.getTheme())); - toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, - mContext.getString(com.android.internal.R.string.reset)); + toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0, com.android.internal.R.string.reset) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); toolbar.setTitle(R.string.qs_edit); mRecyclerView = findViewById(android.R.id.list); mTransparentView = findViewById(R.id.customizer_transparent_view); 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 9f376ae75efe..d32ef327e90e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -49,109 +49,135 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : } fun logTileAdded(tileSpec: String) { - log(DEBUG, { - str1 = tileSpec - }, { - "[$str1] Tile added" - }) + buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" }) } fun logTileDestroyed(tileSpec: String, reason: String) { - log(DEBUG, { - str1 = tileSpec - str2 = reason - }, { - "[$str1] Tile destroyed. Reason: $str2" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + str2 = reason + }, + { "[$str1] Tile destroyed. Reason: $str2" } + ) } fun logTileChangeListening(tileSpec: String, listening: Boolean) { - log(VERBOSE, { - bool1 = listening - str1 = tileSpec - }, { - "[$str1] Tile listening=$bool1" - }) + buffer.log( + TAG, + VERBOSE, + { + bool1 = listening + str1 = tileSpec + }, + { "[$str1] Tile listening=$bool1" } + ) } fun logAllTilesChangeListening(listening: Boolean, containerName: String, allSpecs: String) { - log(DEBUG, { - bool1 = listening - str1 = containerName - str2 = allSpecs - }, { - "Tiles listening=$bool1 in $str1. $str2" - }) + buffer.log( + TAG, + DEBUG, + { + bool1 = listening + str1 = containerName + str2 = allSpecs + }, + { "Tiles listening=$bool1 in $str1. $str2" } + ) } fun logTileClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - str2 = StatusBarState.toString(statusBarState) - str3 = toStateString(state) - }, { - "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + str2 = StatusBarState.toString(statusBarState) + str3 = toStateString(state) + }, + { "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3" } + ) } fun logHandleClick(tileSpec: String, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - }, { - "[$str1][$int1] Tile handling click." - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + }, + { "[$str1][$int1] Tile handling click." } + ) } fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - str2 = StatusBarState.toString(statusBarState) - str3 = toStateString(state) - }, { - "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + str2 = StatusBarState.toString(statusBarState) + str3 = toStateString(state) + }, + { "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3" } + ) } fun logHandleSecondaryClick(tileSpec: String, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - }, { - "[$str1][$int1] Tile handling secondary click." - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + }, + { "[$str1][$int1] Tile handling secondary click." } + ) } fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - str2 = StatusBarState.toString(statusBarState) - str3 = toStateString(state) - }, { - "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + str2 = StatusBarState.toString(statusBarState) + str3 = toStateString(state) + }, + { "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3" } + ) } fun logHandleLongClick(tileSpec: String, eventId: Int) { - log(DEBUG, { - str1 = tileSpec - int1 = eventId - }, { - "[$str1][$int1] Tile handling long click." - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileSpec + int1 = eventId + }, + { "[$str1][$int1] Tile handling long click." } + ) } fun logInternetTileUpdate(tileSpec: String, lastType: Int, callback: String) { - log(VERBOSE, { - str1 = tileSpec - int1 = lastType - str2 = callback - }, { - "[$str1] mLastTileState=$int1, Callback=$str2." - }) + buffer.log( + TAG, + VERBOSE, + { + str1 = tileSpec + int1 = lastType + str2 = callback + }, + { "[$str1] mLastTileState=$int1, Callback=$str2." } + ) } // TODO(b/250618218): Remove this method once we know the root cause of b/250618218. @@ -167,58 +193,75 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : if (tileSpec != "internet") { return } - log(VERBOSE, { - str1 = tileSpec - int1 = state - bool1 = disabledByPolicy - int2 = color - }, { - "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2." - }) + buffer.log( + TAG, + VERBOSE, + { + str1 = tileSpec + int1 = state + bool1 = disabledByPolicy + int2 = color + }, + { "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2." } + ) } fun logTileUpdated(tileSpec: String, state: QSTile.State) { - log(VERBOSE, { - str1 = tileSpec - str2 = state.label?.toString() - str3 = state.icon?.toString() - int1 = state.state - if (state is QSTile.SignalState) { - bool1 = true - bool2 = state.activityIn - bool3 = state.activityOut + buffer.log( + TAG, + VERBOSE, + { + str1 = tileSpec + str2 = state.label?.toString() + str3 = state.icon?.toString() + int1 = state.state + if (state is QSTile.SignalState) { + bool1 = true + bool2 = state.activityIn + bool3 = state.activityOut + } + }, + { + "[$str1] Tile updated. Label=$str2. State=$int1. Icon=$str3." + + if (bool1) " Activity in/out=$bool2/$bool3" else "" } - }, { - "[$str1] Tile updated. Label=$str2. State=$int1. Icon=$str3." + - if (bool1) " Activity in/out=$bool2/$bool3" else "" - }) + ) } fun logPanelExpanded(expanded: Boolean, containerName: String) { - log(DEBUG, { - str1 = containerName - bool1 = expanded - }, { - "$str1 expanded=$bool1" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + bool1 = expanded + }, + { "$str1 expanded=$bool1" } + ) } fun logOnViewAttached(orientation: Int, containerName: String) { - log(DEBUG, { - str1 = containerName - int1 = orientation - }, { - "onViewAttached: $str1 orientation $int1" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + int1 = orientation + }, + { "onViewAttached: $str1 orientation $int1" } + ) } fun logOnViewDetached(orientation: Int, containerName: String) { - log(DEBUG, { - str1 = containerName - int1 = orientation - }, { - "onViewDetached: $str1 orientation $int1" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + int1 = orientation + }, + { "onViewDetached: $str1 orientation $int1" } + ) } fun logOnConfigurationChanged( @@ -226,13 +269,16 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : newOrientation: Int, containerName: String ) { - log(DEBUG, { - str1 = containerName - int1 = lastOrientation - int2 = newOrientation - }, { - "configuration change: $str1 orientation was $int1, now $int2" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + int1 = lastOrientation + int2 = newOrientation + }, + { "configuration change: $str1 orientation was $int1, now $int2" } + ) } fun logSwitchTileLayout( @@ -241,32 +287,41 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : force: Boolean, containerName: String ) { - log(DEBUG, { - str1 = containerName - bool1 = after - bool2 = before - bool3 = force - }, { - "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = containerName + bool1 = after + bool2 = before + bool3 = force + }, + { "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3" } + ) } fun logTileDistributionInProgress(tilesPerPageCount: Int, totalTilesCount: Int) { - log(DEBUG, { - int1 = tilesPerPageCount - int2 = totalTilesCount - }, { - "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]" - }) + buffer.log( + TAG, + DEBUG, + { + int1 = tilesPerPageCount + int2 = totalTilesCount + }, + { "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]" } + ) } fun logTileDistributed(tileName: String, pageIndex: Int) { - log(DEBUG, { - str1 = tileName - int1 = pageIndex - }, { - "Adding $str1 to page number $int1" - }) + buffer.log( + TAG, + DEBUG, + { + str1 = tileName + int1 = pageIndex + }, + { "Adding $str1 to page number $int1" } + ) } private fun toStateString(state: Int): String { @@ -277,12 +332,4 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : else -> "wrong state" } } - - private inline fun log( - logLevel: LogLevel, - initializer: LogMessage.() -> Unit, - noinline printer: LogMessage.() -> String - ) { - buffer.log(TAG, logLevel, initializer, printer) - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index a92c7e3c8554..24a4f60b7c00 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -33,7 +33,6 @@ import com.android.systemui.qs.tiles.BatterySaverTile; import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CameraToggleTile; import com.android.systemui.qs.tiles.CastTile; -import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorCorrectionTile; import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.DataSaverTile; @@ -54,7 +53,6 @@ import com.android.systemui.qs.tiles.ReduceBrightColorsTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.ScreenRecordTile; import com.android.systemui.qs.tiles.UiModeNightTile; -import com.android.systemui.qs.tiles.WifiTile; import com.android.systemui.qs.tiles.WorkModeTile; import com.android.systemui.util.leak.GarbageMonitor; @@ -68,10 +66,8 @@ public class QSFactoryImpl implements QSFactory { private static final String TAG = "QSFactory"; - private final Provider<WifiTile> mWifiTileProvider; private final Provider<InternetTile> mInternetTileProvider; private final Provider<BluetoothTile> mBluetoothTileProvider; - private final Provider<CellularTile> mCellularTileProvider; private final Provider<DndTile> mDndTileProvider; private final Provider<ColorCorrectionTile> mColorCorrectionTileProvider; private final Provider<ColorInversionTile> mColorInversionTileProvider; @@ -106,10 +102,8 @@ public class QSFactoryImpl implements QSFactory { public QSFactoryImpl( Lazy<QSHost> qsHostLazy, Provider<CustomTile.Builder> customTileBuilderProvider, - Provider<WifiTile> wifiTileProvider, Provider<InternetTile> internetTileProvider, Provider<BluetoothTile> bluetoothTileProvider, - Provider<CellularTile> cellularTileProvider, Provider<DndTile> dndTileProvider, Provider<ColorInversionTile> colorInversionTileProvider, Provider<AirplaneModeTile> airplaneModeTileProvider, @@ -139,10 +133,8 @@ public class QSFactoryImpl implements QSFactory { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; - mWifiTileProvider = wifiTileProvider; mInternetTileProvider = internetTileProvider; mBluetoothTileProvider = bluetoothTileProvider; - mCellularTileProvider = cellularTileProvider; mDndTileProvider = dndTileProvider; mColorInversionTileProvider = colorInversionTileProvider; mAirplaneModeTileProvider = airplaneModeTileProvider; @@ -186,14 +178,10 @@ public class QSFactoryImpl implements QSFactory { protected QSTileImpl createTileInternal(String tileSpec) { // Stock tiles. switch (tileSpec) { - case "wifi": - return mWifiTileProvider.get(); case "internet": return mInternetTileProvider.get(); case "bt": return mBluetoothTileProvider.get(); - case "cell": - return mCellularTileProvider.get(); case "dnd": return mDndTileProvider.get(); case "inversion": diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java deleted file mode 100644 index 04a25fc193b3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles; - -import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; - -import android.annotation.NonNull; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; -import android.service.quicksettings.Tile; -import android.telephony.SubscriptionManager; -import android.text.Html; -import android.text.TextUtils; -import android.view.View; -import android.view.WindowManager.LayoutParams; -import android.widget.Switch; - -import androidx.annotation.Nullable; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.net.DataUsageController; -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile.SignalState; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.SignalTileView; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.statusbar.connectivity.IconState; -import com.android.systemui.statusbar.connectivity.MobileDataIndicators; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.SignalCallback; -import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import javax.inject.Inject; - -/** Quick settings tile: Cellular **/ -public class CellularTile extends QSTileImpl<SignalState> { - private static final String ENABLE_SETTINGS_DATA_PLAN = "enable.settings.data.plan"; - - private final NetworkController mController; - private final DataUsageController mDataController; - private final KeyguardStateController mKeyguard; - private final CellSignalCallback mSignalCallback = new CellSignalCallback(); - - @Inject - public CellularTile( - QSHost host, - @Background Looper backgroundLooper, - @Main Handler mainHandler, - FalsingManager falsingManager, - MetricsLogger metricsLogger, - StatusBarStateController statusBarStateController, - ActivityStarter activityStarter, - QSLogger qsLogger, - NetworkController networkController, - KeyguardStateController keyguardStateController - - ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, - statusBarStateController, activityStarter, qsLogger); - mController = networkController; - mKeyguard = keyguardStateController; - mDataController = mController.getMobileDataController(); - mController.observe(getLifecycle(), mSignalCallback); - } - - @Override - public SignalState newTileState() { - return new SignalState(); - } - - @Override - public QSIconView createTileView(Context context) { - return new SignalTileView(context); - } - - @Override - public Intent getLongClickIntent() { - if (getState().state == Tile.STATE_UNAVAILABLE) { - return new Intent(Settings.ACTION_WIRELESS_SETTINGS); - } - return getCellularSettingIntent(); - } - - @Override - protected void handleClick(@Nullable View view) { - if (getState().state == Tile.STATE_UNAVAILABLE) { - return; - } - if (mDataController.isMobileDataEnabled()) { - maybeShowDisableDialog(); - } else { - mDataController.setMobileDataEnabled(true); - } - } - - private void maybeShowDisableDialog() { - if (Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, false)) { - // Directly turn off mobile data if the user has seen the dialog before. - mDataController.setMobileDataEnabled(false); - return; - } - String carrierName = mController.getMobileDataNetworkName(); - boolean isInService = mController.isMobileDataNetworkInService(); - if (TextUtils.isEmpty(carrierName) || !isInService) { - carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); - } - AlertDialog dialog = new Builder(mContext) - .setTitle(R.string.mobile_data_disable_title) - .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName)) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton( - com.android.internal.R.string.alert_windows_notification_turn_off_action, - (d, w) -> { - mDataController.setMobileDataEnabled(false); - Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true); - }) - .create(); - dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(dialog, true); - SystemUIDialog.registerDismissListener(dialog); - SystemUIDialog.setWindowOnTop(dialog, mKeyguard.isShowing()); - dialog.show(); - } - - @Override - protected void handleSecondaryClick(@Nullable View view) { - handleLongClick(view); - } - - @Override - public CharSequence getTileLabel() { - return mContext.getString(R.string.quick_settings_cellular_detail_title); - } - - @Override - protected void handleUpdateState(SignalState state, Object arg) { - CallbackInfo cb = (CallbackInfo) arg; - if (cb == null) { - cb = mSignalCallback.mInfo; - } - - final Resources r = mContext.getResources(); - state.label = r.getString(R.string.mobile_data); - boolean mobileDataEnabled = mDataController.isMobileDataSupported() - && mDataController.isMobileDataEnabled(); - state.value = mobileDataEnabled; - state.activityIn = mobileDataEnabled && cb.activityIn; - state.activityOut = mobileDataEnabled && cb.activityOut; - state.expandedAccessibilityClassName = Switch.class.getName(); - if (cb.noSim) { - state.icon = ResourceIcon.get(R.drawable.ic_qs_no_sim); - } else { - state.icon = ResourceIcon.get(R.drawable.ic_swap_vert); - } - - if (cb.noSim) { - state.state = Tile.STATE_UNAVAILABLE; - state.secondaryLabel = r.getString(R.string.keyguard_missing_sim_message_short); - } else if (cb.airplaneModeEnabled) { - state.state = Tile.STATE_UNAVAILABLE; - state.secondaryLabel = r.getString(R.string.status_bar_airplane); - } else if (mobileDataEnabled) { - state.state = Tile.STATE_ACTIVE; - state.secondaryLabel = appendMobileDataType( - // Only show carrier name if there are more than 1 subscription - cb.multipleSubs ? cb.dataSubscriptionName : "", - getMobileDataContentName(cb)); - } else { - state.state = Tile.STATE_INACTIVE; - state.secondaryLabel = r.getString(R.string.cell_data_off); - } - - state.contentDescription = state.label; - if (state.state == Tile.STATE_INACTIVE) { - // This information is appended later by converting the Tile.STATE_INACTIVE state. - state.stateDescription = ""; - } else { - state.stateDescription = state.secondaryLabel; - } - } - - private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) { - if (TextUtils.isEmpty(dataType)) { - return Html.fromHtml(current.toString(), 0); - } - if (TextUtils.isEmpty(current)) { - return Html.fromHtml(dataType.toString(), 0); - } - String concat = mContext.getString(R.string.mobile_carrier_text_format, current, dataType); - return Html.fromHtml(concat, 0); - } - - private CharSequence getMobileDataContentName(CallbackInfo cb) { - if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) { - String roaming = mContext.getString(R.string.data_connection_roaming); - String dataDescription = cb.dataContentDescription.toString(); - return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription); - } - if (cb.roaming) { - return mContext.getString(R.string.data_connection_roaming); - } - return cb.dataContentDescription; - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_CELLULAR; - } - - @Override - public boolean isAvailable() { - return mController.hasMobileDataFeature() - && mHost.getUserContext().getUserId() == UserHandle.USER_SYSTEM; - } - - private static final class CallbackInfo { - boolean airplaneModeEnabled; - @Nullable - CharSequence dataSubscriptionName; - @Nullable - CharSequence dataContentDescription; - boolean activityIn; - boolean activityOut; - boolean noSim; - boolean roaming; - boolean multipleSubs; - } - - private final class CellSignalCallback implements SignalCallback { - private final CallbackInfo mInfo = new CallbackInfo(); - - @Override - public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) { - if (indicators.qsIcon == null) { - // Not data sim, don't display. - return; - } - mInfo.dataSubscriptionName = mController.getMobileDataNetworkName(); - mInfo.dataContentDescription = indicators.qsDescription != null - ? indicators.typeContentDescriptionHtml : null; - mInfo.activityIn = indicators.activityIn; - mInfo.activityOut = indicators.activityOut; - mInfo.roaming = indicators.roaming; - mInfo.multipleSubs = mController.getNumberSubscriptions() > 1; - refreshState(mInfo); - } - - @Override - public void setNoSims(boolean show, boolean simDetected) { - mInfo.noSim = show; - refreshState(mInfo); - } - - @Override - public void setIsAirplaneMode(@NonNull IconState icon) { - mInfo.airplaneModeEnabled = icon.visible; - refreshState(mInfo); - } - } - - static Intent getCellularSettingIntent() { - Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS); - int dataSub = SubscriptionManager.getDefaultDataSubscriptionId(); - if (dataSub != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - intent.putExtra(Settings.EXTRA_SUB_ID, - SubscriptionManager.getDefaultDataSubscriptionId()); - } - return intent; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index 57a00c9a1620..b6b657ec82f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -204,15 +204,6 @@ public class UserDetailView extends PseudoGridView { Trace.endSection(); } - @Override - public void onUserListItemClicked(@NonNull UserRecord record, - @Nullable UserSwitchDialogController.DialogShower dialogShower) { - if (dialogShower != null) { - mDialogShower.dismiss(); - } - super.onUserListItemClicked(record, dialogShower); - } - public void linkToViewGroup(ViewGroup viewGroup) { PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java deleted file mode 100644 index b2be56cca51e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles; - -import android.annotation.NonNull; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; -import android.service.quicksettings.Tile; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.widget.Switch; - -import androidx.annotation.Nullable; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.qs.QSIconView; -import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.plugins.qs.QSTile.SignalState; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.AlphaControlledSignalTileView; -import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.tileimpl.QSIconViewImpl; -import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.statusbar.connectivity.AccessPointController; -import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.connectivity.SignalCallback; -import com.android.systemui.statusbar.connectivity.WifiIcons; -import com.android.systemui.statusbar.connectivity.WifiIndicators; - -import javax.inject.Inject; - -/** Quick settings tile: Wifi **/ -public class WifiTile extends QSTileImpl<SignalState> { - private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS); - - protected final NetworkController mController; - private final AccessPointController mWifiController; - private final QSTile.SignalState mStateBeforeClick = newTileState(); - - protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback(); - private boolean mExpectDisabled; - - @Inject - public WifiTile( - QSHost host, - @Background Looper backgroundLooper, - @Main Handler mainHandler, - FalsingManager falsingManager, - MetricsLogger metricsLogger, - StatusBarStateController statusBarStateController, - ActivityStarter activityStarter, - QSLogger qsLogger, - NetworkController networkController, - AccessPointController accessPointController - ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, - statusBarStateController, activityStarter, qsLogger); - mController = networkController; - mWifiController = accessPointController; - mController.observe(getLifecycle(), mSignalCallback); - mStateBeforeClick.spec = "wifi"; - } - - @Override - public SignalState newTileState() { - return new SignalState(); - } - - @Override - public QSIconView createTileView(Context context) { - return new AlphaControlledSignalTileView(context); - } - - @Override - public Intent getLongClickIntent() { - return WIFI_SETTINGS; - } - - @Override - protected void handleClick(@Nullable View view) { - // Secondary clicks are header clicks, just toggle. - mState.copyTo(mStateBeforeClick); - boolean wifiEnabled = mState.value; - // Immediately enter transient state when turning on wifi. - refreshState(wifiEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING); - mController.setWifiEnabled(!wifiEnabled); - mExpectDisabled = wifiEnabled; - if (mExpectDisabled) { - mHandler.postDelayed(() -> { - if (mExpectDisabled) { - mExpectDisabled = false; - refreshState(); - } - }, QSIconViewImpl.QS_ANIM_LENGTH); - } - } - - @Override - protected void handleSecondaryClick(@Nullable View view) { - if (!mWifiController.canConfigWifi()) { - mActivityStarter.postStartActivityDismissingKeyguard( - new Intent(Settings.ACTION_WIFI_SETTINGS), 0); - return; - } - if (!mState.value) { - mController.setWifiEnabled(true); - } - } - - @Override - public CharSequence getTileLabel() { - return mContext.getString(R.string.quick_settings_wifi_label); - } - - @Override - protected void handleUpdateState(SignalState state, Object arg) { - if (DEBUG) Log.d(TAG, "handleUpdateState arg=" + arg); - final CallbackInfo cb = mSignalCallback.mInfo; - if (mExpectDisabled) { - if (cb.enabled) { - return; // Ignore updates until disabled event occurs. - } else { - mExpectDisabled = false; - } - } - boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING; - boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) - && (cb.ssid != null || cb.wifiSignalIconId != WifiIcons.QS_WIFI_NO_NETWORK); - boolean wifiNotConnected = (cb.ssid == null) - && (cb.wifiSignalIconId == WifiIcons.QS_WIFI_NO_NETWORK); - if (state.slash == null) { - state.slash = new SlashState(); - state.slash.rotation = 6; - } - state.slash.isSlashed = false; - boolean isTransient = transientEnabling || cb.isTransient; - state.secondaryLabel = getSecondaryLabel(isTransient, cb.statusLabel); - state.state = Tile.STATE_ACTIVE; - state.dualTarget = true; - state.value = transientEnabling || cb.enabled; - state.activityIn = cb.enabled && cb.activityIn; - state.activityOut = cb.enabled && cb.activityOut; - final StringBuffer minimalContentDescription = new StringBuffer(); - final StringBuffer minimalStateDescription = new StringBuffer(); - final Resources r = mContext.getResources(); - if (isTransient) { - state.icon = ResourceIcon.get( - com.android.internal.R.drawable.ic_signal_wifi_transient_animation); - state.label = r.getString(R.string.quick_settings_wifi_label); - } else if (!state.value) { - state.slash.isSlashed = true; - state.state = Tile.STATE_INACTIVE; - state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_DISABLED); - state.label = r.getString(R.string.quick_settings_wifi_label); - } else if (wifiConnected) { - state.icon = ResourceIcon.get(cb.wifiSignalIconId); - state.label = cb.ssid != null ? removeDoubleQuotes(cb.ssid) : getTileLabel(); - } else if (wifiNotConnected) { - state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK); - state.label = r.getString(R.string.quick_settings_wifi_label); - } else { - state.icon = ResourceIcon.get(WifiIcons.QS_WIFI_NO_NETWORK); - state.label = r.getString(R.string.quick_settings_wifi_label); - } - minimalContentDescription.append( - mContext.getString(R.string.quick_settings_wifi_label)).append(","); - if (state.value) { - if (wifiConnected) { - minimalStateDescription.append(cb.wifiSignalContentDescription); - minimalContentDescription.append(removeDoubleQuotes(cb.ssid)); - if (!TextUtils.isEmpty(state.secondaryLabel)) { - minimalContentDescription.append(",").append(state.secondaryLabel); - } - } - } - state.stateDescription = minimalStateDescription.toString(); - state.contentDescription = minimalContentDescription.toString(); - state.dualLabelContentDescription = r.getString( - R.string.accessibility_quick_settings_open_settings, getTileLabel()); - state.expandedAccessibilityClassName = Switch.class.getName(); - } - - private CharSequence getSecondaryLabel(boolean isTransient, String statusLabel) { - return isTransient - ? mContext.getString(R.string.quick_settings_wifi_secondary_label_transient) - : statusLabel; - } - - @Override - public int getMetricsCategory() { - return MetricsEvent.QS_WIFI; - } - - @Override - public boolean isAvailable() { - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); - } - - @Nullable - private static String removeDoubleQuotes(String string) { - if (string == null) return null; - final int length = string.length(); - if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) { - return string.substring(1, length - 1); - } - return string; - } - - protected static final class CallbackInfo { - boolean enabled; - boolean connected; - int wifiSignalIconId; - @Nullable - String ssid; - boolean activityIn; - boolean activityOut; - @Nullable - String wifiSignalContentDescription; - boolean isTransient; - @Nullable - public String statusLabel; - - @Override - public String toString() { - return new StringBuilder("CallbackInfo[") - .append("enabled=").append(enabled) - .append(",connected=").append(connected) - .append(",wifiSignalIconId=").append(wifiSignalIconId) - .append(",ssid=").append(ssid) - .append(",activityIn=").append(activityIn) - .append(",activityOut=").append(activityOut) - .append(",wifiSignalContentDescription=").append(wifiSignalContentDescription) - .append(",isTransient=").append(isTransient) - .append(']').toString(); - } - } - - protected final class WifiSignalCallback implements SignalCallback { - final CallbackInfo mInfo = new CallbackInfo(); - - @Override - public void setWifiIndicators(@NonNull WifiIndicators indicators) { - if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + indicators.enabled); - if (indicators.qsIcon == null) { - return; - } - mInfo.enabled = indicators.enabled; - mInfo.connected = indicators.qsIcon.visible; - mInfo.wifiSignalIconId = indicators.qsIcon.icon; - mInfo.ssid = indicators.description; - mInfo.activityIn = indicators.activityIn; - mInfo.activityOut = indicators.activityOut; - mInfo.wifiSignalContentDescription = indicators.qsIcon.contentDescription; - mInfo.isTransient = indicators.isTransient; - mInfo.statusLabel = indicators.statusLabel; - refreshState(); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index a6c7781d891c..72c6bfe371ce 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -101,7 +101,6 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements @MainThread public void onManagedProfileRemoved() { mHost.removeTile(getTileSpec()); - mHost.unmarkTileAsAutoAdded(getTileSpec()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt index 314252bf310b..4c9c99cc16f0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt @@ -36,6 +36,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.QSUserSwitcherEvent import com.android.systemui.qs.tiles.UserDetailView import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.user.ui.dialog.DialogShowerImpl import javax.inject.Inject import javax.inject.Provider @@ -130,19 +131,6 @@ class UserSwitchDialogController @VisibleForTesting constructor( } } - private class DialogShowerImpl( - private val animateFrom: Dialog, - private val dialogLaunchAnimator: DialogLaunchAnimator - ) : DialogInterface by animateFrom, DialogShower { - override fun showDialog(dialog: Dialog, cuj: DialogCuj) { - dialogLaunchAnimator.showFromDialog( - dialog, - animateFrom = animateFrom, - cuj - ) - } - } - interface DialogShower : DialogInterface { fun showDialog(dialog: Dialog, cuj: DialogCuj) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index b4934cf7b804..bf5fbd223186 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -20,8 +20,7 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE; import static com.android.systemui.screenshot.LogConfig.logTag; -import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION; -import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS; +import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType; import android.app.ActivityTaskManager; import android.app.Notification; @@ -155,7 +154,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { CompletableFuture<List<Notification.Action>> smartActionsFuture = mScreenshotSmartActions.getSmartActionsFuture( - mScreenshotId, uri, image, mSmartActionsProvider, REGULAR_SMART_ACTIONS, + mScreenshotId, uri, image, mSmartActionsProvider, + ScreenshotSmartActionType.REGULAR_SMART_ACTIONS, smartActionsEnabled, user); List<Notification.Action> smartActions = new ArrayList<>(); if (smartActionsEnabled) { @@ -166,7 +166,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { smartActions.addAll(buildSmartActions( mScreenshotSmartActions.getSmartActions( mScreenshotId, smartActionsFuture, timeoutMs, - mSmartActionsProvider, REGULAR_SMART_ACTIONS), + mSmartActionsProvider, + ScreenshotSmartActionType.REGULAR_SMART_ACTIONS), mContext)); } @@ -476,7 +477,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { CompletableFuture<List<Notification.Action>> quickShareActionsFuture = mScreenshotSmartActions.getSmartActionsFuture( mScreenshotId, null, image, mSmartActionsProvider, - QUICK_SHARE_ACTION, + ScreenshotSmartActionType.QUICK_SHARE_ACTION, true /* smartActionsEnabled */, user); int timeoutMs = DeviceConfig.getInt( DeviceConfig.NAMESPACE_SYSTEMUI, @@ -485,7 +486,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { List<Notification.Action> quickShareActions = mScreenshotSmartActions.getSmartActions( mScreenshotId, quickShareActionsFuture, timeoutMs, - mSmartActionsProvider, QUICK_SHARE_ACTION); + mSmartActionsProvider, + ScreenshotSmartActionType.QUICK_SHARE_ACTION); if (!quickShareActions.isEmpty()) { mQuickShareData.quickShareAction = quickShareActions.get(0); mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 5716a1d7260c..b21a4857c736 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -280,6 +280,7 @@ public class ScreenshotController { private final TimeoutHandler mScreenshotHandler; private final ActionIntentExecutor mActionExecutor; private final UserManager mUserManager; + private final WorkProfileMessageController mWorkProfileMessageController; private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { if (DEBUG_INPUT) { @@ -326,7 +327,8 @@ public class ScreenshotController { BroadcastSender broadcastSender, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, ActionIntentExecutor actionExecutor, - UserManager userManager + UserManager userManager, + WorkProfileMessageController workProfileMessageController ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; @@ -358,6 +360,7 @@ public class ScreenshotController { mFlags = flags; mActionExecutor = actionExecutor; mUserManager = userManager; + mWorkProfileMessageController = workProfileMessageController; mAccessibilityManager = AccessibilityManager.getInstance(mContext); @@ -683,10 +686,9 @@ public class ScreenshotController { return true; } }); - if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { - mScreenshotView.badgeScreenshot( - mContext.getPackageManager().getUserBadgeForDensity(owner, 0)); + mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( + mContext.getDrawable(R.drawable.overlay_badge_background), owner)); } mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); if (DEBUG_WINDOW) { @@ -784,9 +786,9 @@ public class ScreenshotController { mLongScreenshotHolder.setLongScreenshot(longScreenshot); mLongScreenshotHolder.setTransitionDestinationCallback( (transitionDestination, onTransitionEnd) -> { - mScreenshotView.startLongScreenshotTransition( - transitionDestination, onTransitionEnd, - longScreenshot); + mScreenshotView.startLongScreenshotTransition( + transitionDestination, onTransitionEnd, + longScreenshot); // TODO: Do this via ActionIntentExecutor instead. mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); } @@ -1037,10 +1039,8 @@ public class ScreenshotController { private void doPostAnimation(ScreenshotController.SavedImageData imageData) { mScreenshotView.setChipIntents(imageData); - if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY) - && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) { - // TODO: Read app from configuration - mScreenshotView.showWorkProfileMessage("Files"); + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { + mWorkProfileMessageController.onScreenshotTaken(imageData.owner, mScreenshotView); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index e8ceb521b6b0..200a7dc08bb3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -33,6 +33,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; @@ -100,7 +101,8 @@ import java.util.ArrayList; * Handles the visual elements and animations for the screenshot flow. */ public class ScreenshotView extends FrameLayout implements - ViewTreeObserver.OnComputeInternalInsetsListener { + ViewTreeObserver.OnComputeInternalInsetsListener, + WorkProfileMessageController.WorkProfileMessageDisplay { interface ScreenshotViewCallback { void onUserInteraction(); @@ -351,13 +353,23 @@ public class ScreenshotView extends FrameLayout implements * been taken and which app can be used to view it. * * @param appName The name of the app to use to view screenshots + * @param appIcon Optional icon for the relevant files app + * @param onDismiss Runnable to be run when the user dismisses this message */ - void showWorkProfileMessage(String appName) { + @Override + public void showWorkProfileMessage(CharSequence appName, @Nullable Drawable appIcon, + Runnable onDismiss) { + if (appIcon != null) { + // Replace the default icon if one is provided. + ImageView imageView = mMessageContainer.findViewById(R.id.screenshot_message_icon); + imageView.setImageDrawable(appIcon); + } mMessageContent.setText( mContext.getString(R.string.screenshot_work_profile_notification, appName)); mMessageContainer.setVisibility(VISIBLE); mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> { mMessageContainer.setVisibility(View.GONE); + onDismiss.run(); }); } @@ -1078,7 +1090,7 @@ public class ScreenshotView extends FrameLayout implements mScreenshotBadge.setVisibility(View.GONE); mScreenshotBadge.setImageDrawable(null); mPendingSharedTransition = false; - mActionsContainerBackground.setVisibility(View.GONE); + mActionsContainerBackground.setVisibility(View.INVISIBLE); mActionsContainer.setVisibility(View.GONE); mDismissButton.setVisibility(View.GONE); mScrollingScrim.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 2176825d8b38..35e9f3e56723 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -21,7 +21,6 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE; import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI; -import static com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR; import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY; import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; @@ -122,7 +121,6 @@ public class TakeScreenshotService extends Service { mContext = context; mBgExecutor = bgExecutor; mFeatureFlags = featureFlags; - mFeatureFlags.addListener(SCREENSHOT_REQUEST_PROCESSOR, FlagEvent::requestNoRestart); mFeatureFlags.addListener(SCREENSHOT_WORK_PROFILE_POLICY, FlagEvent::requestNoRestart); mProcessor = processor; } @@ -224,14 +222,8 @@ public class TakeScreenshotService extends Service { return; } - if (mFeatureFlags.isEnabled(SCREENSHOT_REQUEST_PROCESSOR)) { - Log.d(TAG, "handleMessage: Using request processor"); - mProcessor.processAsync(request, - (r) -> dispatchToController(r, onSaved, callback)); - return; - } - - dispatchToController(request, onSaved, callback); + mProcessor.processAsync(request, + (r) -> dispatchToController(r, onSaved, callback)); } private void dispatchToController(ScreenshotHelper.ScreenshotRequest request, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt new file mode 100644 index 000000000000..5d7e56f6c98a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.UserHandle +import android.os.UserManager +import android.util.Log +import com.android.systemui.R +import javax.inject.Inject + +/** + * Handles all the non-UI portions of the work profile first run: + * - Track whether the user has already dismissed it. + * - Load the proper icon and app name. + */ +class WorkProfileMessageController +@Inject +constructor( + private val context: Context, + private val userManager: UserManager, + private val packageManager: PackageManager, +) { + + /** + * Determine if a message should be shown to the user, send message details to messageDisplay if + * appropriate. + */ + fun onScreenshotTaken(userHandle: UserHandle, messageDisplay: WorkProfileMessageDisplay) { + if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) { + var badgedIcon: Drawable? = null + var label: CharSequence? = null + val fileManager = fileManagerComponentName() + try { + val info = + packageManager.getActivityInfo( + fileManager, + PackageManager.ComponentInfoFlags.of(0) + ) + val icon = packageManager.getActivityIcon(fileManager) + badgedIcon = packageManager.getUserBadgedIcon(icon, userHandle) + label = info.loadLabel(packageManager) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, "Component $fileManager not found") + } + + // If label wasn't loaded, use a default + val badgedLabel = + packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle) + + messageDisplay.showWorkProfileMessage(badgedLabel, badgedIcon) { onMessageDismissed() } + } + } + + private fun messageAlreadyDismissed(): Boolean { + return sharedPreference().getBoolean(PREFERENCE_KEY, false) + } + + private fun onMessageDismissed() { + val editor = sharedPreference().edit() + editor.putBoolean(PREFERENCE_KEY, true) + editor.apply() + } + + private fun sharedPreference() = + context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE) + + private fun fileManagerComponentName() = + ComponentName.unflattenFromString( + context.getString(R.string.config_sceenshotWorkProfileFilesApp) + ) + + private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name) + + /** UI that can show work profile messages (ScreenshotView in practice) */ + interface WorkProfileMessageDisplay { + /** + * Show the given message and icon, calling onDismiss if the user explicitly dismisses the + * message. + */ + fun showWorkProfileMessage(text: CharSequence, icon: Drawable?, onDismiss: Runnable) + } + + companion object { + const val TAG = "WorkProfileMessageCtrl" + const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot" + const val PREFERENCE_KEY = "work_profile_first_run" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 28da38b701bc..61390c582fd6 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -112,7 +112,7 @@ class UserTrackerImpl internal constructor( // These get called when a managed profile goes in or out of quiet mode. addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) - + addAction(Intent.ACTION_MANAGED_PROFILE_ADDED) addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED) addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED) } @@ -129,6 +129,7 @@ class UserTrackerImpl internal constructor( Intent.ACTION_USER_INFO_CHANGED, Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, + Intent.ACTION_MANAGED_PROFILE_ADDED, Intent.ACTION_MANAGED_PROFILE_REMOVED, Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> { handleProfilesChanged() diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt index 7fc0a5f6d4bf..e406be1ea0a3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt @@ -175,9 +175,10 @@ class LargeScreenShadeHeaderController @Inject constructor( */ var shadeExpandedFraction = -1f set(value) { - if (visible && field != value) { + if (field != value) { header.alpha = ShadeInterpolation.getContentAlpha(value) field = value + updateVisibility() } } @@ -331,6 +332,9 @@ class LargeScreenShadeHeaderController @Inject constructor( .setDuration(duration) .alpha(if (show) 0f else 1f) .setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN) + .setUpdateListener { + updateVisibility() + } .start() } @@ -414,7 +418,7 @@ class LargeScreenShadeHeaderController @Inject constructor( private fun updateVisibility() { val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) { View.GONE - } else if (qsVisible) { + } else if (qsVisible && header.alpha > 0f) { View.VISIBLE } else { View.INVISIBLE diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8f512d0205b8..ccffc5f14559 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -138,12 +138,15 @@ import com.android.systemui.flags.Flags; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.KeyguardMediaController; @@ -360,6 +363,7 @@ public final class NotificationPanelViewController implements Dumpable { private final FragmentListener mQsFragmentListener = new QsFragmentListener(); private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate(); private final NotificationGutsManager mGutsManager; + private final AlternateBouncerInteractor mAlternateBouncerInteractor; private long mDownTime; private boolean mTouchSlopExceededBeforeDown; @@ -687,12 +691,16 @@ public final class NotificationPanelViewController implements Dumpable { private boolean mExpandLatencyTracking; private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel; + private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel; + private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; private KeyguardTransitionInteractor mKeyguardTransitionInteractor; private CoroutineDispatcher mMainDispatcher; - private boolean mIsToLockscreenTransitionRunning = false; + private boolean mIsOcclusionTransitionRunning = false; private int mDreamingToLockscreenTransitionTranslationY; private int mOccludedToLockscreenTransitionTranslationY; + private int mLockscreenToDreamingTransitionTranslationY; + private int mLockscreenToOccludedTransitionTranslationY; private boolean mUnocclusionTransitionFlagEnabled = false; private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, @@ -711,13 +719,25 @@ public final class NotificationPanelViewController implements Dumpable { private final Consumer<TransitionStep> mDreamingToLockscreenTransition = (TransitionStep step) -> { - mIsToLockscreenTransitionRunning = + mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; private final Consumer<TransitionStep> mOccludedToLockscreenTransition = (TransitionStep step) -> { - mIsToLockscreenTransitionRunning = + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; + + private final Consumer<TransitionStep> mLockscreenToDreamingTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; + + private final Consumer<TransitionStep> mLockscreenToOccludedTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = step.getTransitionState() == TransitionState.RUNNING; }; @@ -789,8 +809,11 @@ public final class NotificationPanelViewController implements Dumpable { SystemClock systemClock, KeyguardBottomAreaViewModel keyguardBottomAreaViewModel, KeyguardBottomAreaInteractor keyguardBottomAreaInteractor, + AlternateBouncerInteractor alternateBouncerInteractor, DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel, OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel, + LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel, + LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel, @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, DumpManager dumpManager) { @@ -810,6 +833,8 @@ public final class NotificationPanelViewController implements Dumpable { mGutsManager = gutsManager; mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel; mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel; + mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel; + mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override @@ -980,6 +1005,7 @@ public final class NotificationPanelViewController implements Dumpable { unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay); } }); + mAlternateBouncerInteractor = alternateBouncerInteractor; dumpManager.registerDumpable(this); } @@ -1117,22 +1143,44 @@ public final class NotificationPanelViewController implements Dumpable { collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(), mDreamingToLockscreenTransition, mMainDispatcher); collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(), - toLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY( mDreamingToLockscreenTransitionTranslationY), - toLockscreenTransitionY(mNotificationStackScrollLayoutController), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); // Occluded->Lockscreen collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(), mOccludedToLockscreenTransition, mMainDispatcher); collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), - toLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); collectFlow(mView, mOccludedToLockscreenTransitionViewModel.lockscreenTranslationY( mOccludedToLockscreenTransitionTranslationY), - toLockscreenTransitionY(mNotificationStackScrollLayoutController), + setTransitionY(mNotificationStackScrollLayoutController), + mMainDispatcher); + + // Lockscreen->Dreaming + collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(), + mLockscreenToDreamingTransition, mMainDispatcher); + collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), + mMainDispatcher); + collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY( + mLockscreenToDreamingTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), + mMainDispatcher); + + // Lockscreen->Occluded + collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(), + mLockscreenToOccludedTransition, mMainDispatcher); + collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), + mMainDispatcher); + collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY( + mLockscreenToOccludedTransitionTranslationY), + setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); } } @@ -1173,6 +1221,10 @@ public final class NotificationPanelViewController implements Dumpable { R.dimen.dreaming_to_lockscreen_transition_lockscreen_translation_y); mOccludedToLockscreenTransitionTranslationY = mResources.getDimensionPixelSize( R.dimen.occluded_to_lockscreen_transition_lockscreen_translation_y); + mLockscreenToDreamingTransitionTranslationY = mResources.getDimensionPixelSize( + R.dimen.lockscreen_to_dreaming_transition_lockscreen_translation_y); + mLockscreenToOccludedTransitionTranslationY = mResources.getDimensionPixelSize( + R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y); } private void updateViewControllers(KeyguardStatusView keyguardStatusView, @@ -1836,7 +1888,7 @@ public final class NotificationPanelViewController implements Dumpable { } private void updateClock() { - if (mIsToLockscreenTransitionRunning) { + if (mIsOcclusionTransitionRunning) { return; } float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha; @@ -2288,7 +2340,7 @@ public final class NotificationPanelViewController implements Dumpable { // When false, down but not synthesized motion event. mLastEventSynthesizedDown = mExpectingSynthesizedDown; mLastDownEvents.insert( - mSystemClock.currentTimeMillis(), + event.getEventTime(), mDownX, mDownY, mQsTouchAboveFalsingThreshold, @@ -2727,7 +2779,7 @@ public final class NotificationPanelViewController implements Dumpable { } else if (statusBarState == KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) { mKeyguardBottomArea.setVisibility(View.VISIBLE); - if (!mIsToLockscreenTransitionRunning) { + if (!mIsOcclusionTransitionRunning) { mKeyguardBottomArea.setAlpha(1f); } } else { @@ -3596,7 +3648,7 @@ public final class NotificationPanelViewController implements Dumpable { } private void updateNotificationTranslucency() { - if (mIsToLockscreenTransitionRunning) { + if (mIsOcclusionTransitionRunning) { return; } float alpha = 1f; @@ -3654,7 +3706,7 @@ public final class NotificationPanelViewController implements Dumpable { } private void updateKeyguardBottomAreaAlpha() { - if (mIsToLockscreenTransitionRunning) { + if (mIsOcclusionTransitionRunning) { return; } // There are two possible panel expansion behaviors: @@ -4902,7 +4954,7 @@ public final class NotificationPanelViewController implements Dumpable { mUpdateFlingVelocity = vel; } } else if (!mCentralSurfaces.isBouncerShowing() - && !mStatusBarKeyguardViewManager.isShowingAlternateBouncer() + && !mAlternateBouncerInteractor.isVisibleState() && !mKeyguardStateController.isKeyguardGoingAway()) { onEmptySpaceClick(); onTrackingStopped(true); @@ -5886,7 +5938,7 @@ public final class NotificationPanelViewController implements Dumpable { mCurrentPanelState = state; } - private Consumer<Float> toLockscreenTransitionAlpha( + private Consumer<Float> setTransitionAlpha( NotificationStackScrollLayoutController stackScroller) { return (Float alpha) -> { mKeyguardStatusViewController.setAlpha(alpha); @@ -5904,7 +5956,7 @@ public final class NotificationPanelViewController implements Dumpable { }; } - private Consumer<Float> toLockscreenTransitionY( + private Consumer<Float> setTransitionY( NotificationStackScrollLayoutController stackScroller) { return (Float translationY) -> { mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 5c1ddd601f96..7ed6e3e55623 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.shade; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; + import android.app.StatusBarManager; import android.media.AudioManager; import android.media.session.MediaSessionLegacyHelper; @@ -39,6 +41,10 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.statusbar.DragDownHelper; @@ -57,6 +63,7 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import java.io.PrintWriter; +import java.util.function.Consumer; import javax.inject.Inject; @@ -79,6 +86,7 @@ public class NotificationShadeWindowViewController { private final AmbientState mAmbientState; private final PulsingGestureListener mPulsingGestureListener; private final NotificationInsetsController mNotificationInsetsController; + private final AlternateBouncerInteractor mAlternateBouncerInteractor; private GestureDetector mPulsingWakeupGestureHandler; private View mBrightnessMirror; @@ -96,6 +104,13 @@ public class NotificationShadeWindowViewController { private final ShadeExpansionStateManager mShadeExpansionStateManager; private boolean mIsTrackingBarGesture = false; + private boolean mIsOcclusionTransitionRunning = false; + + private final Consumer<TransitionStep> mLockscreenToDreamingTransition = + (TransitionStep step) -> { + mIsOcclusionTransitionRunning = + step.getTransitionState() == TransitionState.RUNNING; + }; @Inject public NotificationShadeWindowViewController( @@ -119,7 +134,9 @@ public class NotificationShadeWindowViewController { PulsingGestureListener pulsingGestureListener, FeatureFlags featureFlags, KeyguardBouncerViewModel keyguardBouncerViewModel, - KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory + KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, + AlternateBouncerInteractor alternateBouncerInteractor, + KeyguardTransitionInteractor keyguardTransitionInteractor ) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; @@ -139,6 +156,7 @@ public class NotificationShadeWindowViewController { mAmbientState = ambientState; mPulsingGestureListener = pulsingGestureListener; mNotificationInsetsController = notificationInsetsController; + mAlternateBouncerInteractor = alternateBouncerInteractor; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -148,6 +166,11 @@ public class NotificationShadeWindowViewController { keyguardBouncerViewModel, keyguardBouncerComponentFactory); } + + if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) { + collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), + mLockscreenToDreamingTransition); + } } /** @@ -215,6 +238,10 @@ public class NotificationShadeWindowViewController { return true; } + if (mIsOcclusionTransitionRunning) { + return false; + } + mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); @@ -292,7 +319,7 @@ public class NotificationShadeWindowViewController { return true; } - if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) { + if (mAlternateBouncerInteractor.isVisibleState()) { // capture all touches if the alt auth bouncer is showing return true; } @@ -330,7 +357,7 @@ public class NotificationShadeWindowViewController { handled = !mService.isPulsing(); } - if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) { + if (mAlternateBouncerInteractor.isVisibleState()) { // eat the touch handled = true; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 5fedbeb556c2..11617be40f53 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -36,16 +36,9 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { buffer.log(TAG, LogLevel.DEBUG, msg) } - private inline fun log( - logLevel: LogLevel, - initializer: LogMessage.() -> Unit, - noinline printer: LogMessage.() -> String - ) { - buffer.log(TAG, logLevel, initializer, printer) - } - fun onQsInterceptMoveQsTrackingEnabled(h: Float) { - log( + buffer.log( + TAG, LogLevel.VERBOSE, { double1 = h.toDouble() }, { "onQsIntercept: move action, QS tracking enabled. h = $double1" } @@ -62,7 +55,8 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { keyguardShowing: Boolean, qsExpansionEnabled: Boolean ) { - log( + buffer.log( + TAG, LogLevel.VERBOSE, { int1 = initialTouchY.toInt() @@ -82,7 +76,8 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { } fun logMotionEvent(event: MotionEvent, message: String) { - log( + buffer.log( + TAG, LogLevel.VERBOSE, { str1 = message @@ -99,7 +94,8 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { } fun logMotionEventStatusBarState(event: MotionEvent, statusBarState: Int, message: String) { - log( + buffer.log( + TAG, LogLevel.VERBOSE, { str1 = message @@ -128,25 +124,33 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { tracking: Boolean, dragDownPxAmount: Float, ) { - log(LogLevel.VERBOSE, { - str1 = message - double1 = fraction.toDouble() - bool1 = expanded - bool2 = tracking - long1 = dragDownPxAmount.toLong() - }, { - "$str1 fraction=$double1,expanded=$bool1," + + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = message + double1 = fraction.toDouble() + bool1 = expanded + bool2 = tracking + long1 = dragDownPxAmount.toLong() + }, + { + "$str1 fraction=$double1,expanded=$bool1," + "tracking=$bool2," + "dragDownPxAmount=$dragDownPxAmount" - }) + } + ) } fun logHasVibrated(hasVibratedOnOpen: Boolean, fraction: Float) { - log(LogLevel.VERBOSE, { - bool1 = hasVibratedOnOpen - double1 = fraction.toDouble() - }, { - "hasVibratedOnOpen=$bool1, expansionFraction=$double1" - }) + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = hasVibratedOnOpen + double1 = fraction.toDouble() + }, + { "hasVibratedOnOpen=$bool1, expansionFraction=$double1" } + ) } fun logQsExpansionChanged( @@ -159,42 +163,56 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { qsAnimatorExpand: Boolean, animatingQs: Boolean ) { - log(LogLevel.VERBOSE, { - str1 = message - bool1 = qsExpanded - int1 = qsMinExpansionHeight - int2 = qsMaxExpansionHeight - bool2 = stackScrollerOverscrolling - bool3 = dozing - bool4 = qsAnimatorExpand - // 0 = false, 1 = true - long1 = animatingQs.compareTo(false).toLong() - }, { - "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," + + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = message + bool1 = qsExpanded + int1 = qsMinExpansionHeight + int2 = qsMaxExpansionHeight + bool2 = stackScrollerOverscrolling + bool3 = dozing + bool4 = qsAnimatorExpand + // 0 = false, 1 = true + long1 = animatingQs.compareTo(false).toLong() + }, + { + "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," + "stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," + "animatingQs=$long1" - }) + } + ) } fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) { - log(LogLevel.DEBUG, { - bool1 = isDozing - bool2 = singleTapEnabled - bool3 = isNotDocked - }, { - "PulsingGestureListener#onSingleTapUp all of this must true for single " + - "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3" + buffer.log( + TAG, + LogLevel.DEBUG, + { + bool1 = isDozing + bool2 = singleTapEnabled + bool3 = isNotDocked + }, + { + "PulsingGestureListener#onSingleTapUp all of this must true for single " + + "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3" }) } fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) { - log(LogLevel.DEBUG, { - bool1 = proximityIsNotNear - bool2 = isNotFalseTap - }, { - "PulsingGestureListener#onSingleTapUp all of this must true for single " + + buffer.log( + TAG, + LogLevel.DEBUG, + { + bool1 = proximityIsNotNear + bool2 = isNotFalseTap + }, + { + "PulsingGestureListener#onSingleTapUp all of this must true for single " + "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2" - }) + } + ) } fun logNotInterceptingTouchInstantExpanding( @@ -202,13 +220,18 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { notificationsDragEnabled: Boolean, touchDisabled: Boolean ) { - log(LogLevel.VERBOSE, { - bool1 = instantExpanding - bool2 = notificationsDragEnabled - bool3 = touchDisabled - }, { - "NPVC not intercepting touch, instantExpanding: $bool1, " + + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = instantExpanding + bool2 = notificationsDragEnabled + bool3 = touchDisabled + }, + { + "NPVC not intercepting touch, instantExpanding: $bool1, " + "!notificationsDragEnabled: $bool2, touchDisabled: $bool3" - }) + } + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt index c6a6e875b82d..9851625b6152 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt @@ -32,11 +32,21 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { fun logApplyingWindowLayoutParams(lp: WindowManager.LayoutParams) { - log(DEBUG, { str1 = lp.toString() }, { "Applying new window layout params: $str1" }) + buffer.log( + TAG, + DEBUG, + { str1 = lp.toString() }, + { "Applying new window layout params: $str1" } + ) } fun logNewState(state: Any) { - log(DEBUG, { str1 = state.toString() }, { "Applying new state: $str1" }) + buffer.log( + TAG, + DEBUG, + { str1 = state.toString() }, + { "Applying new state: $str1" } + ) } private inline fun log( @@ -48,11 +58,16 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: } fun logApplyVisibility(visible: Boolean) { - log(DEBUG, { bool1 = visible }, { "Updating visibility, should be visible : $bool1" }) + buffer.log( + TAG, + DEBUG, + { bool1 = visible }, + { "Updating visibility, should be visible : $bool1" }) } fun logShadeVisibleAndFocusable(visible: Boolean) { - log( + buffer.log( + TAG, DEBUG, { bool1 = visible }, { "Updating shade, should be visible and focusable: $bool1" } @@ -60,6 +75,11 @@ class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: } fun logShadeFocusable(focusable: Boolean) { - log(DEBUG, { bool1 = focusable }, { "Updating shade, should be focusable : $bool1" }) + buffer.log( + TAG, + DEBUG, + { bool1 = focusable }, + { "Updating shade, should be focusable : $bool1" } + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 750d00466a8d..584a382184f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -334,7 +334,7 @@ public class CommandQueue extends IStatusBar.Stub implements /** * @see IStatusBar#setBiometicContextListener(IBiometricContextListener) */ - default void setBiometicContextListener(IBiometricContextListener listener) { + default void setBiometricContextListener(IBiometricContextListener listener) { } /** @@ -1583,7 +1583,7 @@ public class CommandQueue extends IStatusBar.Stub implements } case MSG_SET_BIOMETRICS_LISTENER: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).setBiometicContextListener( + mCallbacks.get(i).setBiometricContextListener( (IBiometricContextListener) msg.obj); } break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 6a658b6ee047..006b5528e7e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -41,6 +41,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont 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.plugins.FalsingManager.LOW_PENALTY; +import static com.android.systemui.plugins.log.LogLevel.ERROR; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -90,6 +91,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController; import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -155,6 +157,7 @@ public class KeyguardIndicationController { private final KeyguardBypassController mKeyguardBypassController; private final AccessibilityManager mAccessibilityManager; private final Handler mHandler; + private final AlternateBouncerInteractor mAlternateBouncerInteractor; @VisibleForTesting public KeyguardIndicationRotateTextViewController mRotateTextViewController; @@ -234,7 +237,8 @@ public class KeyguardIndicationController { KeyguardBypassController keyguardBypassController, AccessibilityManager accessibilityManager, FaceHelpMessageDeferral faceHelpMessageDeferral, - KeyguardLogger keyguardLogger) { + KeyguardLogger keyguardLogger, + AlternateBouncerInteractor alternateBouncerInteractor) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mDevicePolicyManager = devicePolicyManager; @@ -256,6 +260,7 @@ public class KeyguardIndicationController { mScreenLifecycle = screenLifecycle; mKeyguardLogger = keyguardLogger; mScreenLifecycle.addObserver(mScreenObserver); + mAlternateBouncerInteractor = alternateBouncerInteractor; mFaceAcquiredMessageDeferral = faceHelpMessageDeferral; mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>(); @@ -928,7 +933,7 @@ public class KeyguardIndicationController { } if (mStatusBarKeyguardViewManager.isBouncerShowing()) { - if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) { + if (mAlternateBouncerInteractor.isVisibleState()) { return; // udfps affordance is highlighted, no need to show action to unlock } else if (mKeyguardUpdateMonitor.isFaceEnrolled() && !mKeyguardUpdateMonitor.getIsFaceAuthenticated()) { @@ -1028,7 +1033,7 @@ public class KeyguardIndicationController { mChargingTimeRemaining = mPowerPluggedIn ? mBatteryInfo.computeChargeTimeRemaining() : -1; } catch (RemoteException e) { - mKeyguardLogger.logException(e, "Error calling IBatteryStats"); + mKeyguardLogger.log(TAG, ERROR, "Error calling IBatteryStats", e); mChargingTimeRemaining = -1; } updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 905cc3fc71e0..f565f3daf2ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -509,9 +509,14 @@ class LockscreenShadeTransitionController @Inject constructor( * If secure with redaction: Show bouncer, go to unlocked shade. * If secure without redaction or no security: Go to [StatusBarState.SHADE_LOCKED]. * + * Split shade is special case and [needsQSAnimation] will be always overridden to true. + * That's because handheld shade will automatically follow notifications animation, but that's + * not the case for split shade. + * * @param expandView The view to expand after going to the shade * @param needsQSAnimation if this needs the quick settings to slide in from the top or if - * that's already handled separately + * that's already handled separately. This argument will be ignored on + * split shade as there QS animation can't be handled separately. */ @JvmOverloads fun goToLockedShade(expandedView: View?, needsQSAnimation: Boolean = true) { @@ -519,7 +524,7 @@ class LockscreenShadeTransitionController @Inject constructor( logger.logTryGoToLockedShade(isKeyguard) if (isKeyguard) { val animationHandler: ((Long) -> Unit)? - if (needsQSAnimation) { + if (needsQSAnimation || useSplitShade) { // Let's use the default animation animationHandler = null } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 56b689efaa79..7d0ac1874056 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -290,7 +290,8 @@ public class NotificationListener extends NotificationListenerWithPlugins implem false, null, 0, - false + false, + 0 ); } return ranking; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 8f9365cd4dc4..99081e98c4a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -65,8 +65,6 @@ import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.ListenerSet; -import dagger.Lazy; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -74,6 +72,8 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; +import dagger.Lazy; + /** * Class for handling remote input state over a set of notifications. This class handles things * like keeping notifications temporarily that were cancelled as a response to a remote input @@ -465,8 +465,7 @@ public class NotificationRemoteInputManager implements Dumpable { riv.getController().setRemoteInput(input); riv.getController().setRemoteInputs(inputs); riv.getController().setEditedSuggestionInfo(editedSuggestionInfo); - ViewGroup parent = view.getParent() != null ? (ViewGroup) view.getParent() : null; - riv.focusAnimated(parent); + riv.focusAnimated(); if (userMessageContent != null) { riv.setEditTextContent(userMessageContent); } 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 14d0d7e032d5..9a65e342478e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -31,6 +31,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -61,7 +62,6 @@ import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl; import com.android.systemui.statusbar.phone.StatusBarIconList; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags; @@ -280,7 +280,7 @@ public interface CentralSurfacesDependenciesModule { @SysUISingleton static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager, KeyguardStateController keyguardStateController, - Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManager, + Lazy<AlternateBouncerInteractor> alternateBouncerInteractor, InteractionJankMonitor interactionJankMonitor) { DialogLaunchAnimator.Callback callback = new DialogLaunchAnimator.Callback() { @Override @@ -300,7 +300,7 @@ public interface CentralSurfacesDependenciesModule { @Override public boolean isShowingAlternateAuthOnUnlock() { - return statusBarKeyguardViewManager.get().canShowAlternateBouncer(); + return alternateBouncerInteractor.get().canShowAlternateBouncerForFingerprint(); } }; return new DialogLaunchAnimator(callback, interactionJankMonitor); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java index c4961029dc33..b084a765956d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java @@ -109,7 +109,7 @@ public class ActivatableNotificationViewController return true; } if (ev.getAction() == MotionEvent.ACTION_UP) { - mView.setLastActionUpTime(SystemClock.uptimeMillis()); + mView.setLastActionUpTime(ev.getEventTime()); } // With a11y, just do nothing. if (mAccessibilityManager.isTouchExplorationEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 8d48d738f0f2..9b93d7b9e1d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -1431,6 +1431,22 @@ public class NotificationChildrenContainer extends ViewGroup @Override public void applyRoundnessAndInvalidate() { boolean last = true; + if (mUseRoundnessSourceTypes) { + if (mNotificationHeaderWrapper != null) { + mNotificationHeaderWrapper.requestTopRoundness( + /* value = */ getTopRoundness(), + /* sourceType = */ FROM_PARENT, + /* animate = */ false + ); + } + if (mNotificationHeaderWrapperLowPriority != null) { + mNotificationHeaderWrapperLowPriority.requestTopRoundness( + /* value = */ getTopRoundness(), + /* sourceType = */ FROM_PARENT, + /* animate = */ false + ); + } + } for (int i = mAttachedChildren.size() - 1; i >= 0; i--) { ExpandableNotificationRow child = mAttachedChildren.get(i); if (child.getVisibility() == View.GONE) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index ca1e397f930a..356ddfa2d482 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -1811,9 +1811,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override @ShadeViewRefactor(RefactorComponent.COORDINATOR) public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mBottomInset = insets.getSystemWindowInsetBottom() - + insets.getInsets(WindowInsets.Type.ime()).bottom; - + mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom; mWaterfallTopInset = 0; final DisplayCutout cutout = insets.getDisplayCutout(); if (cutout != null) { @@ -2262,7 +2260,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getImeInset() { - return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight())); + // The NotificationStackScrollLayout does not extend all the way to the bottom of the + // display. Therefore, subtract that space from the mBottomInset, in order to only include + // the portion of the bottom inset that actually overlaps the NotificationStackScrollLayout. + return Math.max(0, mBottomInset + - (getRootView().getHeight() - getHeight() - getLocationOnScreen()[1])); } /** @@ -2970,12 +2972,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable childInGroup = (ExpandableNotificationRow) requestedView; requestedView = requestedRow = childInGroup.getNotificationParent(); } - int position = 0; + final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings; + int position = (int) scrimTopPadding; + int visibleIndex = -1; + ExpandableView lastVisibleChild = null; for (int i = 0; i < getChildCount(); i++) { ExpandableView child = getChildAtIndex(i); boolean notGone = child.getVisibility() != View.GONE; + if (notGone) visibleIndex++; if (notGone && !child.hasNoContentHeight()) { - if (position != 0) { + if (position != scrimTopPadding) { + if (lastVisibleChild != null) { + position += calculateGapHeight(lastVisibleChild, child, visibleIndex); + } position += mPaddingBetweenElements; } } @@ -2987,6 +2996,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } if (notGone) { position += getIntrinsicHeight(child); + lastVisibleChild = child; } } return 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 9070eadd9944..149ec545dfa7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -154,9 +154,7 @@ public class AutoTileManager implements UserAwareController { if (!mAutoTracker.isAdded(SAVER)) { mDataSaverController.addCallback(mDataSaverListener); } - if (!mAutoTracker.isAdded(WORK)) { - mManagedProfileController.addCallback(mProfileCallback); - } + mManagedProfileController.addCallback(mProfileCallback); if (!mAutoTracker.isAdded(NIGHT) && ColorDisplayManager.isNightDisplayAvailable(mContext)) { mNightDisplayListener.setCallback(mNightDisplayCallback); @@ -275,18 +273,18 @@ public class AutoTileManager implements UserAwareController { return mCurrentUser.getIdentifier(); } - public void unmarkTileAsAutoAdded(String tabSpec) { - mAutoTracker.setTileRemoved(tabSpec); - } - private final ManagedProfileController.Callback mProfileCallback = new ManagedProfileController.Callback() { @Override public void onManagedProfileChanged() { - if (mAutoTracker.isAdded(WORK)) return; if (mManagedProfileController.hasActiveProfile()) { + if (mAutoTracker.isAdded(WORK)) return; mHost.addTile(WORK); mAutoTracker.setTileAdded(WORK); + } else { + if (!mAutoTracker.isAdded(WORK)) return; + mHost.removeTile(WORK); + mAutoTracker.setTileRemoved(WORK); } } @@ -429,7 +427,7 @@ public class AutoTileManager implements UserAwareController { initSafetyTile(); } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) { mHost.removeTile(mSafetySpec); - mHost.unmarkTileAsAutoAdded(mSafetySpec); + mAutoTracker.setTileRemoved(mSafetySpec); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 895a2934ec1b..db2c0a08c1d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.SESSION_KEYGUARD; +import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME; + import android.annotation.IntDef; import android.content.res.Resources; import android.hardware.biometrics.BiometricFaceConstants; @@ -27,7 +29,6 @@ import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; -import android.os.SystemClock; import android.os.Trace; import androidx.annotation.Nullable; @@ -62,6 +63,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -78,6 +80,7 @@ import javax.inject.Inject; */ @SysUISingleton public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable { + private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L; private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); @@ -169,9 +172,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; + private final WakefulnessLifecycle mWakefulnessLifecycle; private final LatencyTracker mLatencyTracker; private final VibratorHelper mVibratorHelper; private final BiometricUnlockLogger mLogger; + private final SystemClock mSystemClock; private long mLastFpFailureUptimeMillis; private int mNumConsecutiveFpFailures; @@ -279,14 +284,17 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp SessionTracker sessionTracker, LatencyTracker latencyTracker, ScreenOffAnimationController screenOffAnimationController, - VibratorHelper vibrator) { + VibratorHelper vibrator, + SystemClock systemClock + ) { mPowerManager = powerManager; mShadeController = shadeController; mUpdateMonitor = keyguardUpdateMonitor; mUpdateMonitor.registerCallback(this); mMediaManager = notificationMediaManager; mLatencyTracker = latencyTracker; - wakefulnessLifecycle.addObserver(mWakefulnessObserver); + mWakefulnessLifecycle = wakefulnessLifecycle; + mWakefulnessLifecycle.addObserver(mWakefulnessObserver); screenLifecycle.addObserver(mScreenObserver); mNotificationShadeWindowController = notificationShadeWindowController; @@ -306,6 +314,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mScreenOffAnimationController = screenOffAnimationController; mVibratorHelper = vibrator; mLogger = biometricUnlockLogger; + mSystemClock = systemClock; dumpManager.registerDumpable(getClass().getName(), this); } @@ -429,8 +438,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Runnable wakeUp = ()-> { if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) { mLogger.i("bio wakelock: Authenticated, waking up..."); - mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC, - "android.policy:BIOMETRIC"); + mPowerManager.wakeUp( + mSystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_BIOMETRIC, + "android.policy:BIOMETRIC" + ); } Trace.beginSection("release wake-and-unlock"); releaseBiometricWakeLock(); @@ -670,7 +682,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp startWakeAndUnlock(MODE_ONLY_WAKE); } else if (biometricSourceType == BiometricSourceType.FINGERPRINT && mUpdateMonitor.isUdfpsSupported()) { - long currUptimeMillis = SystemClock.uptimeMillis(); + long currUptimeMillis = mSystemClock.uptimeMillis(); if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) { mNumConsecutiveFpFailures += 1; } else { @@ -718,12 +730,26 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp cleanup(); } - //these haptics are for device-entry only + // these haptics are for device-entry only private void vibrateSuccess(BiometricSourceType type) { + if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) + && lastWakeupFromPowerButtonWithinHapticThreshold()) { + mLogger.d("Skip auth success haptic. Power button was recently pressed."); + return; + } mVibratorHelper.vibrateAuthSuccess( getClass().getSimpleName() + ", type =" + type + "device-entry::success"); } + private boolean lastWakeupFromPowerButtonWithinHapticThreshold() { + final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason() + == PowerManager.WAKE_REASON_POWER_BUTTON; + return lastWakeupFromPowerButton + && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME + && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime() + < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS; + } + private void vibrateError(BiometricSourceType type) { mVibratorHelper.vibrateAuthError( getClass().getSimpleName() + ", type =" + type + "device-entry::error"); @@ -816,7 +842,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (mUpdateMonitor.isUdfpsSupported()) { pw.print(" mNumConsecutiveFpFailures="); pw.println(mNumConsecutiveFpFailures); pw.print(" time since last failure="); - pw.println(SystemClock.uptimeMillis() - mLastFpFailureUptimeMillis); + pw.println(mSystemClock.uptimeMillis() - mLastFpFailureUptimeMillis); } } 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 198572a51760..f1e1f426b467 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -162,6 +162,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; @@ -485,6 +486,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final ShadeController mShadeController; private final InitController mInitController; private final Lazy<CameraLauncher> mCameraLauncherLazy; + private final AlternateBouncerInteractor mAlternateBouncerInteractor; private final PluginDependencyProvider mPluginDependencyProvider; private final KeyguardDismissUtil mKeyguardDismissUtil; @@ -763,7 +765,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { WiredChargingRippleController wiredChargingRippleController, IDreamManager dreamManager, Lazy<CameraLauncher> cameraLauncherLazy, - Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy) { + Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy, + AlternateBouncerInteractor alternateBouncerInteractor + ) { mContext = context; mNotificationsController = notificationsController; mFragmentService = fragmentService; @@ -841,6 +845,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mWallpaperManager = wallpaperManager; mJankMonitor = jankMonitor; mCameraLauncherLazy = cameraLauncherLazy; + mAlternateBouncerInteractor = alternateBouncerInteractor; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -3257,8 +3262,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void showBouncerOrLockScreenIfKeyguard() { // If the keyguard is animating away, we aren't really the keyguard anymore and should not // show the bouncer/lockscreen. - if (!mKeyguardViewMediator.isHiding() - && !mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { + if (!mKeyguardViewMediator.isHiding() && !mKeyguardUpdateMonitor.isKeyguardGoingAway()) { if (mState == StatusBarState.SHADE_LOCKED) { // shade is showing while locked on the keyguard, so go back to showing the // lock screen where users can use the UDFPS affordance to enter the device @@ -3737,7 +3741,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { boolean launchingAffordanceWithPreview = mLaunchingAffordance; mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); - if (mStatusBarKeyguardViewManager.isShowingAlternateBouncer()) { + if (mAlternateBouncerInteractor.isVisibleState()) { if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED || mTransitionToFullShadeProgress > 0f) { mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); @@ -4262,8 +4266,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onDozeAmountChanged(float linear, float eased) { - if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS) - && !mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION) + if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION) && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { mLightRevealScrim.setRevealAmount(1f - linear); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index de7b152adaab..0446cefb10dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -44,10 +44,9 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.tuner.TunerService; @@ -82,7 +81,6 @@ public class DozeParameters implements private final AlwaysOnDisplayPolicy mAlwaysOnPolicy; private final Resources mResources; private final BatteryController mBatteryController; - private final FeatureFlags mFeatureFlags; private final ScreenOffAnimationController mScreenOffAnimationController; private final FoldAodAnimationController mFoldAodAnimationController; private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @@ -125,7 +123,6 @@ public class DozeParameters implements BatteryController batteryController, TunerService tunerService, DumpManager dumpManager, - FeatureFlags featureFlags, ScreenOffAnimationController screenOffAnimationController, Optional<SysUIUnfoldComponent> sysUiUnfoldComponent, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @@ -141,7 +138,6 @@ public class DozeParameters implements mControlScreenOffAnimation = !getDisplayNeedsBlanking(); mPowerManager = powerManager; mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation); - mFeatureFlags = featureFlags; mScreenOffAnimationController = screenOffAnimationController; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; @@ -162,6 +158,13 @@ public class DozeParameters implements SettingsObserver quickPickupSettingsObserver = new SettingsObserver(context, handler); quickPickupSettingsObserver.observe(); + + batteryController.addCallback(new BatteryStateChangeCallback() { + @Override + public void onPowerSaveChanged(boolean isPowerSave) { + dispatchAlwaysOnEvent(); + } + }); } private void updateQuickPickupEnabled() { @@ -300,13 +303,10 @@ public class DozeParameters implements /** * Whether we're capable of controlling the screen off animation if we want to. This isn't - * possible if AOD isn't even enabled or if the flag is disabled, or if the display needs - * blanking. + * possible if AOD isn't even enabled or if the display needs blanking. */ public boolean canControlUnlockedScreenOff() { - return getAlwaysOn() - && mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS) - && !getDisplayNeedsBlanking(); + return getAlwaysOn() && !getDisplayNeedsBlanking(); } /** @@ -424,9 +424,7 @@ public class DozeParameters implements updateControlScreenOff(); } - for (Callback callback : mCallbacks) { - callback.onAlwaysOnChange(); - } + dispatchAlwaysOnEvent(); mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn()); } @@ -463,6 +461,12 @@ public class DozeParameters implements pw.print("isQuickPickupEnabled(): "); pw.println(isQuickPickupEnabled()); } + private void dispatchAlwaysOnEvent() { + for (Callback callback : mCallbacks) { + callback.onAlwaysOnChange(); + } + } + private boolean getPostureSpecificBool( int[] postureMapping, boolean defaultSensorBool, @@ -477,7 +481,8 @@ public class DozeParameters implements return bool; } - interface Callback { + /** Callbacks for doze parameter related information */ + public interface Callback { /** * Invoked when the value of getAlwaysOn may have changed. */ 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 348357445223..4ad319969eaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -45,6 +45,7 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.plugins.log.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.CommandQueue; @@ -76,6 +77,7 @@ import javax.inject.Inject; /** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */ public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> { + private static final String TAG = "KeyguardStatusBarViewController"; private static final AnimationProperties KEYGUARD_HUN_PROPERTIES = new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); @@ -422,7 +424,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /** Animate the keyguard status bar in. */ public void animateKeyguardStatusBarIn() { - mLogger.d("animating status bar in"); + mLogger.log(TAG, LogLevel.DEBUG, "animating status bar in"); if (mDisableStateTracker.isDisabled()) { // If our view is disabled, don't allow us to animate in. return; @@ -438,7 +440,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /** Animate the keyguard status bar out. */ public void animateKeyguardStatusBarOut(long startDelay, long duration) { - mLogger.d("animating status bar out"); + mLogger.log(TAG, LogLevel.DEBUG, "animating status bar out"); ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f); anim.addUpdateListener(mAnimatorUpdateListener); anim.setStartDelay(startDelay); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index d480fabe75b1..7d917bd9cd1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -58,6 +58,7 @@ import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.BouncerView; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.navigationbar.NavigationBarView; @@ -134,6 +135,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController; private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; + private final AlternateBouncerInteractor mAlternateBouncerInteractor; private final BouncerView mPrimaryBouncerView; private final Lazy<ShadeController> mShadeController; @@ -253,6 +255,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); private boolean mIsModernBouncerEnabled; private boolean mIsUnoccludeTransitionFlagEnabled; + private boolean mIsModernAlternateBouncerEnabled; private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; @@ -269,7 +272,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final LatencyTracker mLatencyTracker; private final KeyguardSecurityModel mKeyguardSecurityModel; @Nullable private KeyguardBypassController mBypassController; - @Nullable private AlternateBouncer mAlternateBouncer; + @Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI; private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @@ -306,7 +309,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb FeatureFlags featureFlags, PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor, PrimaryBouncerInteractor primaryBouncerInteractor, - BouncerView primaryBouncerView) { + BouncerView primaryBouncerView, + AlternateBouncerInteractor alternateBouncerInteractor) { mContext = context; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; @@ -331,6 +335,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER); mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); + mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER); + mAlternateBouncerInteractor = alternateBouncerInteractor; } @Override @@ -363,23 +369,51 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } /** - * Sets the given alt auth interceptor to null if it's the current auth interceptor. Else, + * Sets the given legacy alternate bouncer to null if it's the current alternate bouncer. Else, + * does nothing. Only used if modern alternate bouncer is NOT enabled. + */ + public void removeLegacyAlternateBouncer( + @NonNull LegacyAlternateBouncer alternateBouncerLegacy) { + if (!mIsModernAlternateBouncerEnabled) { + if (Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(), + alternateBouncerLegacy)) { + mAlternateBouncerInteractor.setLegacyAlternateBouncer(null); + hideAlternateBouncer(true); + } + } + } + + /** + * Sets a new legacy alternate bouncer. Only used if mdoern alternate bouncer is NOT enable. + */ + public void setLegacyAlternateBouncer(@NonNull LegacyAlternateBouncer alternateBouncerLegacy) { + if (!mIsModernAlternateBouncerEnabled) { + if (!Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(), + alternateBouncerLegacy)) { + mAlternateBouncerInteractor.setLegacyAlternateBouncer(alternateBouncerLegacy); + hideAlternateBouncer(false); + } + } + + } + + + /** + * Sets the given OccludingAppBiometricUI to null if it's the current auth interceptor. Else, * does nothing. */ - public void removeAlternateAuthInterceptor(@NonNull AlternateBouncer authInterceptor) { - if (Objects.equals(mAlternateBouncer, authInterceptor)) { - mAlternateBouncer = null; - hideAlternateBouncer(true); + public void removeOccludingAppBiometricUI(@NonNull OccludingAppBiometricUI biometricUI) { + if (Objects.equals(mOccludingAppBiometricUI, biometricUI)) { + mOccludingAppBiometricUI = null; } } /** - * Sets a new alt auth interceptor. + * Sets a new OccludingAppBiometricUI. */ - public void setAlternateBouncer(@NonNull AlternateBouncer authInterceptor) { - if (!Objects.equals(mAlternateBouncer, authInterceptor)) { - mAlternateBouncer = authInterceptor; - hideAlternateBouncer(false); + public void setOccludingAppBiometricUI(@NonNull OccludingAppBiometricUI biometricUI) { + if (!Objects.equals(mOccludingAppBiometricUI, biometricUI)) { + mOccludingAppBiometricUI = biometricUI; } } @@ -566,18 +600,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * {@see KeyguardBouncer#show(boolean, boolean)} */ public void showBouncer(boolean scrimmed) { - if (canShowAlternateBouncer()) { - updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer()); - return; + if (!mAlternateBouncerInteractor.show()) { + showPrimaryBouncer(scrimmed); + } else { + updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); } - - showPrimaryBouncer(scrimmed); - } - - /** Whether we can show the alternate bouncer instead of the primary bouncer. */ - public boolean canShowAlternateBouncer() { - return mAlternateBouncer != null - && mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true); } /** @@ -641,9 +668,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardGoneCancelAction = cancelAction; mDismissActionWillAnimateOnKeyguard = r != null && r.willRunAnimationOnKeyguard(); - // If there is an an alternate auth interceptor (like the UDFPS), show that one + // If there is an alternate auth interceptor (like the UDFPS), show that one // instead of the bouncer. - if (canShowAlternateBouncer()) { + if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) { if (!afterKeyguardGone) { if (mPrimaryBouncer != null) { mPrimaryBouncer.setDismissAction(mAfterKeyguardGoneAction, @@ -656,7 +683,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardGoneCancelAction = null; } - updateAlternateBouncerShowing(mAlternateBouncer.showAlternateBouncer()); + updateAlternateBouncerShowing(mAlternateBouncerInteractor.show()); return; } @@ -725,10 +752,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void hideAlternateBouncer(boolean forceUpdateScrim) { - final boolean updateScrim = (mAlternateBouncer != null - && mAlternateBouncer.hideAlternateBouncer()) - || forceUpdateScrim; - updateAlternateBouncerShowing(updateScrim); + updateAlternateBouncerShowing(mAlternateBouncerInteractor.hide() || forceUpdateScrim); } private void updateAlternateBouncerShowing(boolean updateScrim) { @@ -738,7 +762,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return; } - final boolean isShowingAlternateBouncer = isShowingAlternateBouncer(); + final boolean isShowingAlternateBouncer = mAlternateBouncerInteractor.isVisibleState(); if (mKeyguardMessageAreaController != null) { mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer); mKeyguardMessageAreaController.setMessage(""); @@ -1095,7 +1119,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public boolean isBouncerShowing() { - return primaryBouncerIsShowing() || isShowingAlternateBouncer(); + return primaryBouncerIsShowing() || mAlternateBouncerInteractor.isVisibleState(); } @Override @@ -1339,7 +1363,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth); } - if (mAlternateBouncer != null && isShowingAlternateBouncer()) { + if (mAlternateBouncerInteractor.isVisibleState()) { hideAlternateBouncer(false); executeAfterKeyguardGoneAction(); } @@ -1347,7 +1371,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb /** Display security message to relevant KeyguardMessageArea. */ public void setKeyguardMessage(String message, ColorStateList colorState) { - if (isShowingAlternateBouncer()) { + if (mAlternateBouncerInteractor.isVisibleState()) { if (mKeyguardMessageAreaController != null) { mKeyguardMessageAreaController.setMessage(message); } @@ -1421,6 +1445,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void dump(PrintWriter pw) { pw.println("StatusBarKeyguardViewManager:"); + pw.println(" mIsModernAlternateBouncerEnabled: " + mIsModernAlternateBouncerEnabled); pw.println(" mRemoteInputActive: " + mRemoteInputActive); pw.println(" mDozing: " + mDozing); pw.println(" mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction); @@ -1438,9 +1463,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mPrimaryBouncer.dump(pw); } - if (mAlternateBouncer != null) { - pw.println("AlternateBouncer:"); - mAlternateBouncer.dump(pw); + if (mOccludingAppBiometricUI != null) { + pw.println("mOccludingAppBiometricUI:"); + mOccludingAppBiometricUI.dump(pw); } } @@ -1492,14 +1517,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return mPrimaryBouncer; } - public boolean isShowingAlternateBouncer() { - return mAlternateBouncer != null && mAlternateBouncer.isShowingAlternateBouncer(); - } - /** - * Forward touches to callbacks. + * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently + * showing. */ public void onTouch(MotionEvent event) { + if (mAlternateBouncerInteractor.isVisibleState() + && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) { + showPrimaryBouncer(true); + } + + // Forward NPVC touches to callbacks in case they want to respond to touches for (KeyguardViewManagerCallback callback: mCallbacks) { callback.onTouch(event); } @@ -1542,8 +1570,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ public void requestFp(boolean request, int udfpsColor) { mKeyguardUpdateManager.requestFingerprintAuthOnOccludingApp(request); - if (mAlternateBouncer != null) { - mAlternateBouncer.requestUdfps(request, udfpsColor); + if (mOccludingAppBiometricUI != null) { + mOccludingAppBiometricUI.requestUdfps(request, udfpsColor); } } @@ -1614,10 +1642,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } /** - * Delegate used to send show and hide events to an alternate authentication method instead of - * the regular pin/pattern/password bouncer. + * @Deprecated Delegate used to send show and hide events to an alternate bouncer. */ - public interface AlternateBouncer { + public interface LegacyAlternateBouncer { /** * Show alternate authentication bouncer. * @return whether alternate auth method was newly shown @@ -1634,7 +1661,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * @return true if the alternate auth bouncer is showing */ boolean isShowingAlternateBouncer(); + } + /** + * Delegate used to send show and hide events to an alternate authentication method instead of + * the regular pin/pattern/password bouncer. + */ + public interface OccludingAppBiometricUI { /** * Use when an app occluding the keyguard would like to give the user ability to * unlock the device using udfps. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 26bc3e3c0920..608bfa611ff0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -189,7 +189,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh // for foldables that often go from large <=> small screen when folding/unfolding. ViewRootImpl.addConfigCallback(this); mDialogManager.setShowing(this, true); - mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true); + mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true) + .commitUpdate(mContext.getDisplayId()); } @Override @@ -202,7 +203,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh ViewRootImpl.removeConfigCallback(this); mDialogManager.setShowing(this, false); - mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false); + mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false) + .commitUpdate(mContext.getDisplayId()); } public void setShowForAllUsers(boolean show) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 0e164e7ee859..8ac12379e59e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -149,7 +149,7 @@ constructor( } private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository { - val tableLogBuffer = logFactory.create("DemoMobileConnectionLog [$subId]", 100) + val tableLogBuffer = logFactory.getOrCreate("DemoMobileConnectionLog [$subId]", 100) return DemoMobileConnectionRepository( subId, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 0fa0fea0bebf..4e42f9b31e5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -308,7 +308,7 @@ class MobileConnectionRepositoryImpl( networkNameSeparator: String, globalMobileDataSettingChangedEvent: Flow<Unit>, ): MobileConnectionRepository { - val mobileLogger = logFactory.create(tableBufferLogName(subId), 100) + val mobileLogger = logFactory.getOrCreate(tableBufferLogName(subId), 100) return MobileConnectionRepositoryImpl( context, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt index 68d30d3f3d1e..2b4f51c63043 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -60,7 +60,7 @@ protected constructor( * animation to and from the parent dialog. */ @JvmOverloads - open fun onUserListItemClicked( + fun onUserListItemClicked( record: UserRecord, dialogShower: DialogShower? = null, ) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index f63d65246d9b..c8ee647cf8a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -160,7 +160,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mStatusBarStateController = statusBarStateController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, - screenOffAnimationController, /* animateYPos= */ false); + screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null); mUserSwitchDialogController = userSwitchDialogController; mUiEventLogger = uiEventLogger; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index c1506541229d..e9f0dcb4eb51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -173,7 +173,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mUserSwitcherController, this); mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, - screenOffAnimationController, /* animateYPos= */ false); + screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null); mBackground = new KeyguardUserSwitcherScrim(context); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index c9ed0cb4155d..f8c17e8c8379 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -109,6 +109,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33; private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83; private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f; + private static final long DEFOCUS_ANIMATION_FADE_OUT_DELAY = 120; + private static final long DEFOCUS_ANIMATION_CROSSFADE_DELAY = 180; public final Object mToken = new Object(); @@ -421,7 +423,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } @VisibleForTesting - void onDefocus(boolean animate, boolean logClose) { + void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) { mController.removeRemoteInput(mEntry, mToken); mEntry.remoteInputText = mEditText.getText(); @@ -431,18 +433,20 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene ViewGroup parent = (ViewGroup) getParent(); if (animate && parent != null && mIsFocusAnimationFlagActive) { - ViewGroup grandParent = (ViewGroup) parent.getParent(); ViewGroupOverlay overlay = parent.getOverlay(); + View actionsContainer = getActionsContainerLayout(); + int actionsContainerHeight = + actionsContainer != null ? actionsContainer.getHeight() : 0; // After adding this RemoteInputView to the overlay of the parent (and thus removing // it from the parent itself), the parent will shrink in height. This causes the // overlay to be moved. To correct the position of the overlay we need to offset it. - int overlayOffsetY = getMaxSiblingHeight() - getHeight(); + int overlayOffsetY = actionsContainerHeight - getHeight(); overlay.add(this); if (grandParent != null) grandParent.setClipChildren(false); - Animator animator = getDefocusAnimator(overlayOffsetY); + Animator animator = getDefocusAnimator(actionsContainer, overlayOffsetY); View self = this; animator.addListener(new AnimatorListenerAdapter() { @Override @@ -454,8 +458,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (mWrapper != null) { mWrapper.setRemoteInputVisible(false); } + if (doAfterDefocus != null) { + doAfterDefocus.run(); + } } }); + if (actionsContainer != null) actionsContainer.setAlpha(0f); animator.start(); } else if (animate && mRevealParams != null && mRevealParams.radius > 0) { @@ -474,6 +482,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene reveal.start(); } else { setVisibility(GONE); + if (doAfterDefocus != null) doAfterDefocus.run(); if (mWrapper != null) { mWrapper.setRemoteInputVisible(false); } @@ -596,10 +605,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene /** * Focuses the RemoteInputView and animates its appearance - * - * @param crossFadeView view that will be crossfaded during the appearance animation */ - public void focusAnimated(View crossFadeView) { + public void focusAnimated() { if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE && mRevealParams != null) { android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this); @@ -609,7 +616,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) { mIsAnimatingAppearance = true; setAlpha(0f); - Animator focusAnimator = getFocusAnimator(crossFadeView); + Animator focusAnimator = getFocusAnimator(getActionsContainerLayout()); focusAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation, boolean isReverse) { @@ -661,6 +668,23 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void reset() { + if (mIsFocusAnimationFlagActive) { + mProgressBar.setVisibility(INVISIBLE); + mResetting = true; + mSending = false; + onDefocus(true /* animate */, false /* logClose */, () -> { + mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); + mEditText.getText().clear(); + mEditText.setEnabled(isAggregatedVisible()); + mSendButton.setVisibility(VISIBLE); + mController.removeSpinning(mEntry.getKey(), mToken); + updateSendButton(); + setAttachment(null); + mResetting = false; + }); + return; + } + mResetting = true; mSending = false; mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); @@ -671,7 +695,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mProgressBar.setVisibility(INVISIBLE); mController.removeSpinning(mEntry.getKey(), mToken); updateSendButton(); - onDefocus(false /* animate */, false /* logClose */); + onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */); setAttachment(null); mResetting = false; @@ -825,23 +849,22 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } /** - * @return max sibling height (0 in case of no siblings) + * @return action button container view (i.e. ViewGroup containing Reply button etc.) */ - public int getMaxSiblingHeight() { + public View getActionsContainerLayout() { ViewGroup parentView = (ViewGroup) getParent(); - int maxHeight = 0; - if (parentView == null) return 0; - for (int i = 0; i < parentView.getChildCount(); i++) { - View siblingView = parentView.getChildAt(i); - if (siblingView != this) maxHeight = Math.max(maxHeight, siblingView.getHeight()); - } - return maxHeight; + if (parentView == null) return null; + return parentView.findViewById(com.android.internal.R.id.actions_container_layout); } /** * Creates an animator for the focus animation. + * + * @param fadeOutView View that will be faded out during the focus animation. */ - private Animator getFocusAnimator(View crossFadeView) { + private Animator getFocusAnimator(@Nullable View fadeOutView) { + final AnimatorSet animatorSet = new AnimatorSet(); + final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f); alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY); alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION); @@ -854,30 +877,36 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION); scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN); - final Animator crossFadeViewAlphaAnimator = - ObjectAnimator.ofFloat(crossFadeView, View.ALPHA, 1f, 0f); - crossFadeViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION); - crossFadeViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR); - alphaAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation, boolean isReverse) { - crossFadeView.setAlpha(1f); - } - }); - - final AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(alphaAnimator, scaleAnimator, crossFadeViewAlphaAnimator); + if (fadeOutView == null) { + animatorSet.playTogether(alphaAnimator, scaleAnimator); + } else { + final Animator fadeOutViewAlphaAnimator = + ObjectAnimator.ofFloat(fadeOutView, View.ALPHA, 1f, 0f); + fadeOutViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION); + fadeOutViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation, boolean isReverse) { + fadeOutView.setAlpha(1f); + } + }); + animatorSet.playTogether(alphaAnimator, scaleAnimator, fadeOutViewAlphaAnimator); + } return animatorSet; } /** * Creates an animator for the defocus animation. * - * @param offsetY The RemoteInputView will be offset by offsetY during the animation + * @param fadeInView View that will be faded in during the defocus animation. + * @param offsetY The RemoteInputView will be offset by offsetY during the animation */ - private Animator getDefocusAnimator(int offsetY) { + private Animator getDefocusAnimator(@Nullable View fadeInView, int offsetY) { + final AnimatorSet animatorSet = new AnimatorSet(); + final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f); - alphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION); + alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION); + alphaAnimator.setStartDelay(DEFOCUS_ANIMATION_FADE_OUT_DELAY); alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR); ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE); @@ -893,8 +922,17 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } }); - final AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(alphaAnimator, scaleAnimator); + if (fadeInView == null) { + animatorSet.playTogether(alphaAnimator, scaleAnimator); + } else { + fadeInView.forceHasOverlappingRendering(false); + Animator fadeInViewAlphaAnimator = + ObjectAnimator.ofFloat(fadeInView, View.ALPHA, 0f, 1f); + fadeInViewAlphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION); + fadeInViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR); + fadeInViewAlphaAnimator.setStartDelay(DEFOCUS_ANIMATION_CROSSFADE_DELAY); + animatorSet.playTogether(alphaAnimator, scaleAnimator, fadeInViewAlphaAnimator); + } return animatorSet; } @@ -1011,7 +1049,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (isFocusable() && isEnabled()) { setInnerFocusable(false); if (mRemoteInputView != null) { - mRemoteInputView.onDefocus(animate, true /* logClose */); + mRemoteInputView + .onDefocus(animate, true /* logClose */, null /* doAfterDefocus */); } mShowImeOnInputConnection = false; } diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt deleted file mode 100644 index 154c6e2e3158..000000000000 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.stylus - -import android.content.Context -import android.hardware.BatteryState -import android.hardware.input.InputManager -import android.os.Handler -import android.util.Log -import android.view.InputDevice -import androidx.annotation.VisibleForTesting -import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import java.util.concurrent.Executor -import javax.inject.Inject - -/** - * A listener that detects when a stylus has first been used, by detecting 1) the presence of an - * internal SOURCE_STYLUS with a battery, or 2) any added SOURCE_STYLUS device with a bluetooth - * address. - */ -@SysUISingleton -class StylusFirstUsageListener -@Inject -constructor( - private val context: Context, - private val inputManager: InputManager, - private val stylusManager: StylusManager, - private val featureFlags: FeatureFlags, - @Background private val executor: Executor, - @Background private val handler: Handler, -) : CoreStartable, StylusManager.StylusCallback, InputManager.InputDeviceBatteryListener { - - // Set must be only accessed from the background handler, which is the same handler that - // runs the StylusManager callbacks. - private val internalStylusDeviceIds: MutableSet<Int> = mutableSetOf() - @VisibleForTesting var hasStarted = false - - override fun start() { - if (true) return // TODO(b/261826950): remove on main - if (hasStarted) return - if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return - if (inputManager.isStylusEverUsed(context)) return - if (!hostDeviceSupportsStylusInput()) return - - hasStarted = true - inputManager.inputDeviceIds.forEach(this::onStylusAdded) - stylusManager.registerCallback(this) - stylusManager.startListener() - } - - override fun onStylusAdded(deviceId: Int) { - if (!hasStarted) return - - val device = inputManager.getInputDevice(deviceId) ?: return - if (device.isExternal || !device.supportsSource(InputDevice.SOURCE_STYLUS)) return - - try { - inputManager.addInputDeviceBatteryListener(deviceId, executor, this) - internalStylusDeviceIds += deviceId - } catch (e: SecurityException) { - Log.e(TAG, "$e: Failed to register battery listener for $deviceId ${device.name}.") - } - } - - override fun onStylusRemoved(deviceId: Int) { - if (!hasStarted) return - - if (!internalStylusDeviceIds.contains(deviceId)) return - try { - inputManager.removeInputDeviceBatteryListener(deviceId, this) - internalStylusDeviceIds.remove(deviceId) - } catch (e: SecurityException) { - Log.e(TAG, "$e: Failed to remove registered battery listener for $deviceId.") - } - } - - override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) { - if (!hasStarted) return - - onRemoteDeviceFound() - } - - override fun onBatteryStateChanged( - deviceId: Int, - eventTimeMillis: Long, - batteryState: BatteryState - ) { - if (!hasStarted) return - - if (batteryState.isPresent) { - onRemoteDeviceFound() - } - } - - private fun onRemoteDeviceFound() { - inputManager.setStylusEverUsed(context, true) - cleanupListeners() - } - - private fun cleanupListeners() { - stylusManager.unregisterCallback(this) - handler.post { - internalStylusDeviceIds.forEach { - inputManager.removeInputDeviceBatteryListener(it, this) - } - } - } - - private fun hostDeviceSupportsStylusInput(): Boolean { - return inputManager.inputDeviceIds - .asSequence() - .mapNotNull { inputManager.getInputDevice(it) } - .any { it.supportsSource(InputDevice.SOURCE_STYLUS) && !it.isExternal } - } - - companion object { - private val TAG = StylusFirstUsageListener::class.simpleName.orEmpty() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt index 302d6a9ca1b7..235495cfa50d 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt @@ -18,6 +18,8 @@ package com.android.systemui.stylus import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice +import android.content.Context +import android.hardware.BatteryState import android.hardware.input.InputManager import android.os.Handler import android.util.ArrayMap @@ -25,6 +27,8 @@ import android.util.Log import android.view.InputDevice import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executor import javax.inject.Inject @@ -37,25 +41,37 @@ import javax.inject.Inject class StylusManager @Inject constructor( + private val context: Context, private val inputManager: InputManager, private val bluetoothAdapter: BluetoothAdapter?, @Background private val handler: Handler, @Background private val executor: Executor, -) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener { + private val featureFlags: FeatureFlags, +) : + InputManager.InputDeviceListener, + InputManager.InputDeviceBatteryListener, + BluetoothAdapter.OnMetadataChangedListener { private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList() private val stylusBatteryCallbacks: CopyOnWriteArrayList<StylusBatteryCallback> = CopyOnWriteArrayList() // This map should only be accessed on the handler private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap() + // This variable should only be accessed on the handler + private var hasStarted: Boolean = false /** * Starts listening to InputManager InputDevice events. Will also load the InputManager snapshot * at time of starting. */ fun startListener() { - addExistingStylusToMap() - inputManager.registerInputDeviceListener(this, handler) + handler.post { + if (hasStarted) return@post + hasStarted = true + addExistingStylusToMap() + + inputManager.registerInputDeviceListener(this, handler) + } } /** Registers a StylusCallback to listen to stylus events. */ @@ -77,21 +93,30 @@ constructor( } override fun onInputDeviceAdded(deviceId: Int) { + if (!hasStarted) return + val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return + if (!device.isExternal) { + registerBatteryListener(deviceId) + } + // TODO(b/257936830): get address once input api available val btAddress: String? = null inputDeviceAddressMap[deviceId] = btAddress executeStylusCallbacks { cb -> cb.onStylusAdded(deviceId) } if (btAddress != null) { + onStylusUsed() onStylusBluetoothConnected(btAddress) executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) } } } override fun onInputDeviceChanged(deviceId: Int) { + if (!hasStarted) return + val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return @@ -112,7 +137,10 @@ constructor( } override fun onInputDeviceRemoved(deviceId: Int) { + if (!hasStarted) return + if (!inputDeviceAddressMap.contains(deviceId)) return + unregisterBatteryListener(deviceId) val btAddress: String? = inputDeviceAddressMap[deviceId] inputDeviceAddressMap.remove(deviceId) @@ -124,13 +152,14 @@ constructor( } override fun onMetadataChanged(device: BluetoothDevice, key: Int, value: ByteArray?) { - handler.post executeMetadataChanged@{ - if (key != BluetoothDevice.METADATA_MAIN_CHARGING || value == null) - return@executeMetadataChanged + handler.post { + if (!hasStarted) return@post + + if (key != BluetoothDevice.METADATA_MAIN_CHARGING || value == null) return@post val inputDeviceId: Int = inputDeviceAddressMap.filterValues { it == device.address }.keys.firstOrNull() - ?: return@executeMetadataChanged + ?: return@post val isCharging = String(value) == "true" @@ -140,6 +169,24 @@ constructor( } } + override fun onBatteryStateChanged( + deviceId: Int, + eventTimeMillis: Long, + batteryState: BatteryState + ) { + handler.post { + if (!hasStarted) return@post + + if (batteryState.isPresent) { + onStylusUsed() + } + + executeStylusBatteryCallbacks { cb -> + cb.onStylusUsiBatteryStateChanged(deviceId, eventTimeMillis, batteryState) + } + } + } + private fun onStylusBluetoothConnected(btAddress: String) { val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return try { @@ -158,6 +205,21 @@ constructor( } } + /** + * An InputDevice that supports [InputDevice.SOURCE_STYLUS] may still be present even when a + * physical stylus device has never been used. This method is run when 1) a USI stylus battery + * event happens, or 2) a bluetooth stylus is connected, as they are both indicators that a + * physical stylus device has actually been used. + */ + private fun onStylusUsed() { + if (true) return // TODO(b/261826950): remove on main + if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return + if (inputManager.isStylusEverUsed(context)) return + + inputManager.setStylusEverUsed(context, true) + executeStylusCallbacks { cb -> cb.onStylusFirstUsed() } + } + private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) { stylusCallbacks.forEach(run) } @@ -166,31 +228,69 @@ constructor( stylusBatteryCallbacks.forEach(run) } + private fun registerBatteryListener(deviceId: Int) { + try { + inputManager.addInputDeviceBatteryListener(deviceId, executor, this) + } catch (e: SecurityException) { + Log.e(TAG, "$e: Failed to register battery listener for $deviceId.") + } + } + + private fun unregisterBatteryListener(deviceId: Int) { + // If deviceId wasn't registered, the result is a no-op, so an "is registered" + // check is not needed. + try { + inputManager.removeInputDeviceBatteryListener(deviceId, this) + } catch (e: SecurityException) { + Log.e(TAG, "$e: Failed to remove registered battery listener for $deviceId.") + } + } + private fun addExistingStylusToMap() { for (deviceId: Int in inputManager.inputDeviceIds) { val device: InputDevice = inputManager.getInputDevice(deviceId) ?: continue if (device.supportsSource(InputDevice.SOURCE_STYLUS)) { // TODO(b/257936830): get address once input api available inputDeviceAddressMap[deviceId] = null + + if (!device.isExternal) { // TODO(b/263556967): add supportsUsi check once available + // For most devices, an active (non-bluetooth) stylus is represented by an + // internal InputDevice. This InputDevice will be present in InputManager + // before CoreStartables run, and will not be removed. + // In many cases, it reports the battery level of the stylus. + registerBatteryListener(deviceId) + } } } } - /** Callback interface to receive events from the StylusManager. */ + /** + * Callback interface to receive events from the StylusManager. All callbacks are run on the + * same background handler. + */ interface StylusCallback { fun onStylusAdded(deviceId: Int) {} fun onStylusRemoved(deviceId: Int) {} fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {} fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {} + fun onStylusFirstUsed() {} } - /** Callback interface to receive stylus battery events from the StylusManager. */ + /** + * Callback interface to receive stylus battery events from the StylusManager. All callbacks are + * runs on the same background handler. + */ interface StylusBatteryCallback { fun onStylusBluetoothChargingStateChanged( inputDeviceId: Int, btDevice: BluetoothDevice, isCharging: Boolean ) {} + fun onStylusUsiBatteryStateChanged( + deviceId: Int, + eventTimeMillis: Long, + batteryState: BatteryState, + ) {} } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt index 11233dda165c..14a9161ac291 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt @@ -18,14 +18,11 @@ package com.android.systemui.stylus import android.hardware.BatteryState import android.hardware.input.InputManager -import android.util.Log import android.view.InputDevice import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import java.util.concurrent.Executor import javax.inject.Inject /** @@ -40,16 +37,7 @@ constructor( private val inputManager: InputManager, private val stylusUsiPowerUi: StylusUsiPowerUI, private val featureFlags: FeatureFlags, - @Background private val executor: Executor, -) : CoreStartable, StylusManager.StylusCallback, InputManager.InputDeviceBatteryListener { - - override fun onStylusAdded(deviceId: Int) { - val device = inputManager.getInputDevice(deviceId) ?: return - - if (!device.isExternal) { - registerBatteryListener(deviceId) - } - } +) : CoreStartable, StylusManager.StylusCallback, StylusManager.StylusBatteryCallback { override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) { stylusUsiPowerUi.refresh() @@ -59,15 +47,7 @@ constructor( stylusUsiPowerUi.refresh() } - override fun onStylusRemoved(deviceId: Int) { - val device = inputManager.getInputDevice(deviceId) ?: return - - if (!device.isExternal) { - unregisterBatteryListener(deviceId) - } - } - - override fun onBatteryStateChanged( + override fun onStylusUsiBatteryStateChanged( deviceId: Int, eventTimeMillis: Long, batteryState: BatteryState @@ -77,39 +57,19 @@ constructor( } } - private fun registerBatteryListener(deviceId: Int) { - try { - inputManager.addInputDeviceBatteryListener(deviceId, executor, this) - } catch (e: SecurityException) { - Log.e(TAG, "$e: Failed to register battery listener for $deviceId.") - } - } - - private fun unregisterBatteryListener(deviceId: Int) { - try { - inputManager.removeInputDeviceBatteryListener(deviceId, this) - } catch (e: SecurityException) { - Log.e(TAG, "$e: Failed to unregister battery listener for $deviceId.") - } - } - override fun start() { if (!featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)) return - addBatteryListenerForInternalStyluses() + if (!hostDeviceSupportsStylusInput()) return stylusManager.registerCallback(this) stylusManager.startListener() } - private fun addBatteryListenerForInternalStyluses() { - // For most devices, an active stylus is represented by an internal InputDevice. - // This InputDevice will be present in InputManager before CoreStartables run, - // and will not be removed. In many cases, it reports the battery level of the stylus. - inputManager.inputDeviceIds + private fun hostDeviceSupportsStylusInput(): Boolean { + return inputManager.inputDeviceIds .asSequence() .mapNotNull { inputManager.getInputDevice(it) } - .filter { it.supportsSource(InputDevice.SOURCE_STYLUS) } - .forEach { onStylusAdded(it.id) } + .any { it.supportsSource(InputDevice.SOURCE_STYLUS) && !it.isExternal } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt index 70a5b366263e..e8216576811a 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt @@ -123,13 +123,13 @@ constructor( .setSmallIcon(R.drawable.ic_power_low) .setDeleteIntent(getPendingBroadcast(ACTION_DISMISSED_LOW_BATTERY)) .setContentIntent(getPendingBroadcast(ACTION_CLICKED_LOW_BATTERY)) - .setContentTitle(context.getString(R.string.stylus_battery_low)) - .setContentText( + .setContentTitle( context.getString( - R.string.battery_low_percent_format, + R.string.stylus_battery_low_percentage, NumberFormat.getPercentInstance().format(batteryCapacity) ) ) + .setContentText(context.getString(R.string.stylus_battery_low_subtitle)) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setLocalOnly(true) .setAutoCancel(true) @@ -177,7 +177,7 @@ constructor( // https://source.chromium.org/chromium/chromium/src/+/main:ash/system/power/peripheral_battery_notifier.cc;l=41 private const val LOW_BATTERY_THRESHOLD = 0.16f - private val USI_NOTIFICATION_ID = R.string.stylus_battery_low + private val USI_NOTIFICATION_ID = R.string.stylus_battery_low_percentage private const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss" private const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click" diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 59ad24a3e7bb..2709da38a7d8 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -17,6 +17,9 @@ package com.android.systemui.unfold import android.content.Context +import android.hardware.devicestate.DeviceStateManager +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.system.SystemUnfoldSharedModule @@ -32,6 +35,7 @@ import dagger.Lazy import dagger.Module import dagger.Provides import java.util.Optional +import java.util.concurrent.Executor import javax.inject.Named import javax.inject.Singleton @@ -40,6 +44,20 @@ class UnfoldTransitionModule { @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui" + /** A globally available FoldStateListener that allows one to query the fold state. */ + @Provides + @Singleton + fun providesFoldStateListener( + deviceStateManager: DeviceStateManager, + @Application context: Context, + @Main executor: Executor + ): DeviceStateManager.FoldStateListener { + val listener = DeviceStateManager.FoldStateListener(context) + deviceStateManager.registerCallback(executor, listener) + + return listener + } + @Provides @Singleton fun providesFoldStateLoggingProvider( diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index d7b0971d9126..c0ba3cc352b0 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -115,9 +115,7 @@ constructor( private val callbackMutex = Mutex() private val callbacks = mutableSetOf<UserCallback>() private val userInfos: Flow<List<UserInfo>> = - repository.userInfos.map { userInfos -> - userInfos.filter { it.isFull } - } + repository.userInfos.map { userInfos -> userInfos.filter { it.isFull } } /** List of current on-device users to select from. */ val users: Flow<List<UserModel>> @@ -445,7 +443,8 @@ constructor( ) ) } - UserActionModel.ADD_SUPERVISED_USER -> + UserActionModel.ADD_SUPERVISED_USER -> { + dismissDialog() activityStarter.startActivity( Intent() .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) @@ -453,6 +452,7 @@ constructor( .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), /* dismissShade= */ true, ) + } UserActionModel.NAVIGATE_TO_USER_MANAGEMENT -> activityStarter.startActivity( Intent(Settings.ACTION_USER_SETTINGS), @@ -493,7 +493,7 @@ constructor( fun showUserSwitcher(context: Context, expandable: Expandable) { if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) { - showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog) + showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable)) return } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt index 85c29647719b..14cc3e783fed 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt @@ -18,11 +18,13 @@ package com.android.systemui.user.domain.model import android.os.UserHandle +import com.android.systemui.animation.Expandable import com.android.systemui.qs.user.UserSwitchDialogController /** Encapsulates a request to show a dialog. */ sealed class ShowDialogRequestModel( open val dialogShower: UserSwitchDialogController.DialogShower? = null, + open val expandable: Expandable? = null, ) { data class ShowAddUserDialog( val userHandle: UserHandle, @@ -45,5 +47,7 @@ sealed class ShowDialogRequestModel( ) : ShowDialogRequestModel(dialogShower) /** Show the user switcher dialog */ - object ShowUserSwitcherDialog : ShowDialogRequestModel() + data class ShowUserSwitcherDialog( + override val expandable: Expandable?, + ) : ShowDialogRequestModel() } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt new file mode 100644 index 000000000000..3fe2a7b19851 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.user.ui.dialog + +import android.app.Dialog +import android.content.DialogInterface +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower + +/** Extracted from [UserSwitchDialogController] */ +class DialogShowerImpl( + private val animateFrom: Dialog, + private val dialogLaunchAnimator: DialogLaunchAnimator, +) : DialogInterface by animateFrom, DialogShower { + override fun showDialog(dialog: Dialog, cuj: DialogCuj) { + dialogLaunchAnimator.showFromDialog(dialog, animateFrom = animateFrom, cuj) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt index ed2589889435..b8ae257aaac5 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt @@ -60,6 +60,7 @@ class UserSwitchDialog( setView(gridFrame) adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid)) + adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator)) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index 41410542204c..79721b370c21 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -66,12 +66,6 @@ constructor( private fun startHandlingDialogShowRequests() { applicationScope.get().launch { interactor.get().dialogShowRequests.filterNotNull().collect { request -> - currentDialog?.let { - if (it.isShowing) { - it.cancel() - } - } - val (dialog, dialogCuj) = when (request) { is ShowDialogRequestModel.ShowAddUserDialog -> @@ -133,7 +127,10 @@ constructor( } currentDialog = dialog - if (request.dialogShower != null && dialogCuj != null) { + val controller = request.expandable?.dialogLaunchController(dialogCuj) + if (controller != null) { + dialogLaunchAnimator.get().show(dialog, controller) + } else if (request.dialogShower != null && dialogCuj != null) { request.dialogShower?.showDialog(dialog, dialogCuj) } else { dialog.show() diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index db1853d37817..52eef421eb8c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -1487,6 +1487,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, .setDuration(mDialogHideAnimationDurationMs) .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) .withEndAction(() -> mHandler.postDelayed(() -> { + mController.notifyVisible(false); mDialog.dismiss(); tryToRemoveCaptionsTooltip(); mIsAnimatingDismiss = false; @@ -1497,7 +1498,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS, mDialogHideAnimationDurationMs)).start(); checkODICaptionsTooltip(true); - mController.notifyVisible(false); synchronized (mSafetyWarningLock) { if (mSafetyWarning != null) { if (D.BUG) Log.d(TAG, "SafetyWarning dismissed"); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index e8f8e25364b3..c76b127a161c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -15,6 +15,7 @@ */ package com.android.keyguard +import com.android.systemui.statusbar.CommandQueue import android.content.BroadcastReceiver import android.testing.AndroidTestingRunner import android.view.View @@ -81,8 +82,10 @@ class ClockEventControllerTest : SysuiTestCase() { @Mock private lateinit var largeClockEvents: ClockFaceEvents @Mock private lateinit var parentView: View @Mock private lateinit var transitionRepository: KeyguardTransitionRepository + @Mock private lateinit var commandQueue: CommandQueue private lateinit var repository: FakeKeyguardRepository - @Mock private lateinit var logBuffer: LogBuffer + @Mock private lateinit var smallLogBuffer: LogBuffer + @Mock private lateinit var largeLogBuffer: LogBuffer private lateinit var underTest: ClockEventController @Before @@ -99,7 +102,7 @@ class ClockEventControllerTest : SysuiTestCase() { repository = FakeKeyguardRepository() underTest = ClockEventController( - KeyguardInteractor(repository = repository), + KeyguardInteractor(repository = repository, commandQueue = commandQueue), KeyguardTransitionInteractor(repository = transitionRepository), broadcastDispatcher, batteryController, @@ -109,7 +112,8 @@ class ClockEventControllerTest : SysuiTestCase() { context, mainExecutor, bgExecutor, - logBuffer, + smallLogBuffer, + largeLogBuffer, featureFlags ) underTest.clock = clock diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index c8e753844c64..9a9acf3dd986 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -48,6 +48,7 @@ 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.log.LogBuffer; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.shared.clocks.ClockRegistry; @@ -115,6 +116,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { private FrameLayout mLargeClockFrame; @Mock private SecureSettings mSecureSettings; + @Mock + private LogBuffer mLogBuffer; private final View mFakeSmartspaceView = new View(mContext); @@ -156,7 +159,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { mSecureSettings, mExecutor, mDumpManager, - mClockEventController + mClockEventController, + mLogBuffer ); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 254f9531ef83..8dc1e8fba600 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; @@ -189,6 +190,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0); + assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE); } @Test @@ -198,6 +200,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0); + assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE); } @Test @@ -212,6 +215,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // only big clock is removed at switch assertThat(mLargeClockFrame.getParent()).isNull(); assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0); + assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE); } @Test @@ -223,6 +227,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { // only big clock is removed at switch assertThat(mLargeClockFrame.getParent()).isNull(); assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0); + assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index be4bbdf5adbc..dfad15d68375 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -24,6 +24,7 @@ import android.graphics.Rect; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.ClockAnimations; @@ -65,6 +66,8 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { ScreenOffAnimationController mScreenOffAnimationController; @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; + @Mock + KeyguardLogger mKeyguardLogger; private KeyguardStatusViewController mController; @@ -81,7 +84,8 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mConfigurationController, mDozeParameters, mFeatureFlags, - mScreenOffAnimationController); + mScreenOffAnimationController, + mKeyguardLogger); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 13cd328d00e0..df6ce2fec5e6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -92,6 +92,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.service.dreams.IDreamManager; import android.service.trust.TrustAgentService; import android.telephony.ServiceState; @@ -116,6 +117,7 @@ import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated; import com.android.keyguard.logging.KeyguardUpdateMonitorLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.log.SessionTracker; @@ -142,6 +144,7 @@ import org.mockito.internal.util.reflection.FieldSetter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; @@ -233,6 +236,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private GlobalSettings mGlobalSettings; private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; + @Mock + private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider; private final int mCurrentUserId = 100; @@ -1250,7 +1255,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled() + public void startsListeningForSfps_whenKeyguardIsVisible_ifRequireInteractiveToAuthEnabled() throws RemoteException { // SFPS supported and enrolled final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); @@ -1258,12 +1263,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mAuthController.getSfpsProps()).thenReturn(props); when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); - // WHEN require screen on to auth is disabled, and keyguard is not awake - when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0); - mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref(); - - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true); + // WHEN require interactive to auth is disabled, and keyguard is not awake + when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false); // Preconditions for sfps auth to run keyguardNotGoingAway(); @@ -1279,9 +1280,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // THEN we should listen for sfps when screen off, because require screen on is disabled assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); - // WHEN require screen on to auth is enabled, and keyguard is not awake - when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1); - mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref(); + // WHEN require interactive to auth is enabled, and keyguard is not awake + when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(true); // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse(); @@ -1295,6 +1295,61 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); } + @Test + public void notListeningForSfps_whenGoingToSleep_ifRequireInteractiveToAuthEnabled() + throws RemoteException { + // GIVEN SFPS supported and enrolled + final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); + props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON)); + when(mAuthController.getSfpsProps()).thenReturn(props); + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + + // GIVEN Preconditions for sfps auth to run + keyguardNotGoingAway(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + statusBarShadeIsLocked(); + + // WHEN require interactive to auth is enabled & keyguard is going to sleep + when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(true); + deviceGoingToSleep(); + + mTestableLooper.processAllMessages(); + + // THEN we should NOT listen for sfps because device is going to sleep + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse(); + } + + @Test + public void listeningForSfps_whenGoingToSleep_ifRequireInteractiveToAuthDisabled() + throws RemoteException { + // GIVEN SFPS supported and enrolled + final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); + props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON)); + when(mAuthController.getSfpsProps()).thenReturn(props); + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + + // GIVEN Preconditions for sfps auth to run + keyguardNotGoingAway(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + statusBarShadeIsLocked(); + + // WHEN require interactive to auth is disabled & keyguard is going to sleep + when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false); + deviceGoingToSleep(); + + mTestableLooper.processAllMessages(); + + // THEN we should listen for sfps because screen on to auth is disabled + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + } private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal( @FingerprintSensorProperties.SensorType int sensorType) { @@ -2407,7 +2462,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mPowerManager, mTrustManager, mSubscriptionManager, mUserManager, mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager, mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager, - mFaceWakeUpTriggersConfig); + mFaceWakeUpTriggersConfig, Optional.of(mInteractiveToAuthProvider)); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index e4c41a7ed804..05bd1e482950 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -49,6 +49,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -91,6 +92,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected @Mock AuthRippleController mAuthRippleController; protected @Mock FeatureFlags mFeatureFlags; protected @Mock KeyguardTransitionRepository mTransitionRepository; + protected @Mock CommandQueue mCommandQueue; protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); protected LockIconViewController mUnderTest; @@ -157,7 +159,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mAuthRippleController, mResources, new KeyguardTransitionInteractor(mTransitionRepository), - new KeyguardInteractor(new FakeKeyguardRepository()), + new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue), mFeatureFlags ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index 83bf1834989b..ace0ccb6a25b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -739,7 +739,7 @@ public class AuthControllerTest extends SysuiTestCase { public void testForwardsDozeEvents() throws RemoteException { when(mStatusBarStateController.isDozing()).thenReturn(true); when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); - mAuthController.setBiometicContextListener(mContextListener); + mAuthController.setBiometricContextListener(mContextListener); mStatusBarStateListenerCaptor.getValue().onDozingChanged(true); mStatusBarStateListenerCaptor.getValue().onDozingChanged(false); @@ -754,7 +754,7 @@ public class AuthControllerTest extends SysuiTestCase { public void testForwardsWakeEvents() throws RemoteException { when(mStatusBarStateController.isDozing()).thenReturn(false); when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE); - mAuthController.setBiometicContextListener(mContextListener); + mAuthController.setBiometricContextListener(mContextListener); mWakefullnessObserverCaptor.getValue().onStartedGoingToSleep(); mWakefullnessObserverCaptor.getValue().onFinishedGoingToSleep(); 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 53bc2c231d0c..1ef119d7fb16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -43,6 +43,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager @@ -52,7 +53,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager 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.time.SystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -95,7 +95,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var transitionController: LockscreenShadeTransitionController @Mock private lateinit var configurationController: ConfigurationController - @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController @@ -106,7 +105,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsEnrollView: UdfpsEnrollView @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator @Mock private lateinit var featureFlags: FeatureFlags - @Mock private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor + @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } @@ -138,10 +138,10 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { context, fingerprintManager, inflater, windowManager, accessibilityManager, statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager, keyguardUpdateMonitor, dialogManager, dumpManager, transitionController, - configurationController, systemClock, keyguardStateController, + configurationController, keyguardStateController, unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator, featureFlags, - mPrimaryBouncerInteractor, isDebuggable + primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, ) 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 b061eb395119..0c34e54d2ec4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -81,6 +81,7 @@ 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.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -202,6 +203,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock private SinglePointerTouchProcessor mSinglePointerTouchProcessor; + @Mock + private AlternateBouncerInteractor mAlternateBouncerInteractor; // Capture listeners so that they can be used to send events @Captor @@ -292,7 +295,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mDisplayManager, mHandler, mConfigurationController, mSystemClock, mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor, - mPrimaryBouncerInteractor, mSinglePointerTouchProcessor); + mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, + mAlternateBouncerInteractor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -406,7 +410,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN overlay was showing and the udfps bouncer is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); // WHEN the overlay is hidden mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java index 3c61382d9446..9c32c38e665c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java @@ -30,6 +30,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeExpansionChangeEvent; @@ -43,7 +44,6 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; -import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -73,9 +73,9 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase { protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator; protected @Mock KeyguardBouncer mBouncer; protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; + protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor; protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); - protected FakeSystemClock mSystemClock = new FakeSystemClock(); protected UdfpsKeyguardViewController mController; @@ -86,10 +86,6 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase { private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor; protected List<ShadeExpansionListener> mExpansionListeners; - private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateBouncer> - mAlternateBouncerCaptor; - protected StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer; - private @Captor ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor; protected KeyguardStateController.Callback mKeyguardStateControllerCallback; @@ -135,12 +131,6 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase { } } - protected void captureAltAuthInterceptor() { - verify(mStatusBarKeyguardViewManager).setAlternateBouncer( - mAlternateBouncerCaptor.capture()); - mAlternateBouncer = mAlternateBouncerCaptor.getValue(); - } - protected void captureKeyguardStateControllerCallback() { verify(mKeyguardStateController).addCallback( mKeyguardStateControllerCallbackCaptor.capture()); @@ -160,6 +150,7 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase { protected UdfpsKeyguardViewController createUdfpsKeyguardViewController( boolean useModernBouncer, boolean useExpandedOverlay) { mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer); + mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer); mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay); when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn( useModernBouncer ? null : mBouncer); @@ -172,14 +163,14 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase { mDumpManager, mLockscreenShadeTransitionController, mConfigurationController, - mSystemClock, mKeyguardStateController, mUnlockedScreenOffAnimationController, mDialogManager, mUdfpsController, mActivityLaunchAnimator, mFeatureFlags, - mPrimaryBouncerInteractor); + mPrimaryBouncerInteractor, + mAlternateBouncerInteractor); return controller; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index babe5334e3eb..813eeeb557b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -19,18 +19,15 @@ package com.android.systemui.biometrics; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; +import android.testing.TestableLooper; import android.view.MotionEvent; import androidx.test.filters.SmallTest; @@ -46,7 +43,8 @@ import org.mockito.Captor; @SmallTest @RunWith(AndroidTestingRunner.class) -@RunWithLooper + +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest { private @Captor ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> mBouncerExpansionCallbackCaptor; @@ -72,8 +70,6 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController assertTrue(mController.shouldPauseAuth()); } - - @Test public void testRegistersExpansionChangedListenerOnAttached() { mController.onViewAttached(); @@ -237,85 +233,9 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController public void testOverrideShouldPauseAuthOnShadeLocked() { mController.onViewAttached(); captureStatusBarStateListeners(); - captureAltAuthInterceptor(); sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); assertTrue(mController.shouldPauseAuth()); - - mAlternateBouncer.showAlternateBouncer(); // force show - assertFalse(mController.shouldPauseAuth()); - assertTrue(mAlternateBouncer.isShowingAlternateBouncer()); - - mAlternateBouncer.hideAlternateBouncer(); // stop force show - assertTrue(mController.shouldPauseAuth()); - assertFalse(mAlternateBouncer.isShowingAlternateBouncer()); - } - - @Test - public void testOnDetachedStateReset() { - // GIVEN view is attached - mController.onViewAttached(); - captureAltAuthInterceptor(); - - // WHEN view is detached - mController.onViewDetached(); - - // THEN remove alternate auth interceptor - verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAlternateBouncer); - } - - @Test - public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() { - // GIVEN view is attached - mController.onViewAttached(); - captureAltAuthInterceptor(); - - // GIVEN udfps bouncer isn't showing - mAlternateBouncer.hideAlternateBouncer(); - - // WHEN touch is observed outside the view - mController.onTouchOutsideView(); - - // THEN bouncer / alt auth methods are never called - verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean()); - verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean()); - verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean()); - } - - @Test - public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() { - // GIVEN view is attached - mController.onViewAttached(); - captureAltAuthInterceptor(); - - // GIVEN udfps bouncer is showing - mAlternateBouncer.showAlternateBouncer(); - - // WHEN touch is observed outside the view 200ms later (just within threshold) - mSystemClock.advanceTime(200); - mController.onTouchOutsideView(); - - // THEN bouncer / alt auth methods are never called because not enough time has passed - verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean()); - verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean()); - verify(mStatusBarKeyguardViewManager, never()).hideAlternateBouncer(anyBoolean()); - } - - @Test - public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showPrimaryBouncer() { - // GIVEN view is attached - mController.onViewAttached(); - captureAltAuthInterceptor(); - - // GIVEN udfps bouncer is showing - mAlternateBouncer.showAlternateBouncer(); - - // WHEN touch is observed outside the view 205ms later - mSystemClock.advanceTime(205); - mController.onTouchOutsideView(); - - // THEN show the bouncer - verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(eq(true)); } @Test @@ -334,25 +254,6 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController } @Test - public void testShowUdfpsBouncer() { - // GIVEN view is attached and status bar expansion is 0 - mController.onViewAttached(); - captureStatusBarExpansionListeners(); - captureKeyguardStateControllerCallback(); - captureAltAuthInterceptor(); - updateStatusBarExpansion(0, true); - reset(mView); - when(mView.getContext()).thenReturn(mResourceContext); - when(mResourceContext.getString(anyInt())).thenReturn("test string"); - - // WHEN status bar expansion is 0 but udfps bouncer is requested - mAlternateBouncer.showAlternateBouncer(); - - // THEN alpha is 255 - verify(mView).setUnpausedAlpha(255); - } - - @Test public void testTransitionToFullShadeProgress() { // GIVEN view is attached and status bar expansion is 1f mController.onViewAttached(); @@ -370,24 +271,6 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController } @Test - public void testShowUdfpsBouncer_transitionToFullShadeProgress() { - // GIVEN view is attached and status bar expansion is 1f - mController.onViewAttached(); - captureStatusBarExpansionListeners(); - captureKeyguardStateControllerCallback(); - captureAltAuthInterceptor(); - updateStatusBarExpansion(1f, true); - mAlternateBouncer.showAlternateBouncer(); - reset(mView); - - // WHEN we're transitioning to the full shade - mController.setTransitionToFullShadeProgress(1.0f); - - // THEN alpha is 255 (b/c udfps bouncer is requested) - verify(mView).setUnpausedAlpha(255); - } - - @Test public void testUpdatePanelExpansion_pauseAuth() { // GIVEN view is attached + on the keyguard mController.onViewAttached(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index 2d412dcaa909..3b4f7e10c806 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -21,26 +21,35 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.repository.BiometricRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.KeyguardBouncer import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.util.time.SystemClock import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestCoroutineScope import kotlinx.coroutines.yield +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -57,6 +66,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle keyguardBouncerRepository = KeyguardBouncerRepository( mock(com.android.keyguard.ViewMediatorCallback::class.java), + FakeSystemClock(), TestCoroutineScope(), bouncerLogger, ) @@ -77,15 +87,43 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle mock(KeyguardBypassController::class.java), mKeyguardUpdateMonitor ) + mAlternateBouncerInteractor = + AlternateBouncerInteractor( + keyguardBouncerRepository, + mock(BiometricRepository::class.java), + mock(SystemClock::class.java), + mock(KeyguardUpdateMonitor::class.java), + mock(FeatureFlags::class.java) + ) return createUdfpsKeyguardViewController( /* useModernBouncer */ true, /* useExpandedOverlay */ false ) } - /** After migration, replaces LockIconViewControllerTest version */ @Test - fun testShouldPauseAuthBouncerShowing() = + fun shadeLocked_showAlternateBouncer_unpauseAuth() = + runBlocking(IMMEDIATE) { + // GIVEN view is attached + on the SHADE_LOCKED (udfps view not showing) + mController.onViewAttached() + captureStatusBarStateListeners() + sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED) + + // WHEN alternate bouncer is requested + val job = mController.listenForAlternateBouncerVisibility(this) + keyguardBouncerRepository.setAlternateVisible(true) + yield() + + // THEN udfps view will animate in & pause auth is updated to NOT pause + verify(mView).animateInUdfpsBouncer(any()) + assertFalse(mController.shouldPauseAuth()) + + job.cancel() + } + + /** After migration to MODERN_BOUNCER, replaces UdfpsKeyguardViewControllerTest version */ + @Test + fun shouldPauseAuthBouncerShowing() = runBlocking(IMMEDIATE) { // GIVEN view attached and we're on the keyguard mController.onViewAttached() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index d550b927154c..8255a1452bd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -80,15 +80,6 @@ class UdfpsViewTest : SysuiTestCase() { } @Test - fun forwardsEvents() { - view.dozeTimeTick() - verify(animationViewController).dozeTimeTick() - - view.onTouchOutsideView() - verify(animationViewController).onTouchOutsideView() - } - - @Test fun layoutSizeFitsSensor() { val params = withArgCaptor<RectF> { verify(animationViewController).onSensorRectUpdated(capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt new file mode 100644 index 000000000000..af46d9b97abf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.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.biometrics.udfps + +import android.graphics.Point +import android.graphics.Rect +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters +import org.mockito.Mockito.spy +import org.mockito.Mockito.`when` as whenEver + +@SmallTest +@RunWith(Parameterized::class) +class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() { + val underTest = spy(EllipseOverlapDetector(neededPoints = 1)) + + @Before + fun setUp() { + // Use one single center point for testing, required or total number of points may change + whenEver(underTest.calculateSensorPoints(SENSOR)) + .thenReturn(listOf(Point(SENSOR.centerX(), SENSOR.centerY()))) + } + + @Test + fun isGoodOverlap() { + val touchData = + TOUCH_DATA.copy( + x = testCase.x.toFloat(), + y = testCase.y.toFloat(), + minor = testCase.minor, + major = testCase.major + ) + val actual = underTest.isGoodOverlap(touchData, SENSOR) + + assertThat(actual).isEqualTo(testCase.expected) + } + + data class TestCase( + val x: Int, + val y: Int, + val minor: Float, + val major: Float, + val expected: Boolean + ) + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun data(): List<TestCase> = + listOf( + genTestCases( + innerXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()), + innerYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()), + outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1), + outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1), + minor = 300f, + major = 300f, + expected = true + ), + genTestCases( + innerXs = listOf(SENSOR.left, SENSOR.right), + innerYs = listOf(SENSOR.top, SENSOR.bottom), + outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1), + outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1), + minor = 100f, + major = 100f, + expected = false + ) + ) + .flatten() + } +} + +/* Placeholder touch parameters. */ +private const val POINTER_ID = 42 +private const val NATIVE_MINOR = 2.71828f +private const val NATIVE_MAJOR = 3.14f +private const val ORIENTATION = 0f // used for perfect circles +private const val TIME = 12345699L +private const val GESTURE_START = 12345600L + +/* Template [NormalizedTouchData]. */ +private val TOUCH_DATA = + NormalizedTouchData( + POINTER_ID, + x = 0f, + y = 0f, + NATIVE_MINOR, + NATIVE_MAJOR, + ORIENTATION, + TIME, + GESTURE_START + ) + +private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */) + +private fun genTestCases( + innerXs: List<Int>, + innerYs: List<Int>, + outerXs: List<Int>, + outerYs: List<Int>, + minor: Float, + major: Float, + expected: Boolean +): List<EllipseOverlapDetectorTest.TestCase> { + return (innerXs + outerXs).flatMap { x -> + (innerYs + outerYs).map { y -> + EllipseOverlapDetectorTest.TestCase(x, y, minor, major, expected) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt index 95c53b408056..56043e306c16 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt @@ -221,6 +221,14 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400 private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600 +/* Placeholder touch parameters. */ +private const val POINTER_ID = 42 +private const val NATIVE_MINOR = 2.71828f +private const val NATIVE_MAJOR = 3.14f +private const val ORIENTATION = 1.2345f +private const val TIME = 12345699L +private const val GESTURE_START = 12345600L + /* * ROTATION_0 map: * _ _ _ _ @@ -244,6 +252,7 @@ private val ROTATION_0_NATIVE_SENSOR_BOUNDS = private val ROTATION_0_INPUTS = OrientationBasedInputs( rotation = Surface.ROTATION_0, + nativeOrientation = ORIENTATION, nativeXWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterX(), nativeYWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterY(), nativeXOutsideSensor = 250f, @@ -271,6 +280,7 @@ private val ROTATION_90_NATIVE_SENSOR_BOUNDS = private val ROTATION_90_INPUTS = OrientationBasedInputs( rotation = Surface.ROTATION_90, + nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2), nativeXWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterX(), nativeYWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterY(), nativeXOutsideSensor = 150f, @@ -304,20 +314,13 @@ private val ROTATION_270_NATIVE_SENSOR_BOUNDS = private val ROTATION_270_INPUTS = OrientationBasedInputs( rotation = Surface.ROTATION_270, + nativeOrientation = (ORIENTATION + Math.PI.toFloat() / 2), nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(), nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(), nativeXOutsideSensor = 450f, nativeYOutsideSensor = 250f, ) -/* Placeholder touch parameters. */ -private const val POINTER_ID = 42 -private const val NATIVE_MINOR = 2.71828f -private const val NATIVE_MAJOR = 3.14f -private const val ORIENTATION = 1.23f -private const val TIME = 12345699L -private const val GESTURE_START = 12345600L - /* Template [MotionEvent]. */ private val MOTION_EVENT = obtainMotionEvent( @@ -352,6 +355,7 @@ private val NORMALIZED_TOUCH_DATA = */ private data class OrientationBasedInputs( @Rotation val rotation: Int, + val nativeOrientation: Float, val nativeXWithinSensor: Float, val nativeYWithinSensor: Float, val nativeXOutsideSensor: Float, @@ -404,6 +408,7 @@ private fun genPositiveTestCases( y = nativeY * scaleFactor, minor = NATIVE_MINOR * scaleFactor, major = NATIVE_MAJOR * scaleFactor, + orientation = orientation.nativeOrientation ) val expectedTouchData = NORMALIZED_TOUCH_DATA.copy( diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index 0fadc138637a..e4df754ec96a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -106,6 +106,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { mClassifiers.add(mClassifierB); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mFalsingDataProvider.isFolded()).thenReturn(true); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, @@ -121,6 +122,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue(); mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true); + mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 4281ee0f139f..ae38eb67c431 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -89,25 +89,27 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mClassifiers.add(mClassifierA); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mFalsingDataProvider.isFolded()).thenReturn(true); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, mAccessibilityManager, false, mFakeFeatureFlags); mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); + mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true); } @Test public void testA11yDisablesGesture() { - assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue(); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); - assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); } @Test public void testA11yDisablesTap() { - assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); + assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue(); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); - assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); + assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); } @@ -179,4 +181,11 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { when(mFalsingDataProvider.isA11yAction()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); } + + @Test + public void testSkipUnfolded() { + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); + when(mFalsingDataProvider.isFolded()).thenReturn(false); + assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java index 5fa7214f07ff..94cf384267ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java @@ -16,6 +16,7 @@ package com.android.systemui.classifier; +import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.util.DisplayMetrics; import android.view.MotionEvent; @@ -38,6 +39,7 @@ public class ClassifierTest extends SysuiTestCase { private float mOffsetY = 0; @Mock private BatteryController mBatteryController; + private FoldStateListener mFoldStateListener = new FoldStateListener(mContext); private final DockManagerFake mDockManager = new DockManagerFake(); public void setup() { @@ -47,7 +49,8 @@ public class ClassifierTest extends SysuiTestCase { displayMetrics.ydpi = 100; displayMetrics.widthPixels = 1000; displayMetrics.heightPixels = 1000; - mDataProvider = new FalsingDataProvider(displayMetrics, mBatteryController, mDockManager); + mDataProvider = new FalsingDataProvider( + displayMetrics, mBatteryController, mFoldStateListener, mDockManager); } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index d315c2da0703..c451a1e754c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.MotionEvent; @@ -50,6 +51,8 @@ public class FalsingDataProviderTest extends ClassifierTest { private FalsingDataProvider mDataProvider; @Mock private BatteryController mBatteryController; + @Mock + private FoldStateListener mFoldStateListener; private final DockManagerFake mDockManager = new DockManagerFake(); @Before @@ -61,7 +64,8 @@ public class FalsingDataProviderTest extends ClassifierTest { displayMetrics.ydpi = 100; displayMetrics.widthPixels = 1000; displayMetrics.heightPixels = 1000; - mDataProvider = new FalsingDataProvider(displayMetrics, mBatteryController, mDockManager); + mDataProvider = new FalsingDataProvider( + displayMetrics, mBatteryController, mFoldStateListener, mDockManager); } @After @@ -316,4 +320,16 @@ public class FalsingDataProviderTest extends ClassifierTest { mDataProvider.onA11yAction(); assertThat(mDataProvider.isA11yAction()).isTrue(); } + + @Test + public void test_FoldedState_Folded() { + when(mFoldStateListener.getFolded()).thenReturn(true); + assertThat(mDataProvider.isFolded()).isTrue(); + } + + @Test + public void test_FoldedState_Unfolded() { + when(mFoldStateListener.getFolded()).thenReturn(false); + assertThat(mDataProvider.isFolded()).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 465976607a01..0a03b2c87f71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -51,6 +51,7 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any @@ -86,9 +87,9 @@ class CustomizationProviderTest : SysuiTestCase() { @Mock private lateinit var backgroundHandler: Handler @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage @Mock private lateinit var launchAnimator: DialogLaunchAnimator + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: CustomizationProvider - private lateinit var testScope: TestScope @Before @@ -160,6 +161,7 @@ class CustomizationProviderTest : SysuiTestCase() { keyguardInteractor = KeyguardInteractor( repository = FakeKeyguardRepository(), + commandQueue = commandQueue, ), registry = mock(), lockPatternUtils = lockPatternUtils, 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 5196f4962a6c..122d7fdbfa47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -66,6 +66,7 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -135,6 +136,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock AuthController mAuthController; private @Mock ShadeExpansionStateManager mShadeExpansionStateManager; private @Mock ShadeWindowLogger mShadeWindowLogger; + private @Mock FeatureFlags mFeatureFlags; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -444,6 +446,45 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { TestableLooper.get(this).processAllMessages(); } + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testKeyguardDelayedOnGoingToSleep_ifScreenOffAnimationWillPlayButIsntPlayingYet() { + mViewMediator.onSystemReady(); + TestableLooper.get(this).processAllMessages(); + + mViewMediator.setShowingLocked(false); + TestableLooper.get(this).processAllMessages(); + + mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); + TestableLooper.get(this).processAllMessages(); + + when(mScreenOffAnimationController.shouldDelayKeyguardShow()).thenReturn(true); + when(mScreenOffAnimationController.isKeyguardShowDelayed()).thenReturn(false); + mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false); + TestableLooper.get(this).processAllMessages(); + + assertFalse(mViewMediator.isShowingAndNotOccluded()); + } + + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testKeyguardNotDelayedOnGoingToSleep_ifScreenOffAnimationWillNotPlay() { + mViewMediator.onSystemReady(); + TestableLooper.get(this).processAllMessages(); + + mViewMediator.setShowingLocked(false); + TestableLooper.get(this).processAllMessages(); + + mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); + TestableLooper.get(this).processAllMessages(); + + when(mScreenOffAnimationController.shouldDelayKeyguardShow()).thenReturn(false); + mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false); + TestableLooper.get(this).processAllMessages(); + + assertTrue(mViewMediator.isShowingAndNotOccluded()); + } + private void createAndStartViewMediator() { mViewMediator = new KeyguardViewMediator( mContext, @@ -471,6 +512,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mScreenOnCoordinator, mInteractionJankMonitor, mDreamOverlayStateController, + mFeatureFlags, () -> mShadeController, () -> mNotificationShadeWindowController, () -> mActivityLaunchAnimator, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java index f32d76bb601e..39a453da7f92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java @@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -51,7 +52,12 @@ public class WakefulnessLifecycleTest extends SysuiTestCase { public void setUp() throws Exception { mWallpaperManager = mock(IWallpaperManager.class); mWakefulness = - new WakefulnessLifecycle(mContext, mWallpaperManager, mock(DumpManager.class)); + new WakefulnessLifecycle( + mContext, + mWallpaperManager, + new FakeSystemClock(), + mock(DumpManager.class) + ); mWakefulnessObserver = mock(WakefulnessLifecycle.Observer.class); mWakefulness.addObserver(mWakefulnessObserver); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt new file mode 100644 index 000000000000..a92dd3b92397 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +import android.app.admin.DevicePolicyManager +import android.content.Intent +import android.content.pm.UserInfo +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.widget.LockPatternUtils +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +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.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidTestingRunner::class) +class BiometricRepositoryTest : SysuiTestCase() { + private lateinit var underTest: BiometricRepository + + @Mock private lateinit var authController: AuthController + @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var devicePolicyManager: DevicePolicyManager + private lateinit var userRepository: FakeUserRepository + + private lateinit var testDispatcher: TestDispatcher + private lateinit var testScope: TestScope + private var testableLooper: TestableLooper? = null + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + userRepository = FakeUserRepository() + } + + private suspend fun createBiometricRepository() { + userRepository.setUserInfos(listOf(PRIMARY_USER)) + userRepository.setSelectedUserInfo(PRIMARY_USER) + underTest = + BiometricRepositoryImpl( + context = context, + lockPatternUtils = lockPatternUtils, + broadcastDispatcher = fakeBroadcastDispatcher, + authController = authController, + userRepository = userRepository, + devicePolicyManager = devicePolicyManager, + scope = testScope.backgroundScope, + backgroundDispatcher = testDispatcher, + looper = testableLooper!!.looper, + ) + } + + @Test + fun fingerprintEnrollmentChange() = + testScope.runTest { + createBiometricRepository() + val fingerprintEnabledByDevicePolicy = collectLastValue(underTest.isFingerprintEnrolled) + runCurrent() + + val captor = argumentCaptor<AuthController.Callback>() + verify(authController).addCallback(captor.capture()) + whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true) + captor.value.onEnrollmentsChanged( + BiometricType.UNDER_DISPLAY_FINGERPRINT, + PRIMARY_USER_ID, + true + ) + assertThat(fingerprintEnabledByDevicePolicy()).isTrue() + + whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(false) + captor.value.onEnrollmentsChanged( + BiometricType.UNDER_DISPLAY_FINGERPRINT, + PRIMARY_USER_ID, + false + ) + assertThat(fingerprintEnabledByDevicePolicy()).isFalse() + } + + @Test + fun strongBiometricAllowedChange() = + testScope.runTest { + createBiometricRepository() + val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed) + runCurrent() + + val captor = argumentCaptor<LockPatternUtils.StrongAuthTracker>() + verify(lockPatternUtils).registerStrongAuthTracker(captor.capture()) + + captor.value + .getStub() + .onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID) + testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper + assertThat(strongBiometricAllowed()).isTrue() + + captor.value + .getStub() + .onStrongAuthRequiredChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID) + testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper + assertThat(strongBiometricAllowed()).isFalse() + } + + @Test + fun fingerprintDisabledByDpmChange() = + testScope.runTest { + createBiometricRepository() + val fingerprintEnabledByDevicePolicy = + collectLastValue(underTest.isFingerprintEnabledByDevicePolicy) + runCurrent() + + whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt())) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) + broadcastDPMStateChange() + assertThat(fingerprintEnabledByDevicePolicy()).isFalse() + + whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt())).thenReturn(0) + broadcastDPMStateChange() + assertThat(fingerprintEnabledByDevicePolicy()).isTrue() + } + + private fun broadcastDPMStateChange() { + fakeBroadcastDispatcher.registeredReceivers.forEach { receiver -> + receiver.onReceive( + context, + Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED) + ) + } + } + + companion object { + private const val PRIMARY_USER_ID = 0 + private val PRIMARY_USER = + UserInfo( + /* id= */ PRIMARY_USER_ID, + /* name= */ "primary user", + /* flags= */ UserInfo.FLAG_PRIMARY + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt index 9970a6796ed6..969537d23111 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt @@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.ViewMediatorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.util.time.SystemClock import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestCoroutineScope import org.junit.Before @@ -34,6 +35,7 @@ import org.mockito.MockitoAnnotations @RunWith(JUnit4::class) class KeyguardBouncerRepositoryTest : SysuiTestCase() { + @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback @Mock private lateinit var bouncerLogger: TableLogBuffer lateinit var underTest: KeyguardBouncerRepository @@ -43,7 +45,12 @@ class KeyguardBouncerRepositoryTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) val testCoroutineScope = TestCoroutineScope() underTest = - KeyguardBouncerRepository(viewMediatorCallback, testCoroutineScope, bouncerLogger) + KeyguardBouncerRepository( + viewMediatorCallback, + systemClock, + testCoroutineScope, + bouncerLogger, + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index be712f699b7b..f997d18a57a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -24,6 +24,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.common.shared.model.Position +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.DozeHost import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback @@ -38,14 +39,17 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController +import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -68,6 +72,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var authController: AuthController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController + @Mock private lateinit var dozeParameters: DozeParameters private lateinit var underTest: KeyguardRepositoryImpl @@ -84,6 +89,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { keyguardStateController, keyguardUpdateMonitor, dozeTransitionListener, + dozeParameters, authController, dreamOverlayCallbackController, ) @@ -170,6 +176,26 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test + fun isAodAvailable() = runTest { + val flow = underTest.isAodAvailable + var isAodAvailable = collectLastValue(flow) + runCurrent() + + val callback = + withArgCaptor<DozeParameters.Callback> { verify(dozeParameters).addCallback(capture()) } + + whenever(dozeParameters.getAlwaysOn()).thenReturn(false) + callback.onAlwaysOnChange() + assertThat(isAodAvailable()).isEqualTo(false) + + whenever(dozeParameters.getAlwaysOn()).thenReturn(true) + callback.onAlwaysOnChange() + assertThat(isAodAvailable()).isEqualTo(true) + + flow.onCompletion { verify(dozeParameters).removeCallback(callback) } + } + + @Test fun isKeyguardOccluded() = runTest(UnconfinedTestDispatcher()) { whenever(keyguardStateController.isOccluded).thenReturn(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 5d2f0eb01de1..f8f2a56d4808 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -104,7 +104,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1)) assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN) - val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.9)) + val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1)) assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD) job.cancel() @@ -201,7 +201,10 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { ) ) fractions.forEachIndexed { index, fraction -> - assertThat(steps[index + 1]) + val step = steps[index + 1] + val truncatedValue = + BigDecimal(step.value.toDouble()).setScale(2, RoundingMode.HALF_UP).toFloat() + assertThat(step.copy(value = truncatedValue)) .isEqualTo( TransitionStep( from, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt new file mode 100644 index 000000000000..1da7241e58bd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.ViewMediatorCallback +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeBiometricRepository +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScope +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +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.mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class AlternateBouncerInteractorTest : SysuiTestCase() { + private lateinit var underTest: AlternateBouncerInteractor + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var biometricRepository: FakeBiometricRepository + @Mock private lateinit var systemClock: SystemClock + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var bouncerLogger: TableLogBuffer + private lateinit var featureFlags: FakeFeatureFlags + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + bouncerRepository = + KeyguardBouncerRepository( + mock(ViewMediatorCallback::class.java), + FakeSystemClock(), + TestCoroutineScope(), + bouncerLogger, + ) + biometricRepository = FakeBiometricRepository() + featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) } + underTest = + AlternateBouncerInteractor( + bouncerRepository, + biometricRepository, + systemClock, + keyguardUpdateMonitor, + featureFlags, + ) + } + + @Test + fun canShowAlternateBouncerForFingerprint_givenCanShow() { + givenCanShowAlternateBouncer() + assertTrue(underTest.canShowAlternateBouncerForFingerprint()) + } + + @Test + fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() { + givenCanShowAlternateBouncer() + bouncerRepository.setAlternateBouncerUIAvailable(false) + + assertFalse(underTest.canShowAlternateBouncerForFingerprint()) + } + + @Test + fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() { + givenCanShowAlternateBouncer() + biometricRepository.setFingerprintEnrolled(false) + + assertFalse(underTest.canShowAlternateBouncerForFingerprint()) + } + + @Test + fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() { + givenCanShowAlternateBouncer() + biometricRepository.setStrongBiometricAllowed(false) + + assertFalse(underTest.canShowAlternateBouncerForFingerprint()) + } + + @Test + fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() { + givenCanShowAlternateBouncer() + biometricRepository.setFingerprintEnabledByDevicePolicy(false) + + assertFalse(underTest.canShowAlternateBouncerForFingerprint()) + } + + @Test + fun show_whenCanShow() { + givenCanShowAlternateBouncer() + + assertTrue(underTest.show()) + assertTrue(bouncerRepository.isAlternateBouncerVisible.value) + } + + @Test + fun show_whenCannotShow() { + givenCannotShowAlternateBouncer() + + assertFalse(underTest.show()) + assertFalse(bouncerRepository.isAlternateBouncerVisible.value) + } + + @Test + fun hide_wasPreviouslyShowing() { + bouncerRepository.setAlternateVisible(true) + + assertTrue(underTest.hide()) + assertFalse(bouncerRepository.isAlternateBouncerVisible.value) + } + + @Test + fun hide_wasNotPreviouslyShowing() { + bouncerRepository.setAlternateVisible(false) + + assertFalse(underTest.hide()) + assertFalse(bouncerRepository.isAlternateBouncerVisible.value) + } + + private fun givenCanShowAlternateBouncer() { + bouncerRepository.setAlternateBouncerUIAvailable(true) + biometricRepository.setFingerprintEnrolled(true) + biometricRepository.setStrongBiometricAllowed(true) + biometricRepository.setFingerprintEnabledByDevicePolicy(true) + } + + private fun givenCannotShowAlternateBouncer() { + biometricRepository.setFingerprintEnrolled(false) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt new file mode 100644 index 000000000000..68d13d354a43 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.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.domain.interactor + +import android.app.StatusBarManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.CommandQueue.Callbacks +import com.android.systemui.util.mockito.argumentCaptor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.onCompletion +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.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class KeyguardInteractorTest : SysuiTestCase() { + @Mock private lateinit var commandQueue: CommandQueue + + private lateinit var underTest: KeyguardInteractor + private lateinit var repository: FakeKeyguardRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + repository = FakeKeyguardRepository() + underTest = KeyguardInteractor(repository, commandQueue) + } + + @Test + fun onCameraLaunchDetected() = runTest { + val flow = underTest.onCameraLaunchDetected + var cameraLaunchSource = collectLastValue(flow) + runCurrent() + + val captor = argumentCaptor<CommandQueue.Callbacks>() + verify(commandQueue).addCallback(captor.capture()) + + captor.value.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE) + + captor.value.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP + ) + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP) + + captor.value.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER + ) + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER) + + captor.value.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE + ) + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE) + + flow.onCompletion { verify(commandQueue).removeCallback(captor.value) } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 14b7c311d9af..43287b03b36a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any @@ -216,6 +217,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller @Mock private lateinit var expandable: Expandable @Mock private lateinit var launchAnimator: DialogLaunchAnimator + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: KeyguardQuickAffordanceInteractor @@ -286,7 +288,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { ) underTest = KeyguardQuickAffordanceInteractor( - keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()), + keyguardInteractor = + KeyguardInteractor( + repository = FakeKeyguardRepository(), + commandQueue = commandQueue + ), registry = FakeKeyguardQuickAffordanceRegistry( mapOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 972919a9d27a..b75a15da641a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock @@ -75,6 +76,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogLaunchAnimator + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: KeyguardQuickAffordanceInteractor @@ -152,7 +154,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceInteractor( - keyguardInteractor = KeyguardInteractor(repository = repository), + keyguardInteractor = + KeyguardInteractor(repository = repository, commandQueue = commandQueue), registry = FakeKeyguardQuickAffordanceRegistry( mapOf( 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 d2b7838274a9..b3cee2273012 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 @@ -33,6 +33,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.keyguard.util.KeyguardTransitionRunner import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.cancelChildren @@ -66,9 +67,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // Used to verify transition requests for test output @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository + @Mock private lateinit var commandQueue: CommandQueue private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor + private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor + private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor + private lateinit var fromGoneTransitionInteractor: FromGoneTransitionInteractor + private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor @Before fun setUp() { @@ -85,7 +91,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository), + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), shadeRepository = shadeRepository, keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), @@ -95,11 +101,47 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository), + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) fromDreamingTransitionInteractor.start() + + fromAodTransitionInteractor = + FromAodTransitionInteractor( + scope = testScope, + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardTransitionRepository = mockTransitionRepository, + keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), + ) + fromAodTransitionInteractor.start() + + fromGoneTransitionInteractor = + FromGoneTransitionInteractor( + scope = testScope, + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardTransitionRepository = mockTransitionRepository, + keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), + ) + fromGoneTransitionInteractor.start() + + fromDozingTransitionInteractor = + FromDozingTransitionInteractor( + scope = testScope, + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardTransitionRepository = mockTransitionRepository, + keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), + ) + fromDozingTransitionInteractor.start() + + fromOccludedTransitionInteractor = + FromOccludedTransitionInteractor( + scope = testScope, + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardTransitionRepository = mockTransitionRepository, + keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), + ) + fromOccludedTransitionInteractor.start() } @Test @@ -190,6 +232,289 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { coroutineContext.cancelChildren() } + @Test + fun `OCCLUDED to DOZING`() = + testScope.runTest { + // GIVEN a device with AOD not available + keyguardRepository.setAodAvailable(false) + runCurrent() + + // GIVEN a prior transition has run to OCCLUDED + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN the device begins to sleep + keyguardRepository.setWakefulnessModel(startingToSleep()) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.to).isEqualTo(KeyguardState.DOZING) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `OCCLUDED to AOD`() = + testScope.runTest { + // GIVEN a device with AOD available + keyguardRepository.setAodAvailable(true) + runCurrent() + + // GIVEN a prior transition has run to OCCLUDED + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN the device begins to sleep + keyguardRepository.setWakefulnessModel(startingToSleep()) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.to).isEqualTo(KeyguardState.AOD) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `LOCKSCREEN to DOZING`() = + testScope.runTest { + // GIVEN a device with AOD not available + keyguardRepository.setAodAvailable(false) + runCurrent() + + // GIVEN a prior transition has run to LOCKSCREEN + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN the device begins to sleep + keyguardRepository.setWakefulnessModel(startingToSleep()) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.DOZING) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `LOCKSCREEN to AOD`() = + testScope.runTest { + // GIVEN a device with AOD available + keyguardRepository.setAodAvailable(true) + runCurrent() + + // GIVEN a prior transition has run to LOCKSCREEN + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN the device begins to sleep + keyguardRepository.setWakefulnessModel(startingToSleep()) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.AOD) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `DOZING to LOCKSCREEN`() = + testScope.runTest { + // GIVEN a prior transition has run to DOZING + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DOZING, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN the device begins to wake + keyguardRepository.setWakefulnessModel(startingToWake()) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DOZING) + assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `GONE to DOZING`() = + testScope.runTest { + // GIVEN a device with AOD not available + keyguardRepository.setAodAvailable(false) + runCurrent() + + // GIVEN a prior transition has run to GONE + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN the device begins to sleep + keyguardRepository.setWakefulnessModel(startingToSleep()) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.GONE) + assertThat(info.to).isEqualTo(KeyguardState.DOZING) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun `GONE to AOD`() = + testScope.runTest { + // GIVEN a device with AOD available + keyguardRepository.setAodAvailable(true) + runCurrent() + + // GIVEN a prior transition has run to GONE + runner.startTransition( + testScope, + TransitionInfo( + ownerName = "", + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + animator = + ValueAnimator().apply { + duration = 10 + interpolator = Interpolators.LINEAR + }, + ) + ) + runCurrent() + reset(mockTransitionRepository) + + // WHEN the device begins to sleep + keyguardRepository.setWakefulnessModel(startingToSleep()) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(mockTransitionRepository).startTransition(capture()) + } + // THEN a transition to DOZING should occur + assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.GONE) + assertThat(info.to).isEqualTo(KeyguardState.AOD) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + private fun startingToWake() = WakefulnessModel( WakefulnessState.STARTING_TO_WAKE, @@ -197,4 +522,12 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { WakeSleepReason.OTHER, WakeSleepReason.OTHER ) + + private fun startingToSleep() = + WakefulnessModel( + WakefulnessState.STARTING_TO_SLEEP, + true, + WakeSleepReason.OTHER, + WakeSleepReason.OTHER + ) } 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 a2c2f711b1d4..022afdd61fc2 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 @@ -47,6 +47,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any @@ -84,6 +85,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogLaunchAnimator + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: KeyguardBottomAreaViewModel @@ -124,7 +126,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { ) repository = FakeKeyguardRepository() - val keyguardInteractor = KeyguardInteractor(repository = repository) + val keyguardInteractor = + KeyguardInteractor(repository = repository, commandQueue = commandQueue) whenever(userTracker.userHandle).thenReturn(mock()) whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED) 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 new file mode 100644 index 000000000000..739059126b04 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -0,0 +1,135 @@ +/* + * 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.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.AnimationParams +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.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: LockscreenToDreamingTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = KeyguardTransitionInteractor(repository) + underTest = LockscreenToDreamingTransitionViewModel(interactor) + } + + @Test + fun lockscreenFadeOut() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + // ...up to here + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + // Only three values should be present, since the dream overlay runs for a small + // fraction + // of the overall animation time + assertThat(values.size).isEqualTo(3) + assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA)) + assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA)) + assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA)) + + job.cancel() + } + + @Test + fun lockscreenTranslationY() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val pixels = 100 + val job = + underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + // ...up to here + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(3) + assertThat(values[0]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[1]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[2]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + job.cancel() + } + + private fun animValue(stepValue: Float, params: AnimationParams): Float { + val totalDuration = TO_DREAMING_DURATION + val startValue = (params.startTime / totalDuration).toFloat() + + val multiplier = (totalDuration / params.duration).toFloat() + return (stepValue - startValue) * multiplier + } + + private fun step(value: Float): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + value = value, + transitionState = TransitionState.RUNNING, + ownerName = "LockscreenToDreamingTransitionViewModelTest" + ) + } +} 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 new file mode 100644 index 000000000000..759345f51c59 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -0,0 +1,140 @@ +/* + * 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.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.AnimationParams +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.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: LockscreenToOccludedTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = KeyguardTransitionInteractor(repository) + underTest = LockscreenToOccludedTransitionViewModel(interactor) + } + + @Test + fun lockscreenFadeOut() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.4f)) + // ...up to here + repository.sendTransitionStep(step(0.7f)) + repository.sendTransitionStep(step(1f)) + + // Only 3 values should be present, since the dream overlay runs for a small fraction + // of the overall animation time + assertThat(values.size).isEqualTo(3) + assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA)) + assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA)) + assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA)) + + job.cancel() + } + + @Test + fun lockscreenTranslationY() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val pixels = 100 + val job = + underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + + // Should start running here... + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(1f)) + // ...up to here + + assertThat(values.size).isEqualTo(4) + assertThat(values[0]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[1]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.3f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[2]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(0.5f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + assertThat(values[3]) + .isEqualTo( + EMPHASIZED_ACCELERATE.getInterpolation( + animValue(1f, LOCKSCREEN_TRANSLATION_Y) + ) * pixels + ) + job.cancel() + } + + private fun animValue(stepValue: Float, params: AnimationParams): Float { + val totalDuration = TO_OCCLUDED_DURATION + val startValue = (params.startTime / totalDuration).toFloat() + + val multiplier = (totalDuration / params.duration).toFloat() + return (stepValue - startValue) * multiplier + } + + private fun step(value: Float): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = value, + transitionState = TransitionState.RUNNING, + ownerName = "LockscreenToOccludedTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt new file mode 100644 index 000000000000..411b1bd04c52 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt @@ -0,0 +1,64 @@ +/* + * 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.table + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class TableLogBufferFactoryTest : SysuiTestCase() { + private val dumpManager: DumpManager = mock() + private val systemClock = FakeSystemClock() + private val underTest = TableLogBufferFactory(dumpManager, systemClock) + + @Test + fun `create - always creates new instance`() { + val b1 = underTest.create(NAME_1, SIZE) + val b1_copy = underTest.create(NAME_1, SIZE) + val b2 = underTest.create(NAME_2, SIZE) + val b2_copy = underTest.create(NAME_2, SIZE) + + assertThat(b1).isNotSameInstanceAs(b1_copy) + assertThat(b1).isNotSameInstanceAs(b2) + assertThat(b2).isNotSameInstanceAs(b2_copy) + } + + @Test + fun `getOrCreate - reuses instance`() { + val b1 = underTest.getOrCreate(NAME_1, SIZE) + val b1_copy = underTest.getOrCreate(NAME_1, SIZE) + val b2 = underTest.getOrCreate(NAME_2, SIZE) + val b2_copy = underTest.getOrCreate(NAME_2, SIZE) + + assertThat(b1).isSameInstanceAs(b1_copy) + assertThat(b2).isSameInstanceAs(b2_copy) + assertThat(b1).isNotSameInstanceAs(b2) + assertThat(b1_copy).isNotSameInstanceAs(b2_copy) + } + + companion object { + const val NAME_1 = "name 1" + const val NAME_2 = "name 2" + + const val SIZE = 8 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index 6ca34e0bb7ce..e4e95e580a7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -17,8 +17,10 @@ package com.android.systemui.media.controls.ui import android.app.PendingIntent +import android.content.res.Configuration import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.util.MathUtils.abs import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase @@ -30,14 +32,11 @@ import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA import com.android.systemui.media.controls.pipeline.MediaDataManager -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.PAGINATION_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.policy.ConfigurationController @@ -55,6 +54,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.floatThat import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -85,7 +85,12 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Mock lateinit var debugLogger: MediaCarouselControllerLogger @Mock lateinit var mediaViewController: MediaViewController @Mock lateinit var smartspaceMediaData: SmartspaceMediaData + @Mock lateinit var mediaCarousel: MediaScrollView + @Mock lateinit var pageIndicator: PageIndicator @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener> + @Captor + lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener> + @Captor lateinit var newConfig: ArgumentCaptor<Configuration> @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener> private val clock = FakeSystemClock() @@ -111,6 +116,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { logger, debugLogger ) + verify(configurationController).addCallback(capture(configListener)) verify(mediaDataManager).addListener(capture(listener)) verify(visualStabilityProvider) .addPersistentReorderingAllowedListener(capture(visualStabilityCallback)) @@ -642,24 +648,56 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() { val delta = 0.0001F - val paginationSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - val paginationSquishEnd = - TRANSFORM_BEZIER.getInterpolation( - (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION - ) + mediaCarouselController.mediaCarousel = mediaCarousel + mediaCarouselController.pageIndicator = pageIndicator + whenever(mediaCarousel.measuredHeight).thenReturn(100) + whenever(pageIndicator.translationY).thenReturn(80F) + whenever(pageIndicator.height).thenReturn(10) whenever(mediaHostStatesManager.mediaHostStates) .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState)) whenever(mediaHostState.visible).thenReturn(true) mediaCarouselController.currentEndLocation = LOCATION_QS - whenever(mediaHostState.squishFraction).thenReturn(paginationSquishMiddle) + whenever(mediaHostState.squishFraction).thenReturn(0.938F) mediaCarouselController.updatePageIndicatorAlpha() - assertEquals(mediaCarouselController.pageIndicator.alpha, 0.5F, delta) + verify(pageIndicator).alpha = floatThat { abs(it - 0.5F) < delta } - whenever(mediaHostState.squishFraction).thenReturn(paginationSquishEnd) + whenever(mediaHostState.squishFraction).thenReturn(1.0F) mediaCarouselController.updatePageIndicatorAlpha() - assertEquals(mediaCarouselController.pageIndicator.alpha, 1.0F, delta) + verify(pageIndicator).alpha = floatThat { abs(it - 1.0F) < delta } + } + + @Ignore("b/253229241") + @Test + fun testOnConfigChanged_playersAreAddedBack() { + listener.value.onMediaDataLoaded( + "playing local", + null, + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ) + ) + listener.value.onMediaDataLoaded( + "paused local", + null, + DATA.copy( + active = true, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ) + ) + + val playersSize = MediaPlayerData.players().size + + configListener.value.onConfigChanged(capture(newConfig)) + + assertEquals(playersSize, MediaPlayerData.players().size) + assertEquals( + MediaPlayerData.getMediaPlayerIndex("playing local"), + mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 920801f95f5b..a5795184b493 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.dream.MediaDreamComplication import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager @@ -76,6 +77,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var keyguardViewController: KeyguardViewController + @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController @Captor @@ -110,6 +112,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { keyguardStateController, bypassController, mediaCarouselController, + mediaDataManager, keyguardViewController, dreamOverlayStateController, configurationController, @@ -125,6 +128,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP) setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP) whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) + whenever(mediaDataManager.hasActiveMedia()).thenReturn(true) whenever(mediaCarouselController.mediaCarouselScrollHandler) .thenReturn(mediaCarouselScrollHandler) val observer = wakefullnessObserver.value @@ -357,17 +361,31 @@ class MediaHierarchyManagerTest : SysuiTestCase() { } @Test - fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue() { + fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsFalse_with_active() { goToLockscreen() enterGuidedTransformation() whenever(lockHost.visible).thenReturn(false) whenever(qsHost.visible).thenReturn(true) whenever(qqsHost.visible).thenReturn(true) + whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true) assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() } @Test + fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue_without_active() { + // To keep the appearing behavior, we need to be in a guided transition + goToLockscreen() + enterGuidedTransformation() + whenever(lockHost.visible).thenReturn(false) + whenever(qsHost.visible).thenReturn(true) + whenever(qqsHost.visible).thenReturn(true) + whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false) + + assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue() + } + + @Test fun testDream() { goToDream() setMediaDreamComplicationEnabled(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index 35b0eb678441..4ed6d7cf6bd0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -22,13 +22,6 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.ANIMATION_BASE_DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.CONTROLS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DETAILS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.DURATION -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIACONTAINERS_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.MEDIATITLES_DELAY -import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.TRANSFORM_BEZIER import com.android.systemui.util.animation.MeasurementInput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionViewState @@ -60,9 +53,10 @@ class MediaViewControllerTest : SysuiTestCase() { @Mock private lateinit var controlWidgetState: WidgetState @Mock private lateinit var bgWidgetState: WidgetState @Mock private lateinit var mediaTitleWidgetState: WidgetState + @Mock private lateinit var mediaSubTitleWidgetState: WidgetState @Mock private lateinit var mediaContainerWidgetState: WidgetState - val delta = 0.0001F + val delta = 0.1F private lateinit var mediaViewController: MediaViewController @@ -76,10 +70,11 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() { mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) - player.measureState = TransitionViewState().apply { - this.height = 100 - this.measureHeight = 100 - } + player.measureState = + TransitionViewState().apply { + this.height = 100 + this.measureHeight = 100 + } mediaHostStateHolder.expansion = 1f val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) @@ -128,29 +123,21 @@ class MediaViewControllerTest : SysuiTestCase() { R.id.header_artist to detailWidgetState ) ) - - val detailSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (DETAILS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, detailSquishMiddle) + whenever(mockCopiedState.measureHeight).thenReturn(200) + // detail widgets occupy [90, 100] + whenever(detailWidgetState.y).thenReturn(90F) + whenever(detailWidgetState.height).thenReturn(10) + // control widgets occupy [150, 170] + whenever(controlWidgetState.y).thenReturn(150F) + whenever(controlWidgetState.height).thenReturn(20) + // in current beizer, when the progress reach 0.38, the result will be 0.5 + mediaViewController.squishViewState(mockViewState, 119F / 200F) verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - - val detailSquishEnd = - TRANSFORM_BEZIER.getInterpolation((DETAILS_DELAY + DURATION) / ANIMATION_BASE_DURATION) - mediaViewController.squishViewState(mockViewState, detailSquishEnd) + mediaViewController.squishViewState(mockViewState, 150F / 200F) verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } - - val controlSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (CONTROLS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, controlSquishMiddle) + mediaViewController.squishViewState(mockViewState, 181.4F / 200F) verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - - val controlSquishEnd = - TRANSFORM_BEZIER.getInterpolation((CONTROLS_DELAY + DURATION) / ANIMATION_BASE_DURATION) - mediaViewController.squishViewState(mockViewState, controlSquishEnd) + mediaViewController.squishViewState(mockViewState, 200F / 200F) verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } } @@ -161,36 +148,33 @@ class MediaViewControllerTest : SysuiTestCase() { .thenReturn( mutableMapOf( R.id.media_title1 to mediaTitleWidgetState, + R.id.media_subtitle1 to mediaSubTitleWidgetState, R.id.media_cover1_container to mediaContainerWidgetState ) ) - - val containerSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (MEDIACONTAINERS_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, containerSquishMiddle) + whenever(mockCopiedState.measureHeight).thenReturn(360) + // media container widgets occupy [20, 300] + whenever(mediaContainerWidgetState.y).thenReturn(20F) + whenever(mediaContainerWidgetState.height).thenReturn(280) + // media title widgets occupy [320, 330] + whenever(mediaTitleWidgetState.y).thenReturn(320F) + whenever(mediaTitleWidgetState.height).thenReturn(10) + // media subtitle widgets occupy [340, 350] + whenever(mediaSubTitleWidgetState.y).thenReturn(340F) + whenever(mediaSubTitleWidgetState.height).thenReturn(10) + + // in current beizer, when the progress reach 0.38, the result will be 0.5 + mediaViewController.squishViewState(mockViewState, 307.6F / 360F) verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - - val containerSquishEnd = - TRANSFORM_BEZIER.getInterpolation( - (MEDIACONTAINERS_DELAY + DURATION) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, containerSquishEnd) + mediaViewController.squishViewState(mockViewState, 320F / 360F) verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } - - val titleSquishMiddle = - TRANSFORM_BEZIER.getInterpolation( - (MEDIATITLES_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, titleSquishMiddle) + // media title and media subtitle are in same widget group, should be calculate together and + // have same alpha + mediaViewController.squishViewState(mockViewState, 353.8F / 360F) verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } - - val titleSquishEnd = - TRANSFORM_BEZIER.getInterpolation( - (MEDIATITLES_DELAY + DURATION) / ANIMATION_BASE_DURATION - ) - mediaViewController.squishViewState(mockViewState, titleSquishEnd) + verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta } + mediaViewController.squishViewState(mockViewState, 360F / 360F) verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } + verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index b16a39f37e39..f5432e22c57e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -102,8 +102,6 @@ public class MediaOutputControllerTest extends SysuiTestCase { private MediaOutputController.Callback mCb = mock(MediaOutputController.Callback.class); private MediaDevice mMediaDevice1 = mock(MediaDevice.class); private MediaDevice mMediaDevice2 = mock(MediaDevice.class); - private MediaItem mMediaItem1 = mock(MediaItem.class); - private MediaItem mMediaItem2 = mock(MediaItem.class); private NearbyDevice mNearbyDevice1 = mock(NearbyDevice.class); private NearbyDevice mNearbyDevice2 = mock(NearbyDevice.class); private MediaMetadata mMediaMetadata = mock(MediaMetadata.class); @@ -127,7 +125,6 @@ public class MediaOutputControllerTest extends SysuiTestCase { private LocalMediaManager mLocalMediaManager; private List<MediaController> mMediaControllers = new ArrayList<>(); private List<MediaDevice> mMediaDevices = new ArrayList<>(); - private List<MediaItem> mMediaItemList = new ArrayList<>(); private List<NearbyDevice> mNearbyDevices = new ArrayList<>(); private MediaDescription mMediaDescription; private List<RoutingSessionInfo> mRoutingSessionInfos = new ArrayList<>(); @@ -149,7 +146,9 @@ public class MediaOutputControllerTest extends SysuiTestCase { Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, mKeyguardManager, mFlags); when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(false); + when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING)).thenReturn(false); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); + when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; MediaDescription.Builder builder = new MediaDescription.Builder(); builder.setTitle(TEST_SONG); @@ -160,16 +159,12 @@ public class MediaOutputControllerTest extends SysuiTestCase { when(mMediaDevice2.getId()).thenReturn(TEST_DEVICE_2_ID); mMediaDevices.add(mMediaDevice1); mMediaDevices.add(mMediaDevice2); - when(mMediaItem1.getMediaDevice()).thenReturn(Optional.of(mMediaDevice1)); - when(mMediaItem2.getMediaDevice()).thenReturn(Optional.of(mMediaDevice2)); - mMediaItemList.add(mMediaItem1); - mMediaItemList.add(mMediaItem2); when(mNearbyDevice1.getMediaRoute2Id()).thenReturn(TEST_DEVICE_1_ID); - when(mNearbyDevice1.getRangeZone()).thenReturn(NearbyDevice.RANGE_CLOSE); + when(mNearbyDevice1.getRangeZone()).thenReturn(NearbyDevice.RANGE_FAR); when(mNearbyDevice2.getMediaRoute2Id()).thenReturn(TEST_DEVICE_2_ID); - when(mNearbyDevice2.getRangeZone()).thenReturn(NearbyDevice.RANGE_FAR); + when(mNearbyDevice2.getRangeZone()).thenReturn(NearbyDevice.RANGE_CLOSE); mNearbyDevices.add(mNearbyDevice1); mNearbyDevices.add(mNearbyDevice2); } @@ -274,8 +269,20 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController.onDevicesUpdated(mNearbyDevices); mMediaOutputController.onDeviceListUpdate(mMediaDevices); - verify(mMediaDevice1).setRangeZone(NearbyDevice.RANGE_CLOSE); - verify(mMediaDevice2).setRangeZone(NearbyDevice.RANGE_FAR); + verify(mMediaDevice1).setRangeZone(NearbyDevice.RANGE_FAR); + verify(mMediaDevice2).setRangeZone(NearbyDevice.RANGE_CLOSE); + } + + @Test + public void onDeviceListUpdate_withNearbyDevices_rankByRangeInformation() + throws RemoteException { + mMediaOutputController.start(mCb); + reset(mCb); + + mMediaOutputController.onDevicesUpdated(mNearbyDevices); + mMediaOutputController.onDeviceListUpdate(mMediaDevices); + + assertThat(mMediaDevices.get(0).getId()).isEqualTo(TEST_DEVICE_1_ID); } @Test @@ -292,6 +299,22 @@ public class MediaOutputControllerTest extends SysuiTestCase { } @Test + public void routeProcessSupport_onDeviceListUpdate_preferenceExist_NotUpdatesRangeInformation() + throws RemoteException { + when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true); + when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING)).thenReturn(true); + when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true); + mMediaOutputController.start(mCb); + reset(mCb); + + mMediaOutputController.onDevicesUpdated(mNearbyDevices); + mMediaOutputController.onDeviceListUpdate(mMediaDevices); + + verify(mMediaDevice1, never()).setRangeZone(anyInt()); + verify(mMediaDevice2, never()).setRangeZone(anyInt()); + } + + @Test public void advanced_onDeviceListUpdate_verifyDeviceListCallback() { when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true); mMediaOutputController.start(mCb); @@ -307,6 +330,35 @@ public class MediaOutputControllerTest extends SysuiTestCase { assertThat(devices.containsAll(mMediaDevices)).isTrue(); assertThat(devices.size()).isEqualTo(mMediaDevices.size()); + assertThat(mMediaOutputController.getMediaItemList().size()).isEqualTo( + mMediaDevices.size() + 2); + verify(mCb).onDeviceListChanged(); + } + + @Test + public void advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize() { + when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true); + when(mMediaDevice1.isSuggestedDevice()).thenReturn(true); + when(mMediaDevice2.isSuggestedDevice()).thenReturn(false); + + mMediaOutputController.start(mCb); + reset(mCb); + mMediaOutputController.getMediaItemList().clear(); + mMediaOutputController.onDeviceListUpdate(mMediaDevices); + final List<MediaDevice> devices = new ArrayList<>(); + int dividerSize = 0; + for (MediaItem item : mMediaOutputController.getMediaItemList()) { + if (item.getMediaDevice().isPresent()) { + devices.add(item.getMediaDevice().get()); + } + if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) { + dividerSize++; + } + } + + assertThat(devices.containsAll(mMediaDevices)).isTrue(); + assertThat(devices.size()).isEqualTo(mMediaDevices.size()); + assertThat(dividerSize).isEqualTo(2); verify(mCb).onDeviceListChanged(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index 4cc12c709fa7..f5b3959b322d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -206,6 +206,21 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { } @Test + fun commandQueueCallback_almostCloseToStartCast_deviceNameBlank_showsDefaultDeviceName() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfoWithBlankDeviceName, + null, + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .contains(context.getString(R.string.media_ttt_default_device_type)) + assertThat(chipbarView.getChipText()) + .isNotEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText()) + } + + @Test fun commandQueueCallback_almostCloseToEndCast_triggersCorrectChip() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, @@ -248,6 +263,21 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { } @Test + fun commandQueueCallback_transferToReceiverTriggered_deviceNameBlank_showsDefaultDeviceName() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfoWithBlankDeviceName, + null, + ) + + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .contains(context.getString(R.string.media_ttt_default_device_type)) + assertThat(chipbarView.getChipText()) + .isNotEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText()) + } + + @Test fun commandQueueCallback_transferToThisDeviceTriggered_triggersCorrectChip() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, @@ -934,6 +964,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { private const val APP_NAME = "Fake app name" private const val OTHER_DEVICE_NAME = "My Tablet" +private const val BLANK_DEVICE_NAME = " " private const val PACKAGE_NAME = "com.android.systemui" private const val TIMEOUT = 10000 @@ -942,3 +973,9 @@ private val routeInfo = .addFeature("feature") .setClientPackageName(PACKAGE_NAME) .build() + +private val routeInfoWithBlankDeviceName = + MediaRoute2Info.Builder("id", BLANK_DEVICE_NAME) + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 4a9c7508b1b3..fc90c1add5e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -93,7 +93,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(isInMultiWindowMode = false) verify(context).startActivity(notesIntent) - verify(bubbles, never()).showAppBubble(notesIntent) + verify(bubbles, never()).showOrHideAppBubble(notesIntent) } @Test @@ -102,7 +102,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(isInMultiWindowMode = false) - verify(bubbles).showAppBubble(notesIntent) + verify(bubbles).showOrHideAppBubble(notesIntent) verify(context, never()).startActivity(notesIntent) } @@ -113,7 +113,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(isInMultiWindowMode = true) verify(context).startActivity(notesIntent) - verify(bubbles, never()).showAppBubble(notesIntent) + verify(bubbles, never()).showOrHideAppBubble(notesIntent) } @Test @@ -123,7 +123,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(isInMultiWindowMode = false) verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showAppBubble(notesIntent) + verify(bubbles, never()).showOrHideAppBubble(notesIntent) } @Test @@ -133,7 +133,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(isInMultiWindowMode = false) verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showAppBubble(notesIntent) + verify(bubbles, never()).showOrHideAppBubble(notesIntent) } @Test @@ -143,7 +143,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(isInMultiWindowMode = false) verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showAppBubble(notesIntent) + verify(bubbles, never()).showOrHideAppBubble(notesIntent) } @Test @@ -153,7 +153,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(isInMultiWindowMode = false) verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showAppBubble(notesIntent) + verify(bubbles, never()).showOrHideAppBubble(notesIntent) } @Test @@ -161,7 +161,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController(isEnabled = false).showNoteTask() verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showAppBubble(notesIntent) + verify(bubbles, never()).showOrHideAppBubble(notesIntent) } @Test @@ -171,7 +171,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(isInMultiWindowMode = false) verify(context, never()).startActivity(notesIntent) - verify(bubbles, never()).showAppBubble(notesIntent) + verify(bubbles, never()).showOrHideAppBubble(notesIntent) } // endregion diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index ca3182affcc1..3281fa9bd8a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.qs.tiles.BatterySaverTile import com.android.systemui.qs.tiles.BluetoothTile import com.android.systemui.qs.tiles.CameraToggleTile import com.android.systemui.qs.tiles.CastTile -import com.android.systemui.qs.tiles.CellularTile import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile import com.android.systemui.qs.tiles.DataSaverTile @@ -49,7 +48,6 @@ import com.android.systemui.qs.tiles.ReduceBrightColorsTile import com.android.systemui.qs.tiles.RotationLockTile import com.android.systemui.qs.tiles.ScreenRecordTile import com.android.systemui.qs.tiles.UiModeNightTile -import com.android.systemui.qs.tiles.WifiTile import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.util.leak.GarbageMonitor import com.google.common.truth.Truth.assertThat @@ -63,10 +61,8 @@ import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever private val specMap = mapOf( - "wifi" to WifiTile::class.java, "internet" to InternetTile::class.java, "bt" to BluetoothTile::class.java, - "cell" to CellularTile::class.java, "dnd" to DndTile::class.java, "inversion" to ColorInversionTile::class.java, "airplane" to AirplaneModeTile::class.java, @@ -102,10 +98,8 @@ class QSFactoryImplTest : SysuiTestCase() { @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder @Mock private lateinit var customTile: CustomTile - @Mock private lateinit var wifiTile: WifiTile @Mock private lateinit var internetTile: InternetTile @Mock private lateinit var bluetoothTile: BluetoothTile - @Mock private lateinit var cellularTile: CellularTile @Mock private lateinit var dndTile: DndTile @Mock private lateinit var colorInversionTile: ColorInversionTile @Mock private lateinit var airplaneTile: AirplaneModeTile @@ -146,10 +140,8 @@ class QSFactoryImplTest : SysuiTestCase() { factory = QSFactoryImpl( { qsHost }, { customTileBuilder }, - { wifiTile }, { internetTile }, { bluetoothTile }, - { cellularTile }, { dndTile }, { colorInversionTile }, { airplaneTile }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index 08a90b79089e..18e40f633955 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.qs.QSUserSwitcherEvent -import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.user.data.source.UserRecord import org.junit.Assert.assertEquals @@ -42,7 +41,6 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @@ -152,15 +150,6 @@ class UserDetailViewAdapterTest : SysuiTestCase() { assertNull(adapter.users.find { it.isManageUsers }) } - @Test - fun clickDismissDialog() { - val shower: UserSwitchDialogController.DialogShower = - mock(UserSwitchDialogController.DialogShower::class.java) - adapter.injectDialogShower(shower) - adapter.onUserListItemClicked(createUserRecord(current = true, guest = false), shower) - verify(shower).dismiss() - } - private fun createUserRecord(current: Boolean, guest: Boolean) = UserRecord( UserInfo(0 /* id */, "name", 0 /* flags */), diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index fa1fedb7c119..99c79b0365ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -39,7 +39,6 @@ import com.android.internal.util.ScreenshotHelper import com.android.internal.util.ScreenshotHelper.ScreenshotRequest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.SCREENSHOT_REQUEST_PROCESSOR import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW @@ -101,7 +100,6 @@ class TakeScreenshotServiceTest : SysuiTestCase() { }.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any()) // Flipped in selected test cases - flags.set(SCREENSHOT_REQUEST_PROCESSOR, false) flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false) service.attach( @@ -149,31 +147,6 @@ class TakeScreenshotServiceTest : SysuiTestCase() { } @Test - fun takeScreenshot_requestProcessorEnabled() { - flags.set(SCREENSHOT_REQUEST_PROCESSOR, true) - - val request = ScreenshotRequest( - TAKE_SCREENSHOT_FULLSCREEN, - SCREENSHOT_KEY_CHORD, - topComponent) - - service.handleRequest(request, { /* onSaved */ }, callback) - - verify(controller, times(1)).takeScreenshotFullscreen( - eq(topComponent), - /* onSavedListener = */ any(), - /* requestCallback = */ any()) - - assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1) - val logEvent = eventLogger.get(0) - - assertEquals("Expected SCREENSHOT_REQUESTED UiEvent", - logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id) - assertEquals("Expected supplied package name", - topComponent.packageName, eventLogger.get(0).packageName) - } - - @Test fun takeScreenshotProvidedImage() { val bounds = Rect(50, 50, 150, 150) val bitmap = makeHardwareBitmap(100, 100) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java new file mode 100644 index 000000000000..bd04b3ccc039 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.os.UserManager; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.util.FakeSharedPreferences; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class WorkProfileMessageControllerTest { + private static final String DEFAULT_LABEL = "default label"; + private static final String BADGED_DEFAULT_LABEL = "badged default label"; + private static final String APP_LABEL = "app label"; + private static final String BADGED_APP_LABEL = "badged app label"; + private static final UserHandle NON_WORK_USER = UserHandle.of(0); + private static final UserHandle WORK_USER = UserHandle.of(10); + + @Mock + private UserManager mUserManager; + @Mock + private PackageManager mPackageManager; + @Mock + private Context mContext; + @Mock + private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay; + @Mock + private Drawable mActivityIcon; + @Mock + private Drawable mBadgedActivityIcon; + @Mock + private ActivityInfo mActivityInfo; + @Captor + private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; + + private FakeSharedPreferences mSharedPreferences = new FakeSharedPreferences(); + + private WorkProfileMessageController mMessageController; + + @Before + public void setup() throws PackageManager.NameNotFoundException { + MockitoAnnotations.initMocks(this); + + when(mUserManager.isManagedProfile(eq(WORK_USER.getIdentifier()))).thenReturn(true); + when(mContext.getSharedPreferences( + eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME), + eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences); + when(mContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL); + when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any())) + .thenReturn(BADGED_DEFAULT_LABEL); + when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any())) + .thenReturn(BADGED_APP_LABEL); + when(mPackageManager.getActivityIcon(any(ComponentName.class))) + .thenReturn(mActivityIcon); + when(mPackageManager.getUserBadgedIcon( + any(), any())).thenReturn(mBadgedActivityIcon); + when(mPackageManager.getActivityInfo(any(), + any(PackageManager.ComponentInfoFlags.class))).thenReturn(mActivityInfo); + when(mActivityInfo.loadLabel(eq(mPackageManager))).thenReturn(APP_LABEL); + + mSharedPreferences.edit().putBoolean( + WorkProfileMessageController.PREFERENCE_KEY, false).apply(); + + mMessageController = new WorkProfileMessageController(mContext, mUserManager, + mPackageManager); + } + + @Test + public void testOnScreenshotTaken_notManaged() { + mMessageController.onScreenshotTaken(NON_WORK_USER, mMessageDisplay); + + verify(mMessageDisplay, never()) + .showWorkProfileMessage(any(), nullable(Drawable.class), any()); + } + + @Test + public void testOnScreenshotTaken_alreadyDismissed() { + mSharedPreferences.edit().putBoolean( + WorkProfileMessageController.PREFERENCE_KEY, true).apply(); + + mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay); + + verify(mMessageDisplay, never()) + .showWorkProfileMessage(any(), nullable(Drawable.class), any()); + } + + @Test + public void testOnScreenshotTaken_packageNotFound() + throws PackageManager.NameNotFoundException { + when(mPackageManager.getActivityInfo(any(), + any(PackageManager.ComponentInfoFlags.class))).thenThrow( + new PackageManager.NameNotFoundException()); + + mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay); + + verify(mMessageDisplay).showWorkProfileMessage( + eq(BADGED_DEFAULT_LABEL), eq(null), any()); + } + + @Test + public void testOnScreenshotTaken() { + mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay); + + verify(mMessageDisplay).showWorkProfileMessage( + eq(BADGED_APP_LABEL), eq(mBadgedActivityIcon), mRunnableArgumentCaptor.capture()); + + // Dismiss hasn't been tapped, preference untouched. + assertFalse( + mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false)); + + mRunnableArgumentCaptor.getValue().run(); + + // After dismiss has been tapped, the setting should be updated. + assertTrue( + mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false)); + } +} + diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt new file mode 100644 index 000000000000..3710281499b3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt @@ -0,0 +1,100 @@ +package com.android.systemui.settings + +import android.content.Context +import android.content.Intent +import android.content.pm.UserInfo +import android.os.Handler +import android.os.UserHandle +import android.os.UserManager +import androidx.concurrent.futures.DirectExecutor +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(Parameterized::class) +class UserTrackerImplReceiveTest : SysuiTestCase() { + + companion object { + + @JvmStatic + @Parameterized.Parameters + fun data(): Iterable<String> = + listOf( + Intent.ACTION_USER_INFO_CHANGED, + Intent.ACTION_MANAGED_PROFILE_AVAILABLE, + Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, + Intent.ACTION_MANAGED_PROFILE_ADDED, + Intent.ACTION_MANAGED_PROFILE_REMOVED, + Intent.ACTION_MANAGED_PROFILE_UNLOCKED + ) + } + + private val executor: Executor = DirectExecutor.INSTANCE + + @Mock private lateinit var context: Context + @Mock private lateinit var userManager: UserManager + @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager + @Mock(stubOnly = true) private lateinit var handler: Handler + + @Parameterized.Parameter lateinit var intentAction: String + @Mock private lateinit var callback: UserTracker.Callback + @Captor private lateinit var captor: ArgumentCaptor<List<UserInfo>> + + private lateinit var tracker: UserTrackerImpl + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(context.user).thenReturn(UserHandle.SYSTEM) + `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context) + + tracker = UserTrackerImpl(context, userManager, dumpManager, handler) + } + + @Test + fun `calls callback and updates profiles when an intent received`() { + tracker.initialize(0) + tracker.addCallback(callback, executor) + val profileID = tracker.userId + 10 + + `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation -> + val id = invocation.getArgument<Int>(0) + val info = UserInfo(id, "", UserInfo.FLAG_FULL) + val infoProfile = + UserInfo( + id + 10, + "", + "", + UserInfo.FLAG_MANAGED_PROFILE, + UserManager.USER_TYPE_PROFILE_MANAGED + ) + infoProfile.profileGroupId = id + listOf(info, infoProfile) + } + + tracker.onReceive(context, Intent(intentAction)) + + verify(callback, times(0)).onUserChanged(anyInt(), any()) + verify(callback, times(1)).onProfilesChanged(capture(captor)) + assertThat(captor.value.map { it.id }).containsExactly(0, profileID) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt index 52462c7186d4..e65bbb1bea08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -124,6 +124,16 @@ class UserTrackerImplTest : SysuiTestCase() { verify(context).registerReceiverForAllUsers( eq(tracker), capture(captor), isNull(), eq(handler)) + with(captor.value) { + assertThat(countActions()).isEqualTo(7) + assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue() + assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue() + assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue() + } } @Test @@ -280,37 +290,6 @@ class UserTrackerImplTest : SysuiTestCase() { } @Test - fun testCallbackCalledOnProfileChanged() { - tracker.initialize(0) - val callback = TestCallback() - tracker.addCallback(callback, executor) - val profileID = tracker.userId + 10 - - `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation -> - val id = invocation.getArgument<Int>(0) - val info = UserInfo(id, "", UserInfo.FLAG_FULL) - val infoProfile = UserInfo( - id + 10, - "", - "", - UserInfo.FLAG_MANAGED_PROFILE, - UserManager.USER_TYPE_PROFILE_MANAGED - ) - infoProfile.profileGroupId = id - listOf(info, infoProfile) - } - - val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) - .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID)) - - tracker.onReceive(context, intent) - - assertThat(callback.calledOnUserChanged).isEqualTo(0) - assertThat(callback.calledOnProfilesChanged).isEqualTo(1) - assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID) - } - - @Test fun testCallbackCalledOnUserInfoChanged() { tracker.initialize(0) val callback = TestCallback() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt index 88651c1292c3..f802a5e09228 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.testing.AndroidTestingRunner +import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START @@ -92,12 +93,12 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f) assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID) - assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f) + assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f) assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd) .isEqualTo(PARENT_ID) assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias) - .isEqualTo(1f) + .isEqualTo(0.5f) assertThat(getConstraint(R.id.privacy_container).layout.endToEnd) .isEqualTo(R.id.end_guide) @@ -331,10 +332,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { val views = mapOf( R.id.clock to "clock", R.id.date to "date", - R.id.statusIcons to "icons", R.id.privacy_container to "privacy", R.id.carrier_group to "carriers", - R.id.batteryRemainingIcon to "battery", ) views.forEach { (id, name) -> assertWithMessage("$name has 0 height in qqs") @@ -352,11 +351,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { fun testCheckViewsDontChangeSizeBetweenAnimationConstraints() { val views = mapOf( R.id.clock to "clock", - R.id.date to "date", - R.id.statusIcons to "icons", R.id.privacy_container to "privacy", R.id.carrier_group to "carriers", - R.id.batteryRemainingIcon to "battery", ) views.forEach { (id, name) -> expect.withMessage("$name changes height") @@ -369,8 +365,8 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() { } private fun Int.fromConstraint() = when (this) { - -1 -> "MATCH_PARENT" - -2 -> "WRAP_CONTENT" + ViewGroup.LayoutParams.MATCH_PARENT -> "MATCH_PARENT" + ViewGroup.LayoutParams.WRAP_CONTENT -> "WRAP_CONTENT" else -> toString() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt index 1d30ad9293a0..f580f5e00f67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt @@ -182,6 +182,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() { null } whenever(view.visibility).thenAnswer { _ -> viewVisibility } + whenever(view.alpha).thenReturn(1f) whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt index b4c8f981b760..b568122d3fed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.shade +import android.animation.ValueAnimator import android.app.StatusBarManager import android.content.Context import android.testing.AndroidTestingRunner @@ -30,6 +31,7 @@ import com.android.systemui.statusbar.policy.VariableDateViewController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before @@ -37,6 +39,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Answers +import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.mock @@ -75,6 +78,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { @JvmField @Rule val mockitoRule = MockitoJUnit.rule() var viewVisibility = View.GONE + var viewAlpha = 1f private lateinit var mLargeScreenShadeHeaderController: LargeScreenShadeHeaderController private lateinit var carrierIconSlots: List<String> @@ -101,6 +105,13 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { null } whenever(view.visibility).thenAnswer { _ -> viewVisibility } + + whenever(view.setAlpha(anyFloat())).then { + viewAlpha = it.arguments[0] as Float + null + } + whenever(view.alpha).thenAnswer { _ -> viewAlpha } + whenever(variableDateViewControllerFactory.create(any())) .thenReturn(variableDateViewController) whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager) @@ -155,6 +166,16 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { } @Test + fun alphaChangesUpdateVisibility() { + makeShadeVisible() + mLargeScreenShadeHeaderController.shadeExpandedFraction = 0f + assertThat(viewVisibility).isEqualTo(View.INVISIBLE) + + mLargeScreenShadeHeaderController.shadeExpandedFraction = 1f + assertThat(viewVisibility).isEqualTo(View.VISIBLE) + } + + @Test fun singleCarrier_enablesCarrierIconsInStatusIcons() { whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true) @@ -239,6 +260,39 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() { } @Test + fun testShadeExpanded_true_alpha_zero_invisible() { + view.alpha = 0f + mLargeScreenShadeHeaderController.largeScreenActive = true + mLargeScreenShadeHeaderController.qsVisible = true + + assertThat(viewVisibility).isEqualTo(View.INVISIBLE) + } + + @Test + fun animatorCallsUpdateVisibilityOnUpdate() { + val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF) + whenever(view.animate()).thenReturn(animator) + + mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, 0L) + + val updateCaptor = argumentCaptor<ValueAnimator.AnimatorUpdateListener>() + verify(animator).setUpdateListener(capture(updateCaptor)) + + mLargeScreenShadeHeaderController.largeScreenActive = true + mLargeScreenShadeHeaderController.qsVisible = true + + view.alpha = 1f + updateCaptor.value.onAnimationUpdate(mock()) + + assertThat(viewVisibility).isEqualTo(View.VISIBLE) + + view.alpha = 0f + updateCaptor.value.onAnimationUpdate(mock()) + + assertThat(viewVisibility).isEqualTo(View.INVISIBLE) + } + + @Test fun demoMode_attachDemoMode() { val cb = argumentCaptor<DemoMode>() verify(demoModeController).addCallback(capture(cb)) 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 65b2ac0eb2d3..d0b42ae8ec56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -103,10 +103,13 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.KeyguardMediaController; @@ -293,8 +296,12 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; @Mock private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; @Mock private OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel; + @Mock private LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel; + @Mock private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel; + @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock private CoroutineDispatcher mMainDispatcher; + @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private MotionEvent mDownMotionEvent; @Captor private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> @@ -511,8 +518,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { systemClock, mKeyguardBottomAreaViewModel, mKeyguardBottomAreaInteractor, + mAlternateBouncerInteractor, mDreamingToLockscreenTransitionViewModel, mOccludedToLockscreenTransitionViewModel, + mLockscreenToDreamingTransitionViewModel, + mLockscreenToOccludedTransitionViewModel, mMainDispatcher, mKeyguardTransitionInteractor, mDumpManager); 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 c3207c2f58a5..4c768253202a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -30,6 +30,8 @@ import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.KeyguardUnlockAnimationController +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -97,10 +99,13 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var pulsingGestureListener: PulsingGestureListener @Mock private lateinit var notificationInsetsController: NotificationInsetsController + @Mock + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerContainer: ViewGroup @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardHostViewController: KeyguardHostViewController + @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> private lateinit var interactionEventHandler: InteractionEventHandler @@ -132,7 +137,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { pulsingGestureListener, featureFlags, keyguardBouncerViewModel, - keyguardBouncerComponentFactory + keyguardBouncerComponentFactory, + alternateBouncerInteractor, + keyguardTransitionInteractor, ) underTest.setupExpandedStatusBar() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index 4bf00c4ccb51..d43562443d6e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -40,6 +40,8 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.dock.DockManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -93,6 +95,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel; @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; @Mock private NotificationInsetsController mNotificationInsetsController; + @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler> mInteractionEventHandlerCaptor; @@ -132,7 +136,9 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mPulsingGestureListener, mFeatureFlags, mKeyguardBouncerViewModel, - mKeyguardBouncerComponentFactory + mKeyguardBouncerComponentFactory, + mAlternateBouncerInteractor, + mKeyguardTransitionInteractor ); mController.setupExpandedStatusBar(); mController.setDragDownHelper(mDragDownHelper); @@ -155,7 +161,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); // THEN we should intercept touch @@ -168,7 +174,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(false); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); // THEN we shouldn't intercept touch @@ -181,7 +187,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarKeyguardViewManager.isShowingAlternateBouncer()).thenReturn(true); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); // THEN we should handle the touch diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index d2dd43308fcc..610bb13c6016 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -99,6 +99,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController; import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -177,6 +178,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Mock private FaceHelpMessageDeferral mFaceHelpMessageDeferral; @Mock + private AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock private ScreenLifecycle mScreenLifecycle; @Mock private AuthController mAuthController; @@ -273,7 +276,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mUserManager, mExecutor, mExecutor, mFalsingManager, mAuthController, mLockPatternUtils, mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager, - mFaceHelpMessageDeferral, mock(KeyguardLogger.class)); + mFaceHelpMessageDeferral, mock(KeyguardLogger.class), + mAlternateBouncerInteractor); mController.init(); mController.setIndicationArea(mIndicationArea); verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); 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 3d11ced6207d..702f278746be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -244,6 +244,14 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { } @Test + fun testGoToLockedShadeAlwaysCreatesQSAnimationInSplitShade() { + enableSplitShade() + transitionController.goToLockedShade(null, needsQSAnimation = true) + verify(notificationPanelController).animateToFullShade(anyLong()) + assertNotNull(transitionController.dragDownAnimator) + } + + @Test fun testDragDownAmountDoesntCallOutInLockedDownShade() { whenever(nsslController.isInLockedDownShade).thenReturn(true) transitionController.dragDownAmount = 10f diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt index 8275c0c24339..9b3626bfc9ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt @@ -127,6 +127,6 @@ class TargetSdkResolverTest : SysuiTestCase() { NotificationManager.IMPORTANCE_DEFAULT, null, null, null, null, null, true, 0, false, -1, false, null, null, false, false, - false, null, 0, false) + false, null, 0, false, 0) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index ca99e24fc105..e41929f7d578 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.notification.LegacySourceType; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper; import org.junit.Assert; import org.junit.Before; @@ -216,4 +217,29 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { Assert.assertEquals(1f, mChildrenContainer.getBottomRoundness(), 0.001f); Assert.assertEquals(1f, notificationRow.getBottomRoundness(), 0.001f); } + + @Test + public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_header() { + mChildrenContainer.useRoundnessSourceTypes(true); + + NotificationHeaderViewWrapper header = mChildrenContainer.getNotificationHeaderWrapper(); + Assert.assertEquals(0f, header.getTopRoundness(), 0.001f); + + mChildrenContainer.requestTopRoundness(1f, SourceType.from(""), false); + + Assert.assertEquals(1f, header.getTopRoundness(), 0.001f); + } + + @Test + public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_headerLowPriority() { + mChildrenContainer.useRoundnessSourceTypes(true); + mChildrenContainer.setIsLowPriority(true); + + NotificationHeaderViewWrapper header = mChildrenContainer.getNotificationHeaderWrapper(); + Assert.assertEquals(0f, header.getTopRoundness(), 0.001f); + + mChildrenContainer.requestTopRoundness(1f, SourceType.from(""), false); + + Assert.assertEquals(1f, header.getTopRoundness(), 0.001f); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 4ccbc6d45e63..091bb5455d93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; @@ -74,6 +75,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.mockito.stubbing.Answer; import java.util.Collections; import java.util.List; @@ -115,8 +117,10 @@ public class AutoTileManagerTest extends SysuiTestCase { @Spy private PackageManager mPackageManager; private final boolean mIsReduceBrightColorsAvailable = true; - private AutoTileManager mAutoTileManager; + private AutoTileManager mAutoTileManager; // under test + private SecureSettings mSecureSettings; + private ManagedProfileController.Callback mManagedProfileCallback; @Before public void setUp() throws Exception { @@ -303,7 +307,7 @@ public class AutoTileManagerTest extends SysuiTestCase { InOrder inOrderManagedProfile = inOrder(mManagedProfileController); inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any()); - inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any()); + inOrderManagedProfile.verify(mManagedProfileController).addCallback(any()); if (ColorDisplayManager.isNightDisplayAvailable(mContext)) { InOrder inOrderNightDisplay = inOrder(mNightDisplayListener); @@ -504,6 +508,40 @@ public class AutoTileManagerTest extends SysuiTestCase { } @Test + public void managedProfileAdded_tileAdded() { + when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false); + mAutoTileManager = createAutoTileManager(mContext); + Mockito.doAnswer((Answer<Object>) invocation -> { + mManagedProfileCallback = invocation.getArgument(0); + return null; + }).when(mManagedProfileController).addCallback(any()); + mAutoTileManager.init(); + when(mManagedProfileController.hasActiveProfile()).thenReturn(true); + + mManagedProfileCallback.onManagedProfileChanged(); + + verify(mQsTileHost, times(1)).addTile(eq("work")); + verify(mAutoAddTracker, times(1)).setTileAdded(eq("work")); + } + + @Test + public void managedProfileRemoved_tileRemoved() { + when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true); + mAutoTileManager = createAutoTileManager(mContext); + Mockito.doAnswer((Answer<Object>) invocation -> { + mManagedProfileCallback = invocation.getArgument(0); + return null; + }).when(mManagedProfileController).addCallback(any()); + mAutoTileManager.init(); + when(mManagedProfileController.hasActiveProfile()).thenReturn(false); + + mManagedProfileCallback.onManagedProfileChanged(); + + verify(mQsTileHost, times(1)).removeTile(eq("work")); + verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work")); + } + + @Test public void testEmptyArray_doesNotCrash() { mContext.getOrCreateTestableResources().addOverride( R.array.config_quickSettingsAutoAdd, new String[0]); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 74f8c61ad186..daf7dd06b0d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -57,6 +59,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -122,6 +125,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private VibratorHelper mVibratorHelper; @Mock private BiometricUnlockLogger mLogger; + private final FakeSystemClock mSystemClock = new FakeSystemClock(); private BiometricUnlockController mBiometricUnlockController; @Before @@ -144,7 +148,9 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mMetricsLogger, mDumpManager, mPowerManager, mLogger, mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController, - mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper); + mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, + mSystemClock + ); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener); when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker); @@ -207,7 +213,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { verify(mKeyguardViewMediator).onWakeAndUnlocking(); assertThat(mBiometricUnlockController.getMode()) - .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); + .isEqualTo(MODE_WAKE_AND_UNLOCK); } @Test @@ -457,4 +463,83 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { // THEN wakeup the device verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString()); } + + @Test + public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() { + // GIVEN side fingerprint enrolled, last wake reason was power button + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + + // GIVEN last wake time just occurred + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + + // WHEN biometric fingerprint succeeds + givenFingerprintModeUnlockCollapsing(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, + true); + + // THEN DO NOT vibrate the device + verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString()); + } + + @Test + public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() { + // GIVEN side fingerprint enrolled, last wake reason was power button + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + + // GIVEN last wake time was 500ms ago + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + mSystemClock.advanceTime(500); + + // WHEN biometric fingerprint succeeds + givenFingerprintModeUnlockCollapsing(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, + true); + + // THEN vibrate the device + verify(mVibratorHelper).vibrateAuthSuccess(anyString()); + } + + @Test + public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() { + // GIVEN side fingerprint enrolled, wakeup just happened + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + + // GIVEN last wake reason was from a gesture + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_GESTURE); + + // WHEN biometric fingerprint succeeds + givenFingerprintModeUnlockCollapsing(); + mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, + true); + + // THEN vibrate the device + verify(mVibratorHelper).vibrateAuthSuccess(anyString()); + } + + @Test + public void onSideFingerprintFail_alwaysPlaysHaptic() { + // GIVEN side fingerprint enrolled, last wake reason was recent power button + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + when(mWakefulnessLifecycle.getLastWakeReason()) + .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); + when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); + + // WHEN biometric fingerprint fails + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + + // THEN always vibrate the device + verify(mVibratorHelper).vibrateAuthError(anyString()); + } + + private void givenFingerprintModeUnlockCollapsing() { + when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); + when(mKeyguardStateController.isShowing()).thenReturn(true); + } } 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 09254ad0faf2..c8157ccc8a9a 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 @@ -110,6 +110,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; @@ -177,8 +178,6 @@ import com.android.systemui.volume.VolumeComponent; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.startingsurface.StartingSurface; -import dagger.Lazy; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -191,6 +190,8 @@ import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.util.Optional; +import dagger.Lazy; + @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -297,6 +298,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private WiredChargingRippleController mWiredChargingRippleController; @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; @Mock private CameraLauncher mCameraLauncher; + @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; /** * The process of registering/unregistering a predictive back callback requires a * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test. @@ -378,7 +380,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { }).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()); mWakefulnessLifecycle = - new WakefulnessLifecycle(mContext, mIWallpaperManager, mDumpManager); + new WakefulnessLifecycle(mContext, mIWallpaperManager, mFakeSystemClock, + mDumpManager); mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN); mWakefulnessLifecycle.dispatchFinishedWakingUp(); @@ -504,7 +507,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mWiredChargingRippleController, mDreamManager, mCameraLauncherLazy, - () -> mLightRevealScrimViewModel) { + () -> mLightRevealScrimViewModel, + mAlternateBouncerInteractor + ) { @Override protected ViewRootImpl getViewRootImpl() { return mViewRootImpl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index 077b41a0aa90..c8438501b3e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java @@ -23,6 +23,10 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; @@ -39,10 +43,9 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; import com.android.systemui.unfold.FoldAodAnimationController; @@ -52,6 +55,8 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -69,7 +74,6 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private PowerManager mPowerManager; @Mock private TunerService mTunerService; @Mock private BatteryController mBatteryController; - @Mock private FeatureFlags mFeatureFlags; @Mock private DumpManager mDumpManager; @Mock private ScreenOffAnimationController mScreenOffAnimationController; @Mock private FoldAodAnimationController mFoldAodAnimationController; @@ -78,6 +82,7 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private StatusBarStateController mStatusBarStateController; @Mock private ConfigurationController mConfigurationController; + @Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback; /** * The current value of PowerManager's dozeAfterScreenOff property. @@ -113,7 +118,6 @@ public class DozeParametersTest extends SysuiTestCase { mBatteryController, mTunerService, mDumpManager, - mFeatureFlags, mScreenOffAnimationController, Optional.of(mSysUIUnfoldComponent), mUnlockedScreenOffAnimationController, @@ -122,7 +126,8 @@ public class DozeParametersTest extends SysuiTestCase { mStatusBarStateController ); - when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(true); + verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture()); + setAodEnabledForTest(true); setShouldControlUnlockedScreenOffForTest(true); setDisplayNeedsBlankingForTest(false); @@ -173,6 +178,29 @@ public class DozeParametersTest extends SysuiTestCase { assertThat(mDozeParameters.getAlwaysOn()).isFalse(); } + @Test + public void testGetAlwaysOn_whenBatterySaverCallback() { + DozeParameters.Callback callback = mock(DozeParameters.Callback.class); + mDozeParameters.addCallback(callback); + + when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); + when(mBatteryController.isAodPowerSave()).thenReturn(true); + + // Both lines should trigger an event + mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); + mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true); + + verify(callback, times(2)).onAlwaysOnChange(); + assertThat(mDozeParameters.getAlwaysOn()).isFalse(); + + reset(callback); + when(mBatteryController.isAodPowerSave()).thenReturn(false); + mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true); + + verify(callback).onAlwaysOnChange(); + assertThat(mDozeParameters.getAlwaysOn()).isTrue(); + } + /** * PowerManager.setDozeAfterScreenOff(true) means we are not controlling screen off, and calling * it with false means we are. Confusing, but sure - make sure that we call PowerManager with @@ -196,17 +224,6 @@ public class DozeParametersTest extends SysuiTestCase { } @Test - public void testControlUnlockedScreenOffAnimationDisabled_dozeAfterScreenOff() { - when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(false); - - assertFalse(mDozeParameters.shouldControlUnlockedScreenOff()); - - // Trigger the setter for the current value. - mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff()); - assertFalse(mDozeParameters.shouldControlScreenOff()); - } - - @Test public void propagatesAnimateScreenOff_noAlwaysOn() { setAodEnabledForTest(false); setDisplayNeedsBlankingForTest(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 14a319bc87e9..04a67006d686 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -56,6 +56,7 @@ import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.data.BouncerView; import com.android.systemui.keyguard.data.BouncerViewDelegate; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.navigationbar.NavigationModeController; @@ -105,7 +106,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory; @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController; - @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer; @Mock private KeyguardMessageArea mKeyguardMessageArea; @Mock private ShadeController mShadeController; @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent; @@ -115,6 +115,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private KeyguardSecurityModel mKeyguardSecurityModel; @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; + @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private BouncerView mBouncerView; @Mock private BouncerViewDelegate mBouncerViewDelegate; @@ -163,7 +164,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mFeatureFlags, mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, - mBouncerView) { + mBouncerView, + mAlternateBouncerInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -434,37 +436,35 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test public void testShowing_whenAlternateAuthShowing() { - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); - when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); assertTrue( - "Is showing not accurate when alternative auth showing", + "Is showing not accurate when alternative bouncer is visible", mStatusBarKeyguardViewManager.isBouncerShowing()); } @Test public void testWillBeShowing_whenAlternateAuthShowing() { - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); - when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); assertTrue( - "Is or will be showing not accurate when alternative auth showing", + "Is or will be showing not accurate when alternate bouncer is visible", mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()); } @Test - public void testHideAlternateBouncer_onShowBouncer() { - // GIVEN alt auth is showing - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); + public void testHideAlternateBouncer_onShowPrimaryBouncer() { + reset(mAlternateBouncerInteractor); + + // GIVEN alt bouncer is showing when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); - when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true); - reset(mAlternateBouncer); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); // WHEN showBouncer is called mStatusBarKeyguardViewManager.showPrimaryBouncer(true); // THEN alt bouncer should be hidden - verify(mAlternateBouncer).hideAlternateBouncer(); + verify(mAlternateBouncerInteractor).hide(); } @Test @@ -479,11 +479,9 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test public void testShowAltAuth_unlockingWithBiometricNotAllowed() { - // GIVEN alt auth exists, unlocking with biometric isn't allowed - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); + // GIVEN cannot use alternate bouncer when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); - when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) - .thenReturn(false); + when(mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()).thenReturn(false); // WHEN showGenericBouncer is called final boolean scrimmed = true; @@ -491,21 +489,19 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { // THEN regular bouncer is shown verify(mPrimaryBouncerInteractor).show(eq(scrimmed)); - verify(mAlternateBouncer, never()).showAlternateBouncer(); } @Test public void testShowAlternateBouncer_unlockingWithBiometricAllowed() { - // GIVEN alt auth exists, unlocking with biometric is allowed - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); + // GIVEN will show alternate bouncer when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); - when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); + when(mAlternateBouncerInteractor.show()).thenReturn(true); // WHEN showGenericBouncer is called mStatusBarKeyguardViewManager.showBouncer(true); // THEN alt auth bouncer is shown - verify(mAlternateBouncer).showAlternateBouncer(); + verify(mAlternateBouncerInteractor).show(); verify(mPrimaryBouncerInteractor, never()).show(anyBoolean()); } @@ -613,7 +609,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mFeatureFlags, mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, - mBouncerView) { + mBouncerView, + mAlternateBouncerInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java index 96fba39d6b59..a9c55fa06cc5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java @@ -56,6 +56,7 @@ import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.data.BouncerView; import com.android.systemui.keyguard.data.BouncerViewDelegate; +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.navigationbar.NavigationModeController; @@ -109,7 +110,6 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController; @Mock private KeyguardBouncer mPrimaryBouncer; - @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer; @Mock private KeyguardMessageArea mKeyguardMessageArea; @Mock private ShadeController mShadeController; @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent; @@ -119,6 +119,7 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { @Mock private KeyguardSecurityModel mKeyguardSecurityModel; @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; + @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock private BouncerView mBouncerView; @Mock private BouncerViewDelegate mBouncerViewDelegate; @@ -169,7 +170,8 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { mFeatureFlags, mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, - mBouncerView) { + mBouncerView, + mAlternateBouncerInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -439,41 +441,6 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { } @Test - public void testShowing_whenAlternateAuthShowing() { - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); - when(mPrimaryBouncer.isShowing()).thenReturn(false); - when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true); - assertTrue( - "Is showing not accurate when alternative auth showing", - mStatusBarKeyguardViewManager.isBouncerShowing()); - } - - @Test - public void testWillBeShowing_whenAlternateAuthShowing() { - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); - when(mPrimaryBouncer.isShowing()).thenReturn(false); - when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true); - assertTrue( - "Is or will be showing not accurate when alternative auth showing", - mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()); - } - - @Test - public void testHideAlternateBouncer_onShowBouncer() { - // GIVEN alt auth is showing - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); - when(mPrimaryBouncer.isShowing()).thenReturn(false); - when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true); - reset(mAlternateBouncer); - - // WHEN showBouncer is called - mStatusBarKeyguardViewManager.showPrimaryBouncer(true); - - // THEN alt bouncer should be hidden - verify(mAlternateBouncer).hideAlternateBouncer(); - } - - @Test public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() { when(mPrimaryBouncer.isShowing()).thenReturn(false); when(mPrimaryBouncer.inTransit()).thenReturn(true); @@ -484,38 +451,6 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { } @Test - public void testShowAltAuth_unlockingWithBiometricNotAllowed() { - // GIVEN alt auth exists, unlocking with biometric isn't allowed - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); - when(mPrimaryBouncer.isShowing()).thenReturn(false); - when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())) - .thenReturn(false); - - // WHEN showGenericBouncer is called - final boolean scrimmed = true; - mStatusBarKeyguardViewManager.showBouncer(scrimmed); - - // THEN regular bouncer is shown - verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed)); - verify(mAlternateBouncer, never()).showAlternateBouncer(); - } - - @Test - public void testShowAlternateBouncer_unlockingWithBiometricAllowed() { - // GIVEN alt auth exists, unlocking with biometric is allowed - mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer); - when(mPrimaryBouncer.isShowing()).thenReturn(false); - when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); - - // WHEN showGenericBouncer is called - mStatusBarKeyguardViewManager.showBouncer(true); - - // THEN alt auth bouncer is shown - verify(mAlternateBouncer).showAlternateBouncer(); - verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean()); - } - - @Test public void testUpdateResources_delegatesToBouncer() { mStatusBarKeyguardViewManager.updateResources(); @@ -628,7 +563,8 @@ public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase { mFeatureFlags, mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, - mBouncerView) { + mBouncerView, + mAlternateBouncerInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; 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 0da15e239932..09589707b331 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 @@ -37,6 +37,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger @@ -46,6 +47,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -94,7 +96,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } } - whenever(logBufferFactory.create(anyString(), anyInt())).thenAnswer { _ -> + whenever(logBufferFactory.getOrCreate(anyString(), anyInt())).thenAnswer { _ -> mock<TableLogBuffer>() } @@ -292,13 +294,13 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { // Get repos to trigger creation underTest.getRepoForSubId(SUB_1_ID) verify(logBufferFactory) - .create( + .getOrCreate( eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)), anyInt(), ) underTest.getRepoForSubId(SUB_2_ID) verify(logBufferFactory) - .create( + .getOrCreate( eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)), anyInt(), ) @@ -307,6 +309,46 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun `connection repository factory - reuses log buffers for same connection`() = + runBlocking(IMMEDIATE) { + val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock()) + + connectionFactory = + MobileConnectionRepositoryImpl.Factory( + fakeBroadcastDispatcher, + context = context, + telephonyManager = telephonyManager, + bgDispatcher = IMMEDIATE, + globalSettings = globalSettings, + logger = logger, + mobileMappingsProxy = mobileMappings, + scope = scope, + logFactory = realLoggerFactory, + ) + + // Create two connections for the same subId. Similar to if the connection appeared + // and disappeared from the connectionFactory's perspective + val connection1 = + connectionFactory.build( + 1, + NetworkNameModel.Default("default_name"), + "-", + underTest.globalMobileDataSettingChangedEvent, + ) + + val connection1_repeat = + connectionFactory.build( + 1, + NetworkNameModel.Default("default_name"), + "-", + underTest.globalMobileDataSettingChangedEvent, + ) + + assertThat(connection1.tableLogBuffer) + .isSameInstanceAs(connection1_repeat.tableLogBuffer) + } + + @Test fun mobileConnectivity_default() { assertThat(underTest.defaultMobileNetworkConnectivity.value) .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 4b32ee262cdc..0cca7b2aa38c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -390,19 +390,27 @@ public class RemoteInputViewTest extends SysuiTestCase { bindController(view, row.getEntry()); view.setVisibility(View.GONE); - View crossFadeView = new View(mContext); + View fadeOutView = new View(mContext); + fadeOutView.setId(com.android.internal.R.id.actions_container_layout); - // Start focus animation - view.focusAnimated(crossFadeView); + FrameLayout parent = new FrameLayout(mContext); + parent.addView(view); + parent.addView(fadeOutView); + // Start focus animation + view.focusAnimated(); assertTrue(view.isAnimatingAppearance()); + // fast forward to 1 ms before end of animation and verify fadeOutView has alpha set to 0f + mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD - 1); + assertEquals(0f, fadeOutView.getAlpha()); + // fast forward to end of animation - mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD); + mAnimatorTestRule.advanceTimeBy(1); - // assert that crossFadeView's alpha is reset to 1f after the animation (hidden behind + // assert that fadeOutView's alpha is reset to 1f after the animation (hidden behind // RemoteInputView) - assertEquals(1f, crossFadeView.getAlpha()); + assertEquals(1f, fadeOutView.getAlpha()); assertFalse(view.isAnimatingAppearance()); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(1f, view.getAlpha()); @@ -415,20 +423,27 @@ public class RemoteInputViewTest extends SysuiTestCase { mDependency, TestableLooper.get(this)); ExpandableNotificationRow row = helper.createRow(); - FrameLayout remoteInputViewParent = new FrameLayout(mContext); RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); - remoteInputViewParent.addView(view); bindController(view, row.getEntry()); + View fadeInView = new View(mContext); + fadeInView.setId(com.android.internal.R.id.actions_container_layout); + + FrameLayout parent = new FrameLayout(mContext); + parent.addView(view); + parent.addView(fadeInView); + // Start defocus animation - view.onDefocus(true, false); + view.onDefocus(true /* animate */, false /* logClose */, null /* doAfterDefocus */); assertEquals(View.VISIBLE, view.getVisibility()); + assertEquals(0f, fadeInView.getAlpha()); // fast forward to end of animation mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD); // assert that RemoteInputView is no longer visible assertEquals(View.GONE, view.getVisibility()); + assertEquals(1f, fadeInView.getAlpha()); } // NOTE: because we're refactoring the RemoteInputView and moving logic into the diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt deleted file mode 100644 index 8dd088f5760c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.stylus - -import android.content.Context -import android.hardware.BatteryState -import android.hardware.input.InputManager -import android.os.Handler -import android.testing.AndroidTestingRunner -import android.view.InputDevice -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.time.FakeSystemClock -import org.junit.Before -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.Mockito.verifyZeroInteractions -import org.mockito.MockitoAnnotations - -@RunWith(AndroidTestingRunner::class) -@SmallTest -@Ignore("TODO(b/20579491): unignore on main") -class StylusFirstUsageListenerTest : SysuiTestCase() { - @Mock lateinit var context: Context - @Mock lateinit var inputManager: InputManager - @Mock lateinit var stylusManager: StylusManager - @Mock lateinit var featureFlags: FeatureFlags - @Mock lateinit var internalStylusDevice: InputDevice - @Mock lateinit var otherDevice: InputDevice - @Mock lateinit var externalStylusDevice: InputDevice - @Mock lateinit var batteryState: BatteryState - @Mock lateinit var handler: Handler - - private lateinit var stylusListener: StylusFirstUsageListener - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true) - whenever(inputManager.isStylusEverUsed(context)).thenReturn(false) - - stylusListener = - StylusFirstUsageListener( - context, - inputManager, - stylusManager, - featureFlags, - EXECUTOR, - handler - ) - stylusListener.hasStarted = false - - whenever(handler.post(any())).thenAnswer { - (it.arguments[0] as Runnable).run() - true - } - - whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false) - whenever(internalStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) - whenever(internalStylusDevice.isExternal).thenReturn(false) - whenever(externalStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) - whenever(externalStylusDevice.isExternal).thenReturn(true) - - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf()) - whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice) - whenever(inputManager.getInputDevice(INTERNAL_STYLUS_DEVICE_ID)) - .thenReturn(internalStylusDevice) - whenever(inputManager.getInputDevice(EXTERNAL_STYLUS_DEVICE_ID)) - .thenReturn(externalStylusDevice) - } - - @Test - fun start_flagDisabled_doesNotRegister() { - whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(false) - - stylusListener.start() - - verify(stylusManager, never()).registerCallback(any()) - verify(inputManager, never()).setStylusEverUsed(context, true) - } - - @Test - fun start_toggleHasStarted() { - stylusListener.start() - - assert(stylusListener.hasStarted) - } - - @Test - fun start_hasStarted_doesNotRegister() { - stylusListener.hasStarted = true - - stylusListener.start() - - verify(stylusManager, never()).registerCallback(any()) - } - - @Test - fun start_hostDeviceDoesNotSupportStylus_doesNotRegister() { - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(OTHER_DEVICE_ID)) - - stylusListener.start() - - verify(stylusManager, never()).registerCallback(any()) - verify(inputManager, never()).setStylusEverUsed(context, true) - } - - @Test - fun start_stylusEverUsed_doesNotRegister() { - whenever(inputManager.inputDeviceIds) - .thenReturn(intArrayOf(OTHER_DEVICE_ID, INTERNAL_STYLUS_DEVICE_ID)) - whenever(inputManager.isStylusEverUsed(context)).thenReturn(true) - - stylusListener.start() - - verify(stylusManager, never()).registerCallback(any()) - verify(inputManager, never()).setStylusEverUsed(context, true) - } - - @Test - fun start_hostDeviceSupportsStylus_registersListener() { - whenever(inputManager.inputDeviceIds) - .thenReturn(intArrayOf(OTHER_DEVICE_ID, INTERNAL_STYLUS_DEVICE_ID)) - - stylusListener.start() - - verify(stylusManager).registerCallback(any()) - verify(inputManager, never()).setStylusEverUsed(context, true) - } - - @Test - fun onStylusAdded_hasNotStarted_doesNotRegisterListener() { - stylusListener.hasStarted = false - - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - - verifyZeroInteractions(inputManager) - } - - @Test - fun onStylusAdded_internalStylus_registersListener() { - stylusListener.hasStarted = true - - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - - verify(inputManager, times(1)) - .addInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, EXECUTOR, stylusListener) - } - - @Test - fun onStylusAdded_externalStylus_doesNotRegisterListener() { - stylusListener.hasStarted = true - - stylusListener.onStylusAdded(EXTERNAL_STYLUS_DEVICE_ID) - - verify(inputManager, never()).addInputDeviceBatteryListener(any(), any(), any()) - } - - @Test - fun onStylusAdded_otherDevice_doesNotRegisterListener() { - stylusListener.onStylusAdded(OTHER_DEVICE_ID) - - verify(inputManager, never()).addInputDeviceBatteryListener(any(), any(), any()) - } - - @Test - fun onStylusRemoved_registeredDevice_unregistersListener() { - stylusListener.hasStarted = true - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - - stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID) - - verify(inputManager, times(1)) - .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener) - } - - @Test - fun onStylusRemoved_hasNotStarted_doesNotUnregisterListener() { - stylusListener.hasStarted = false - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - - stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID) - - verifyZeroInteractions(inputManager) - } - - @Test - fun onStylusRemoved_unregisteredDevice_doesNotUnregisterListener() { - stylusListener.hasStarted = true - - stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID) - - verifyNoMoreInteractions(inputManager) - } - - @Test - fun onStylusBluetoothConnected_updateStylusFlagAndUnregisters() { - stylusListener.hasStarted = true - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - - stylusListener.onStylusBluetoothConnected(EXTERNAL_STYLUS_DEVICE_ID, "ANY") - - verify(inputManager).setStylusEverUsed(context, true) - verify(inputManager, times(1)) - .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener) - verify(stylusManager).unregisterCallback(stylusListener) - } - - @Test - fun onStylusBluetoothConnected_hasNotStarted_doesNoting() { - stylusListener.hasStarted = false - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - - stylusListener.onStylusBluetoothConnected(EXTERNAL_STYLUS_DEVICE_ID, "ANY") - - verifyZeroInteractions(inputManager) - verifyZeroInteractions(stylusManager) - } - - @Test - fun onBatteryStateChanged_batteryPresent_updateStylusFlagAndUnregisters() { - stylusListener.hasStarted = true - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - whenever(batteryState.isPresent).thenReturn(true) - - stylusListener.onBatteryStateChanged(0, 1, batteryState) - - verify(inputManager).setStylusEverUsed(context, true) - verify(inputManager, times(1)) - .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener) - verify(stylusManager).unregisterCallback(stylusListener) - } - - @Test - fun onBatteryStateChanged_batteryNotPresent_doesNotUpdateFlagOrUnregister() { - stylusListener.hasStarted = true - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - whenever(batteryState.isPresent).thenReturn(false) - - stylusListener.onBatteryStateChanged(0, 1, batteryState) - - verifyZeroInteractions(stylusManager) - verify(inputManager, never()) - .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener) - } - - @Test - fun onBatteryStateChanged_hasNotStarted_doesNothing() { - stylusListener.hasStarted = false - stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID) - whenever(batteryState.isPresent).thenReturn(false) - - stylusListener.onBatteryStateChanged(0, 1, batteryState) - - verifyZeroInteractions(inputManager) - verifyZeroInteractions(stylusManager) - } - - companion object { - private const val OTHER_DEVICE_ID = 0 - private const val INTERNAL_STYLUS_DEVICE_ID = 1 - private const val EXTERNAL_STYLUS_DEVICE_ID = 2 - private val EXECUTOR = FakeExecutor(FakeSystemClock()) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt index 984de5b67bf5..6d6e40a90fa6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt @@ -17,12 +17,15 @@ package com.android.systemui.stylus import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice +import android.hardware.BatteryState import android.hardware.input.InputManager import android.os.Handler import android.testing.AndroidTestingRunner import android.view.InputDevice import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import java.util.concurrent.Executor @@ -31,30 +34,27 @@ import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.inOrder import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest -@Ignore("b/257936830 until bt APIs") class StylusManagerTest : SysuiTestCase() { @Mock lateinit var inputManager: InputManager - @Mock lateinit var stylusDevice: InputDevice - @Mock lateinit var btStylusDevice: InputDevice - @Mock lateinit var otherDevice: InputDevice - + @Mock lateinit var batteryState: BatteryState @Mock lateinit var bluetoothAdapter: BluetoothAdapter - @Mock lateinit var bluetoothDevice: BluetoothDevice - @Mock lateinit var handler: Handler + @Mock lateinit var featureFlags: FeatureFlags @Mock lateinit var stylusCallback: StylusManager.StylusCallback @@ -75,11 +75,8 @@ class StylusManagerTest : SysuiTestCase() { true } - stylusManager = StylusManager(inputManager, bluetoothAdapter, handler, EXECUTOR) - - stylusManager.registerCallback(stylusCallback) - - stylusManager.registerBatteryCallback(stylusBatteryCallback) + stylusManager = + StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false) whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) @@ -92,19 +89,47 @@ class StylusManagerTest : SysuiTestCase() { whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice) whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice) whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID)) + whenever(inputManager.isStylusEverUsed(mContext)).thenReturn(false) whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice) whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS) + + whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true) + + stylusManager.startListener() + stylusManager.registerCallback(stylusCallback) + stylusManager.registerBatteryCallback(stylusBatteryCallback) + clearInvocations(inputManager) } @Test - fun startListener_registersInputDeviceListener() { + fun startListener_hasNotStarted_registersInputDeviceListener() { + stylusManager = + StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + stylusManager.startListener() verify(inputManager, times(1)).registerInputDeviceListener(any(), any()) } @Test + fun startListener_hasStarted_doesNothing() { + stylusManager.startListener() + + verifyZeroInteractions(inputManager) + } + + @Test + fun onInputDeviceAdded_hasNotStarted_doesNothing() { + stylusManager = + StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + verifyZeroInteractions(stylusCallback) + } + + @Test fun onInputDeviceAdded_multipleRegisteredCallbacks_callsAll() { stylusManager.registerCallback(otherStylusCallback) @@ -117,6 +142,26 @@ class StylusManagerTest : SysuiTestCase() { } @Test + fun onInputDeviceAdded_internalStylus_registersBatteryListener() { + whenever(stylusDevice.isExternal).thenReturn(false) + + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + verify(inputManager, times(1)) + .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, EXECUTOR, stylusManager) + } + + @Test + fun onInputDeviceAdded_externalStylus_doesNotRegisterbatteryListener() { + whenever(stylusDevice.isExternal).thenReturn(true) + + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + verify(inputManager, never()) + .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, EXECUTOR, stylusManager) + } + + @Test fun onInputDeviceAdded_stylus_callsCallbacksOnStylusAdded() { stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) @@ -125,6 +170,23 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") + fun onInputDeviceAdded_btStylus_firstUsed_callsCallbacksOnStylusFirstUsed() { + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + + verify(stylusCallback, times(1)).onStylusFirstUsed() + } + + @Test + @Ignore("b/257936830 until bt APIs") + fun onInputDeviceAdded_btStylus_firstUsed_setsFlag() { + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + + verify(inputManager, times(1)).setStylusEverUsed(mContext, true) + } + + @Test + @Ignore("b/257936830 until bt APIs") fun onInputDeviceAdded_btStylus_callsCallbacksWithAddress() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -143,6 +205,17 @@ class StylusManagerTest : SysuiTestCase() { } @Test + fun onInputDeviceChanged_hasNotStarted_doesNothing() { + stylusManager = + StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + + stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID) + + verifyZeroInteractions(stylusCallback) + } + + @Test + @Ignore("b/257936830 until bt APIs") fun onInputDeviceChanged_multipleRegisteredCallbacks_callsAll() { stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) // whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS) @@ -157,6 +230,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onInputDeviceChanged_stylusNewBtConnection_callsCallbacks() { stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) // whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS) @@ -168,6 +242,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onInputDeviceChanged_stylusLostBtConnection_callsCallbacks() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) // whenever(btStylusDevice.bluetoothAddress).thenReturn(null) @@ -179,6 +254,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onInputDeviceChanged_btConnection_stylusAlreadyBtConnected_onlyCallsListenersOnce() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -189,6 +265,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onInputDeviceChanged_noBtConnection_stylusNeverBtConnected_doesNotCallCallbacks() { stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) @@ -198,6 +275,17 @@ class StylusManagerTest : SysuiTestCase() { } @Test + fun onInputDeviceRemoved_hasNotStarted_doesNothing() { + stylusManager = + StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags) + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID) + + verifyZeroInteractions(stylusCallback) + } + + @Test fun onInputDeviceRemoved_multipleRegisteredCallbacks_callsAll() { stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) stylusManager.registerCallback(otherStylusCallback) @@ -219,6 +307,17 @@ class StylusManagerTest : SysuiTestCase() { } @Test + fun onInputDeviceRemoved_unregistersBatteryListener() { + stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID) + + stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID) + + verify(inputManager, times(1)) + .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, stylusManager) + } + + @Test + @Ignore("b/257936830 until bt APIs") fun onInputDeviceRemoved_btStylus_callsCallbacks() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -232,6 +331,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onStylusBluetoothConnected_registersMetadataListener() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -239,6 +339,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onStylusBluetoothConnected_noBluetoothDevice_doesNotRegisterMetadataListener() { whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(null) @@ -248,6 +349,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onStylusBluetoothDisconnected_unregistersMetadataListener() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -257,6 +359,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) stylusManager.registerBatteryCallback(otherStylusBatteryCallback) @@ -274,6 +377,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -288,6 +392,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onMetadataChanged_chargingStateFalse_executesBatteryCallbacks() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -302,6 +407,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onMetadataChanged_chargingStateNoDevice_doesNotExecuteBatteryCallbacks() { stylusManager.onMetadataChanged( bluetoothDevice, @@ -313,6 +419,7 @@ class StylusManagerTest : SysuiTestCase() { } @Test + @Ignore("b/257936830 until bt APIs") fun onMetadataChanged_notChargingState_doesNotExecuteBatteryCallbacks() { stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) @@ -326,6 +433,63 @@ class StylusManagerTest : SysuiTestCase() { .onStylusBluetoothChargingStateChanged(any(), any(), any()) } + @Test + @Ignore("TODO(b/261826950): remove on main") + fun onBatteryStateChanged_batteryPresent_stylusNeverUsed_updateEverUsedFlag() { + whenever(batteryState.isPresent).thenReturn(true) + + stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) + + verify(inputManager).setStylusEverUsed(mContext, true) + } + + @Test + @Ignore("TODO(b/261826950): remove on main") + fun onBatteryStateChanged_batteryPresent_stylusNeverUsed_executesStylusFirstUsed() { + whenever(batteryState.isPresent).thenReturn(true) + + stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) + + verify(stylusCallback, times(1)).onStylusFirstUsed() + } + + @Test + @Ignore("TODO(b/261826950): remove on main") + fun onBatteryStateChanged_batteryPresent_stylusUsed_doesNotUpdateEverUsedFlag() { + whenever(inputManager.isStylusEverUsed(mContext)).thenReturn(true) + whenever(batteryState.isPresent).thenReturn(true) + + stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) + + verify(inputManager, never()).setStylusEverUsed(mContext, true) + } + + @Test + @Ignore("TODO(b/261826950): remove on main") + fun onBatteryStateChanged_batteryNotPresent_doesNotUpdateEverUsedFlag() { + whenever(batteryState.isPresent).thenReturn(false) + + stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) + + verify(inputManager, never()) + .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, stylusManager) + } + + @Test + fun onBatteryStateChanged_hasNotStarted_doesNothing() { + stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) + + verifyZeroInteractions(inputManager) + } + + @Test + fun onBatteryStateChanged_executesBatteryCallbacks() { + stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) + + verify(stylusBatteryCallback, times(1)) + .onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) + } + companion object { private val EXECUTOR = Executor { r -> r.run() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt index ff382a3ec19f..117e00dd9b2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt @@ -25,17 +25,15 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.util.mockito.whenever -import java.util.concurrent.Executor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock -import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -60,7 +58,6 @@ class StylusUsiPowerStartableTest : SysuiTestCase() { inputManager, stylusUsiPowerUi, featureFlags, - DIRECT_EXECUTOR, ) whenever(featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)).thenReturn(true) @@ -79,40 +76,12 @@ class StylusUsiPowerStartableTest : SysuiTestCase() { } @Test - fun start_addsBatteryListenerForInternalStylus() { - startable.start() - - verify(inputManager, times(1)) - .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable) - } - - @Test - fun onStylusAdded_internalStylus_addsBatteryListener() { - startable.onStylusAdded(STYLUS_DEVICE_ID) - - verify(inputManager, times(1)) - .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable) - } + fun start_hostDeviceDoesNotSupportStylus_doesNotRegister() { + whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(EXTERNAL_DEVICE_ID)) - @Test - fun onStylusAdded_externalStylus_doesNotAddBatteryListener() { - startable.onStylusAdded(EXTERNAL_DEVICE_ID) - - verify(inputManager, never()) - .addInputDeviceBatteryListener(EXTERNAL_DEVICE_ID, DIRECT_EXECUTOR, startable) - } + startable.start() - @Test - fun onStylusRemoved_registeredStylus_removesBatteryListener() { - startable.onStylusAdded(STYLUS_DEVICE_ID) - startable.onStylusRemoved(STYLUS_DEVICE_ID) - - inOrder(inputManager).let { - it.verify(inputManager, times(1)) - .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, DIRECT_EXECUTOR, startable) - it.verify(inputManager, times(1)) - .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, startable) - } + verifyZeroInteractions(stylusManager) } @Test @@ -130,28 +99,26 @@ class StylusUsiPowerStartableTest : SysuiTestCase() { } @Test - fun onBatteryStateChanged_batteryPresent_refreshesNotification() { + fun onStylusUsiBatteryStateChanged_batteryPresent_refreshesNotification() { val batteryState = mock(BatteryState::class.java) whenever(batteryState.isPresent).thenReturn(true) - startable.onBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState) + startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState) verify(stylusUsiPowerUi, times(1)).updateBatteryState(batteryState) } @Test - fun onBatteryStateChanged_batteryNotPresent_noop() { + fun onStylusUsiBatteryStateChanged_batteryNotPresent_noop() { val batteryState = mock(BatteryState::class.java) whenever(batteryState.isPresent).thenReturn(false) - startable.onBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState) + startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState) verifyNoMoreInteractions(stylusUsiPowerUi) } companion object { - private val DIRECT_EXECUTOR = Executor { r -> r.run() } - private const val EXTERNAL_DEVICE_ID = 0 private const val STYLUS_DEVICE_ID = 1 } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt index 59875507341d..a7951f4fa068 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.stylus +import android.app.Notification import android.hardware.BatteryState import android.hardware.input.InputManager import android.os.Handler @@ -28,10 +29,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever +import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.inOrder import org.mockito.Mockito.times @@ -46,6 +50,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() { @Mock lateinit var inputManager: InputManager @Mock lateinit var handler: Handler @Mock lateinit var btStylusDevice: InputDevice + @Captor lateinit var notificationCaptor: ArgumentCaptor<Notification> private lateinit var stylusUsiPowerUi: StylusUsiPowerUI @@ -70,7 +75,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() { fun updateBatteryState_capacityBelowThreshold_notifies() { stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) - verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any()) + verify(notificationManager, times(1)) + .notify(eq(R.string.stylus_battery_low_percentage), any()) verifyNoMoreInteractions(notificationManager) } @@ -78,7 +84,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() { fun updateBatteryState_capacityAboveThreshold_cancelsNotificattion() { stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f)) - verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low) + verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage) verifyNoMoreInteractions(notificationManager) } @@ -88,8 +94,9 @@ class StylusUsiPowerUiTest : SysuiTestCase() { stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f)) inOrder(notificationManager).let { - it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any()) - it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low) + it.verify(notificationManager, times(1)) + .notify(eq(R.string.stylus_battery_low_percentage), any()) + it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage) it.verifyNoMoreInteractions() } } @@ -99,7 +106,16 @@ class StylusUsiPowerUiTest : SysuiTestCase() { stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.15f)) - verify(notificationManager, times(2)).notify(eq(R.string.stylus_battery_low), any()) + verify(notificationManager, times(2)) + .notify(eq(R.string.stylus_battery_low_percentage), notificationCaptor.capture()) + assertEquals( + notificationCaptor.value.extras.getString(Notification.EXTRA_TITLE), + context.getString(R.string.stylus_battery_low_percentage, "15%") + ) + assertEquals( + notificationCaptor.value.extras.getString(Notification.EXTRA_TEXT), + context.getString(R.string.stylus_battery_low_subtitle) + ) verifyNoMoreInteractions(notificationManager) } @@ -110,9 +126,11 @@ class StylusUsiPowerUiTest : SysuiTestCase() { stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) inOrder(notificationManager).let { - it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any()) - it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low) - it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any()) + it.verify(notificationManager, times(1)) + .notify(eq(R.string.stylus_battery_low_percentage), any()) + it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage) + it.verify(notificationManager, times(1)) + .notify(eq(R.string.stylus_battery_low_percentage), any()) it.verifyNoMoreInteractions() } } @@ -121,7 +139,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() { fun updateSuppression_noExistingNotification_cancelsNotification() { stylusUsiPowerUi.updateSuppression(true) - verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low) + verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage) verifyNoMoreInteractions(notificationManager) } @@ -132,8 +150,9 @@ class StylusUsiPowerUiTest : SysuiTestCase() { stylusUsiPowerUi.updateSuppression(true) inOrder(notificationManager).let { - it.verify(notificationManager, times(1)).notify(eq(R.string.stylus_battery_low), any()) - it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low) + it.verify(notificationManager, times(1)) + .notify(eq(R.string.stylus_battery_low_percentage), any()) + it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage) it.verifyNoMoreInteractions() } } @@ -156,7 +175,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() { stylusUsiPowerUi.refresh() - verify(notificationManager).cancel(R.string.stylus_battery_low) + verify(notificationManager).cancel(R.string.stylus_battery_low_percentage) } class FixedCapacityBatteryState(private val capacity: Float) : BatteryState() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt index 89402de792dc..30c4f97e5503 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.shade.NotificationPanelViewController +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.unfold.util.FoldableDeviceStates @@ -73,6 +74,8 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { @Mock lateinit var viewTreeObserver: ViewTreeObserver + @Mock private lateinit var commandQueue: CommandQueue + @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener> private lateinit var deviceStates: FoldableDeviceStates @@ -102,7 +105,8 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { } keyguardRepository = FakeKeyguardRepository() - val keyguardInteractor = KeyguardInteractor(repository = keyguardRepository) + val keyguardInteractor = + KeyguardInteractor(repository = keyguardRepository, commandQueue = commandQueue) // Needs to be run on the main thread runBlocking(IMMEDIATE) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt index 78b0cbe8c718..9bb52be276f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt @@ -36,12 +36,14 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Text +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.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -60,12 +62,12 @@ 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 -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope -import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +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 @@ -73,10 +75,12 @@ import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class UserInteractorTest : SysuiTestCase() { @@ -90,10 +94,11 @@ class UserInteractorTest : SysuiTestCase() { @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: UserInteractor - private lateinit var testCoroutineScope: TestCoroutineScope + private lateinit var testScope: TestScope private lateinit var userRepository: FakeUserRepository private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var telephonyRepository: FakeTelephonyRepository @@ -117,11 +122,12 @@ class UserInteractorTest : SysuiTestCase() { userRepository = FakeUserRepository() keyguardRepository = FakeKeyguardRepository() telephonyRepository = FakeTelephonyRepository() - testCoroutineScope = TestCoroutineScope() + val testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) val refreshUsersScheduler = RefreshUsersScheduler( - applicationScope = testCoroutineScope, - mainDispatcher = IMMEDIATE, + applicationScope = testScope.backgroundScope, + mainDispatcher = testDispatcher, repository = userRepository, ) underTest = @@ -132,23 +138,24 @@ class UserInteractorTest : SysuiTestCase() { keyguardInteractor = KeyguardInteractor( repository = keyguardRepository, + commandQueue = commandQueue, ), manager = manager, - applicationScope = testCoroutineScope, + applicationScope = testScope.backgroundScope, telephonyInteractor = TelephonyInteractor( repository = telephonyRepository, ), broadcastDispatcher = fakeBroadcastDispatcher, - backgroundDispatcher = IMMEDIATE, + backgroundDispatcher = testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = GuestUserInteractor( applicationContext = context, - applicationScope = testCoroutineScope, - mainDispatcher = IMMEDIATE, - backgroundDispatcher = IMMEDIATE, + applicationScope = testScope.backgroundScope, + mainDispatcher = testDispatcher, + backgroundDispatcher = testDispatcher, manager = manager, repository = userRepository, deviceProvisionedController = deviceProvisionedController, @@ -164,7 +171,7 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `onRecordSelected - user`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -179,7 +186,7 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `onRecordSelected - switch to guest user`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = true) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -193,7 +200,7 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `onRecordSelected - enter guest mode`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -202,6 +209,7 @@ class UserInteractorTest : SysuiTestCase() { whenever(manager.createGuest(any())).thenReturn(guestUserInfo) underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower) + runCurrent() verify(dialogShower).dismiss() verify(manager).createGuest(any()) @@ -210,7 +218,7 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `onRecordSelected - action`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = true) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -224,81 +232,72 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `users - switcher enabled`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = true) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var value: List<UserModel>? = null - val job = underTest.users.onEach { value = it }.launchIn(this) - assertUsers(models = value, count = 3, includeGuest = true) + val value = collectLastValue(underTest.users) - job.cancel() + assertUsers(models = value(), count = 3, includeGuest = true) } @Test fun `users - switches to second user`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var value: List<UserModel>? = null - val job = underTest.users.onEach { value = it }.launchIn(this) + val value = collectLastValue(underTest.users) userRepository.setSelectedUserInfo(userInfos[1]) - assertUsers(models = value, count = 2, selectedIndex = 1) - job.cancel() + assertUsers(models = value(), count = 2, selectedIndex = 1) } @Test fun `users - switcher not enabled`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false)) - var value: List<UserModel>? = null - val job = underTest.users.onEach { value = it }.launchIn(this) - assertUsers(models = value, count = 1) - - job.cancel() + val value = collectLastValue(underTest.users) + assertUsers(models = value(), count = 1) } @Test fun selectedUser() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var value: UserModel? = null - val job = underTest.selectedUser.onEach { value = it }.launchIn(this) - assertUser(value, id = 0, isSelected = true) + val value = collectLastValue(underTest.selectedUser) + assertUser(value(), id = 0, isSelected = true) userRepository.setSelectedUserInfo(userInfos[1]) - assertUser(value, id = 1, isSelected = true) - - job.cancel() + assertUser(value(), id = 1, isSelected = true) } @Test fun `actions - device unlocked`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) keyguardRepository.setKeyguardShowing(false) - var value: List<UserActionModel>? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) + val value = collectLastValue(underTest.actions) + + runCurrent() - assertThat(value) + assertThat(value()) .isEqualTo( listOf( UserActionModel.ENTER_GUEST_MODE, @@ -307,13 +306,11 @@ class UserInteractorTest : SysuiTestCase() { UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) ) - - job.cancel() } @Test fun `actions - device unlocked - full screen`() = - runBlocking(IMMEDIATE) { + testScope.runTest { featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 2, includeGuest = false) @@ -321,10 +318,9 @@ class UserInteractorTest : SysuiTestCase() { userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) keyguardRepository.setKeyguardShowing(false) - var value: List<UserActionModel>? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) + val value = collectLastValue(underTest.actions) - assertThat(value) + assertThat(value()) .isEqualTo( listOf( UserActionModel.ADD_USER, @@ -333,46 +329,38 @@ class UserInteractorTest : SysuiTestCase() { UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) ) - - job.cancel() } @Test fun `actions - device unlocked user not primary - empty list`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[1]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) keyguardRepository.setKeyguardShowing(false) - var value: List<UserActionModel>? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) + val value = collectLastValue(underTest.actions) - assertThat(value).isEqualTo(emptyList<UserActionModel>()) - - job.cancel() + assertThat(value()).isEqualTo(emptyList<UserActionModel>()) } @Test fun `actions - device unlocked user is guest - empty list`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = true) assertThat(userInfos[1].isGuest).isTrue() userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[1]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) keyguardRepository.setKeyguardShowing(false) - var value: List<UserActionModel>? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) - - assertThat(value).isEqualTo(emptyList<UserActionModel>()) + val value = collectLastValue(underTest.actions) - job.cancel() + assertThat(value()).isEqualTo(emptyList<UserActionModel>()) } @Test fun `actions - device locked add from lockscreen set - full list`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -383,10 +371,9 @@ class UserInteractorTest : SysuiTestCase() { ) ) keyguardRepository.setKeyguardShowing(false) - var value: List<UserActionModel>? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) + val value = collectLastValue(underTest.actions) - assertThat(value) + assertThat(value()) .isEqualTo( listOf( UserActionModel.ENTER_GUEST_MODE, @@ -395,13 +382,11 @@ class UserInteractorTest : SysuiTestCase() { UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) ) - - job.cancel() } @Test fun `actions - device locked add from lockscreen set - full list - full screen`() = - runBlocking(IMMEDIATE) { + testScope.runTest { featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) @@ -413,10 +398,9 @@ class UserInteractorTest : SysuiTestCase() { ) ) keyguardRepository.setKeyguardShowing(false) - var value: List<UserActionModel>? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) + val value = collectLastValue(underTest.actions) - assertThat(value) + assertThat(value()) .isEqualTo( listOf( UserActionModel.ADD_USER, @@ -425,39 +409,33 @@ class UserInteractorTest : SysuiTestCase() { UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) ) - - job.cancel() } @Test fun `actions - device locked - only manage user is shown`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) keyguardRepository.setKeyguardShowing(true) - var value: List<UserActionModel>? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) + val value = collectLastValue(underTest.actions) - assertThat(value).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)) - - job.cancel() + assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)) } @Test fun `executeAction - add user - dialog shown`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) keyguardRepository.setKeyguardShowing(false) - var dialogRequest: ShowDialogRequestModel? = null - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + val dialogRequest = collectLastValue(underTest.dialogShowRequests) val dialogShower: UserSwitchDialogController.DialogShower = mock() underTest.executeAction(UserActionModel.ADD_USER, dialogShower) - assertThat(dialogRequest) + assertThat(dialogRequest()) .isEqualTo( ShowDialogRequestModel.ShowAddUserDialog( userHandle = userInfos[0].userHandle, @@ -468,14 +446,12 @@ class UserInteractorTest : SysuiTestCase() { ) underTest.onDialogShown() - assertThat(dialogRequest).isNull() - - job.cancel() + assertThat(dialogRequest()).isNull() } @Test - fun `executeAction - add supervised user - starts activity`() = - runBlocking(IMMEDIATE) { + fun `executeAction - add supervised user - dismisses dialog and starts activity`() = + testScope.runTest { underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER) val intentCaptor = kotlinArgumentCaptor<Intent>() @@ -487,7 +463,7 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `executeAction - navigate to manage users`() = - runBlocking(IMMEDIATE) { + testScope.runTest { underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) val intentCaptor = kotlinArgumentCaptor<Intent>() @@ -497,7 +473,7 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `executeAction - guest mode`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -505,110 +481,103 @@ class UserInteractorTest : SysuiTestCase() { val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true) whenever(manager.createGuest(any())).thenReturn(guestUserInfo) val dialogRequests = mutableListOf<ShowDialogRequestModel?>() - val showDialogsJob = - underTest.dialogShowRequests - .onEach { - dialogRequests.add(it) - if (it != null) { - underTest.onDialogShown() - } + backgroundScope.launch { + underTest.dialogShowRequests.collect { + dialogRequests.add(it) + if (it != null) { + underTest.onDialogShown() } - .launchIn(this) - val dismissDialogsJob = - underTest.dialogDismissRequests - .onEach { - if (it != null) { - underTest.onDialogDismissed() - } + } + } + backgroundScope.launch { + underTest.dialogDismissRequests.collect { + if (it != null) { + underTest.onDialogDismissed() } - .launchIn(this) + } + } underTest.executeAction(UserActionModel.ENTER_GUEST_MODE) + runCurrent() assertThat(dialogRequests) .contains( ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true), ) verify(activityManager).switchUser(guestUserInfo.id) - - showDialogsJob.cancel() - dismissDialogsJob.cancel() } @Test fun `selectUser - already selected guest re-selected - exit guest dialog`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = true) val guestUserInfo = userInfos[1] assertThat(guestUserInfo.isGuest).isTrue() userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(guestUserInfo) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var dialogRequest: ShowDialogRequestModel? = null - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + val dialogRequest = collectLastValue(underTest.dialogShowRequests) underTest.selectUser( newlySelectedUserId = guestUserInfo.id, dialogShower = dialogShower, ) - assertThat(dialogRequest) + assertThat(dialogRequest()) .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java) verify(dialogShower, never()).dismiss() - job.cancel() } @Test fun `selectUser - currently guest non-guest selected - exit guest dialog`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = true) val guestUserInfo = userInfos[1] assertThat(guestUserInfo.isGuest).isTrue() userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(guestUserInfo) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var dialogRequest: ShowDialogRequestModel? = null - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + val dialogRequest = collectLastValue(underTest.dialogShowRequests) underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower) - assertThat(dialogRequest) + assertThat(dialogRequest()) .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java) verify(dialogShower, never()).dismiss() - job.cancel() } @Test fun `selectUser - not currently guest - switches users`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var dialogRequest: ShowDialogRequestModel? = null - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + val dialogRequest = collectLastValue(underTest.dialogShowRequests) underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower) - assertThat(dialogRequest).isNull() + assertThat(dialogRequest()).isNull() verify(activityManager).switchUser(userInfos[1].id) verify(dialogShower).dismiss() - job.cancel() } @Test fun `Telephony call state changes - refreshes users`() = - runBlocking(IMMEDIATE) { + testScope.runTest { + runCurrent() + val refreshUsersCallCount = userRepository.refreshUsersCallCount telephonyRepository.setCallState(1) + runCurrent() assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) } @Test fun `User switched broadcast`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -617,9 +586,11 @@ class UserInteractorTest : SysuiTestCase() { val callback2: UserInteractor.UserCallback = mock() underTest.addCallback(callback1) underTest.addCallback(callback2) + runCurrent() val refreshUsersCallCount = userRepository.refreshUsersCallCount userRepository.setSelectedUserInfo(userInfos[1]) + runCurrent() fakeBroadcastDispatcher.registeredReceivers.forEach { it.onReceive( context, @@ -627,16 +598,17 @@ class UserInteractorTest : SysuiTestCase() { .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id), ) } + runCurrent() - verify(callback1).onUserStateChanged() - verify(callback2).onUserStateChanged() + verify(callback1, atLeastOnce()).onUserStateChanged() + verify(callback2, atLeastOnce()).onUserStateChanged() assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id) assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) } @Test fun `User info changed broadcast`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -649,12 +621,14 @@ class UserInteractorTest : SysuiTestCase() { ) } + runCurrent() + assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) } @Test fun `System user unlocked broadcast - refresh users`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -667,13 +641,14 @@ class UserInteractorTest : SysuiTestCase() { .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM), ) } + runCurrent() assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) } @Test fun `Non-system user unlocked broadcast - do not refresh users`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -691,14 +666,14 @@ class UserInteractorTest : SysuiTestCase() { @Test fun userRecords() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = false) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) keyguardRepository.setKeyguardShowing(false) - testCoroutineScope.advanceUntilIdle() + runCurrent() assertRecords( records = underTest.userRecords.value, @@ -717,7 +692,7 @@ class UserInteractorTest : SysuiTestCase() { @Test fun userRecordsFullScreen() = - runBlocking(IMMEDIATE) { + testScope.runTest { featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 3, includeGuest = false) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) @@ -725,7 +700,7 @@ class UserInteractorTest : SysuiTestCase() { userRepository.setSelectedUserInfo(userInfos[0]) keyguardRepository.setKeyguardShowing(false) - testCoroutineScope.advanceUntilIdle() + runCurrent() assertRecords( records = underTest.userRecords.value, @@ -744,7 +719,7 @@ class UserInteractorTest : SysuiTestCase() { @Test fun selectedUserRecord() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = true) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) userRepository.setUserInfos(userInfos) @@ -762,63 +737,54 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `users - secondary user - guest user can be switched to`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = true) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[1]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var res: List<UserModel>? = null - val job = underTest.users.onEach { res = it }.launchIn(this) - assertThat(res?.size == 3).isTrue() - assertThat(res?.find { it.isGuest }).isNotNull() - job.cancel() + val res = collectLastValue(underTest.users) + assertThat(res()?.size == 3).isTrue() + assertThat(res()?.find { it.isGuest }).isNotNull() } @Test fun `users - secondary user - no guest action`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = true) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[1]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var res: List<UserActionModel>? = null - val job = underTest.actions.onEach { res = it }.launchIn(this) - assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull() - job.cancel() + val res = collectLastValue(underTest.actions) + assertThat(res()?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull() } @Test fun `users - secondary user - no guest user record`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 3, includeGuest = true) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[1]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var res: List<UserRecord>? = null - val job = underTest.userRecords.onEach { res = it }.launchIn(this) - assertThat(res?.find { it.isGuest }).isNull() - job.cancel() + assertThat(underTest.userRecords.value.find { it.isGuest }).isNull() } @Test fun `show user switcher - full screen disabled - shows dialog switcher`() = - runBlocking(IMMEDIATE) { - var dialogRequest: ShowDialogRequestModel? = null + testScope.runTest { val expandable = mock<Expandable>() underTest.showUserSwitcher(context, expandable) - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + val dialogRequest = collectLastValue(underTest.dialogShowRequests) // Dialog is shown. - assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog) + assertThat(dialogRequest()) + .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable)) underTest.onDialogShown() - assertThat(dialogRequest).isNull() - - job.cancel() + assertThat(dialogRequest()).isNull() } @Test @@ -849,8 +815,8 @@ class UserInteractorTest : SysuiTestCase() { @Test fun `users - secondary user - managed profile is not included`() = - runBlocking(IMMEDIATE) { - var userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList() + testScope.runTest { + val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList() userInfos.add( UserInfo( 50, @@ -863,23 +829,19 @@ class UserInteractorTest : SysuiTestCase() { userRepository.setSelectedUserInfo(userInfos[1]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var res: List<UserModel>? = null - val job = underTest.users.onEach { res = it }.launchIn(this) - assertThat(res?.size == 3).isTrue() - job.cancel() + val res = collectLastValue(underTest.users) + assertThat(res()?.size == 3).isTrue() } @Test fun `current user is not primary and user switcher is disabled`() = - runBlocking(IMMEDIATE) { + testScope.runTest { val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[1]) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false)) - var selectedUser: UserModel? = null - val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this) - assertThat(selectedUser).isNotNull() - job.cancel() + val selectedUser = collectLastValue(underTest.selectedUser) + assertThat(selectedUser()).isNotNull() } private fun assertUsers( @@ -1017,7 +979,6 @@ class UserInteractorTest : SysuiTestCase() { } companion object { - private val IMMEDIATE = Dispatchers.Main.immediate private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) private val GUEST_ICON: Drawable = mock() private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation" diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index 108fa6246e9c..9a4ca5654691 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -75,6 +76,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: StatusBarUserChipViewModel @@ -241,6 +243,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { keyguardInteractor = KeyguardInteractor( repository = keyguardRepository, + commandQueue = commandQueue, ), featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 784a26bb371b..3d4bbdb23686 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -76,6 +77,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver + @Mock private lateinit var commandQueue: CommandQueue private lateinit var underTest: UserSwitcherViewModel @@ -142,6 +144,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { keyguardInteractor = KeyguardInteractor( repository = keyguardRepository, + commandQueue = commandQueue, ), featureFlags = FakeFeatureFlags().apply { @@ -169,273 +172,295 @@ class UserSwitcherViewModelTest : SysuiTestCase() { } @Test - fun users() = testScope.runTest { - val userInfos = - listOf( - UserInfo( - /* id= */ 0, - /* name= */ "zero", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - UserInfo( - /* id= */ 1, - /* name= */ "one", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - UserInfo( - /* id= */ 2, - /* name= */ "two", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - ) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) + fun users() = + testScope.runTest { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 2, + /* name= */ "two", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + ) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) - val userViewModels = mutableListOf<List<UserViewModel>>() - val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } + val userViewModels = mutableListOf<List<UserViewModel>>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } - assertThat(userViewModels.last()).hasSize(3) - assertUserViewModel( - viewModel = userViewModels.last()[0], - viewKey = 0, - name = Text.Loaded("zero"), - isSelectionMarkerVisible = true, - ) - assertUserViewModel( - viewModel = userViewModels.last()[1], - viewKey = 1, - name = Text.Loaded("one"), - isSelectionMarkerVisible = false, - ) - assertUserViewModel( - viewModel = userViewModels.last()[2], - viewKey = 2, - name = Text.Loaded("two"), - isSelectionMarkerVisible = false, - ) - job.cancel() - } + assertThat(userViewModels.last()).hasSize(3) + assertUserViewModel( + viewModel = userViewModels.last()[0], + viewKey = 0, + name = Text.Loaded("zero"), + isSelectionMarkerVisible = true, + ) + assertUserViewModel( + viewModel = userViewModels.last()[1], + viewKey = 1, + name = Text.Loaded("one"), + isSelectionMarkerVisible = false, + ) + assertUserViewModel( + viewModel = userViewModels.last()[2], + viewKey = 2, + name = Text.Loaded("two"), + isSelectionMarkerVisible = false, + ) + job.cancel() + } @Test - fun `maximumUserColumns - few users`() = testScope.runTest { - setUsers(count = 2) - val values = mutableListOf<Int>() - val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } + fun `maximumUserColumns - few users`() = + testScope.runTest { + setUsers(count = 2) + val values = mutableListOf<Int>() + val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } - assertThat(values.last()).isEqualTo(4) + assertThat(values.last()).isEqualTo(4) - job.cancel() - } + job.cancel() + } @Test - fun `maximumUserColumns - many users`() = testScope.runTest { - setUsers(count = 5) - val values = mutableListOf<Int>() - val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } - - assertThat(values.last()).isEqualTo(3) - job.cancel() - } + fun `maximumUserColumns - many users`() = + testScope.runTest { + setUsers(count = 5) + val values = mutableListOf<Int>() + val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } + + assertThat(values.last()).isEqualTo(3) + job.cancel() + } @Test - fun `isOpenMenuButtonVisible - has actions - true`() = testScope.runTest { - setUsers(2) + fun `isOpenMenuButtonVisible - has actions - true`() = + testScope.runTest { + setUsers(2) - val isVisible = mutableListOf<Boolean>() - val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } + val isVisible = mutableListOf<Boolean>() + val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } - assertThat(isVisible.last()).isTrue() - job.cancel() - } + assertThat(isVisible.last()).isTrue() + job.cancel() + } @Test - fun `isOpenMenuButtonVisible - no actions - false`() = testScope.runTest { - val userInfos = setUsers(2) - userRepository.setSelectedUserInfo(userInfos[1]) - keyguardRepository.setKeyguardShowing(true) - whenever(manager.canAddMoreUsers(any())).thenReturn(false) - - val isVisible = mutableListOf<Boolean>() - val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } - - assertThat(isVisible.last()).isFalse() - job.cancel() - } + fun `isOpenMenuButtonVisible - no actions - false`() = + testScope.runTest { + val userInfos = setUsers(2) + userRepository.setSelectedUserInfo(userInfos[1]) + keyguardRepository.setKeyguardShowing(true) + whenever(manager.canAddMoreUsers(any())).thenReturn(false) + + val isVisible = mutableListOf<Boolean>() + val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } + + assertThat(isVisible.last()).isFalse() + job.cancel() + } @Test - fun menu() = testScope.runTest { - val isMenuVisible = mutableListOf<Boolean>() - val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) } - assertThat(isMenuVisible.last()).isFalse() + fun menu() = + testScope.runTest { + val isMenuVisible = mutableListOf<Boolean>() + val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) } + assertThat(isMenuVisible.last()).isFalse() - underTest.onOpenMenuButtonClicked() - assertThat(isMenuVisible.last()).isTrue() + underTest.onOpenMenuButtonClicked() + assertThat(isMenuVisible.last()).isTrue() - underTest.onMenuClosed() - assertThat(isMenuVisible.last()).isFalse() + underTest.onMenuClosed() + assertThat(isMenuVisible.last()).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun `menu actions`() = testScope.runTest { - setUsers(2) - val actions = mutableListOf<List<UserActionViewModel>>() - val job = launch(testDispatcher) { underTest.menu.toList(actions) } - - assertThat(actions.last().map { it.viewKey }) - .isEqualTo( - listOf( - UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(), - UserActionModel.ADD_USER.ordinal.toLong(), - UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(), - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(), + fun `menu actions`() = + testScope.runTest { + setUsers(2) + val actions = mutableListOf<List<UserActionViewModel>>() + val job = launch(testDispatcher) { underTest.menu.toList(actions) } + + assertThat(actions.last().map { it.viewKey }) + .isEqualTo( + listOf( + UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(), + UserActionModel.ADD_USER.ordinal.toLong(), + UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(), + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(), + ) ) - ) - job.cancel() - } + job.cancel() + } @Test - fun `isFinishRequested - finishes when user is switched`() = testScope.runTest { - val userInfos = setUsers(count = 2) - val isFinishRequested = mutableListOf<Boolean>() - val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } - assertThat(isFinishRequested.last()).isFalse() + fun `isFinishRequested - finishes when user is switched`() = + testScope.runTest { + val userInfos = setUsers(count = 2) + val isFinishRequested = mutableListOf<Boolean>() + val job = + launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() - userRepository.setSelectedUserInfo(userInfos[1]) + userRepository.setSelectedUserInfo(userInfos[1]) - assertThat(isFinishRequested.last()).isTrue() + assertThat(isFinishRequested.last()).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun `isFinishRequested - finishes when the screen turns off`() = testScope.runTest { - setUsers(count = 2) - powerRepository.setInteractive(true) - val isFinishRequested = mutableListOf<Boolean>() - val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } - assertThat(isFinishRequested.last()).isFalse() + fun `isFinishRequested - finishes when the screen turns off`() = + testScope.runTest { + setUsers(count = 2) + powerRepository.setInteractive(true) + val isFinishRequested = mutableListOf<Boolean>() + val job = + launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() - powerRepository.setInteractive(false) + powerRepository.setInteractive(false) - assertThat(isFinishRequested.last()).isTrue() + assertThat(isFinishRequested.last()).isTrue() - job.cancel() - } + job.cancel() + } @Test - fun `isFinishRequested - finishes when cancel button is clicked`() = testScope.runTest { - setUsers(count = 2) - powerRepository.setInteractive(true) - val isFinishRequested = mutableListOf<Boolean>() - val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } - assertThat(isFinishRequested.last()).isFalse() + fun `isFinishRequested - finishes when cancel button is clicked`() = + testScope.runTest { + setUsers(count = 2) + powerRepository.setInteractive(true) + val isFinishRequested = mutableListOf<Boolean>() + val job = + launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() - underTest.onCancelButtonClicked() + underTest.onCancelButtonClicked() - assertThat(isFinishRequested.last()).isTrue() + assertThat(isFinishRequested.last()).isTrue() - underTest.onFinished() + underTest.onFinished() - assertThat(isFinishRequested.last()).isFalse() + assertThat(isFinishRequested.last()).isFalse() - job.cancel() - } + job.cancel() + } @Test - fun `guest selected -- name is exit guest`() = testScope.runTest { - val userInfos = + fun `guest selected -- name is exit guest`() = + testScope.runTest { + val userInfos = listOf( - UserInfo( - /* id= */ 0, - /* name= */ "zero", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - UserInfo( - /* id= */ 1, - /* name= */ "one", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_GUEST, - ), + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_GUEST, + ), ) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[1]) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[1]) - val userViewModels = mutableListOf<List<UserViewModel>>() - val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } + val userViewModels = mutableListOf<List<UserViewModel>>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } - assertThat(userViewModels.last()).hasSize(2) - assertUserViewModel( + assertThat(userViewModels.last()).hasSize(2) + assertUserViewModel( viewModel = userViewModels.last()[0], viewKey = 0, name = Text.Loaded("zero"), isSelectionMarkerVisible = false, - ) - assertUserViewModel( + ) + assertUserViewModel( viewModel = userViewModels.last()[1], viewKey = 1, - name = Text.Resource( - com.android.settingslib.R.string.guest_exit_quick_settings_button - ), + name = + Text.Resource( + com.android.settingslib.R.string.guest_exit_quick_settings_button + ), isSelectionMarkerVisible = true, - ) - job.cancel() - } + ) + job.cancel() + } @Test - fun `guest not selected -- name is guest`() = testScope.runTest { - val userInfos = + fun `guest not selected -- name is guest`() = + testScope.runTest { + val userInfos = listOf( - UserInfo( - /* id= */ 0, - /* name= */ "zero", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_SYSTEM, - ), - UserInfo( - /* id= */ 1, - /* name= */ "one", - /* iconPath= */ "", - /* flags= */ UserInfo.FLAG_FULL, - UserManager.USER_TYPE_FULL_GUEST, - ), + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_GUEST, + ), ) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - runCurrent() + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + runCurrent() - val userViewModels = mutableListOf<List<UserViewModel>>() - val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } + val userViewModels = mutableListOf<List<UserViewModel>>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } - assertThat(userViewModels.last()).hasSize(2) - assertUserViewModel( + assertThat(userViewModels.last()).hasSize(2) + assertUserViewModel( viewModel = userViewModels.last()[0], viewKey = 0, name = Text.Loaded("zero"), isSelectionMarkerVisible = true, - ) - assertUserViewModel( + ) + assertUserViewModel( viewModel = userViewModels.last()[1], viewKey = 1, name = Text.Loaded("one"), isSelectionMarkerVisible = false, - ) - job.cancel() - } + ) + job.cancel() + } private suspend fun setUsers(count: Int): List<UserInfo> { val userInfos = diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c3c6975af870..d419095921b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -16,6 +16,7 @@ package com.android.systemui.volume; +import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; import static junit.framework.Assert.assertEquals; @@ -342,6 +343,15 @@ public class VolumeDialogImplTest extends SysuiTestCase { assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute); } + @Test + public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() { + mDialog.dismissH(DISMISS_REASON_UNKNOWN); + // notifyVisible(false) should not be called immediately but only after the dismiss + // animation has ended. + verify(mVolumeDialogController, times(0)).notifyVisible(false); + mDialog.getDialogView().animate().cancel(); + } + /* @Test public void testContentDescriptions() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 388c51f04e13..dec80807ec87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -24,6 +24,7 @@ import static android.service.notification.NotificationListenerService.REASON_AP import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE; import static com.google.common.truth.Truth.assertThat; @@ -228,6 +229,8 @@ public class BubblesTest extends SysuiTestCase { private BubbleEntry mBubbleEntryUser11; private BubbleEntry mBubbleEntry2User11; + private Intent mAppBubbleIntent; + @Mock private ShellInit mShellInit; @Mock @@ -323,6 +326,9 @@ public class BubblesTest extends SysuiTestCase { mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry( mNotificationTestHelper.createBubble(handle)); + mAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class); + mAppBubbleIntent.setPackage(mContext.getPackageName()); + mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); @@ -1630,6 +1636,62 @@ public class BubblesTest extends SysuiTestCase { any(Bubble.class), anyBoolean(), anyBoolean()); } + @Test + public void testShowOrHideAppBubble_addsAndExpand() { + assertThat(mBubbleController.isStackExpanded()).isFalse(); + assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull(); + + mBubbleController.showOrHideAppBubble(mAppBubbleIntent); + + verify(mBubbleController).inflateAndAdd(any(Bubble.class), /* suppressFlyout= */ eq(true), + /* showInShade= */ eq(false)); + assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); + assertThat(mBubbleController.isStackExpanded()).isTrue(); + } + + @Test + public void testShowOrHideAppBubble_expandIfCollapsed() { + mBubbleController.showOrHideAppBubble(mAppBubbleIntent); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.collapseStack(); + assertThat(mBubbleController.isStackExpanded()).isFalse(); + + // Calling this while collapsed will expand the app bubble + mBubbleController.showOrHideAppBubble(mAppBubbleIntent); + + assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); + assertThat(mBubbleController.isStackExpanded()).isTrue(); + assertThat(mBubbleData.getBubbles().size()).isEqualTo(2); + } + + @Test + public void testShowOrHideAppBubble_collapseIfSelected() { + mBubbleController.showOrHideAppBubble(mAppBubbleIntent); + assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); + assertThat(mBubbleController.isStackExpanded()).isTrue(); + + // Calling this while the app bubble is expanded should collapse the stack + mBubbleController.showOrHideAppBubble(mAppBubbleIntent); + + assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); + assertThat(mBubbleController.isStackExpanded()).isFalse(); + assertThat(mBubbleData.getBubbles().size()).isEqualTo(1); + } + + @Test + public void testShowOrHideAppBubble_selectIfNotSelected() { + mBubbleController.showOrHideAppBubble(mAppBubbleIntent); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.expandStackAndSelectBubble(mBubbleEntry); + assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.isStackExpanded()).isTrue(); + + mBubbleController.showOrHideAppBubble(mAppBubbleIntent); + assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); + assertThat(mBubbleController.isStackExpanded()).isTrue(); + assertThat(mBubbleData.getBubbles().size()).isEqualTo(2); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java index 3767fbe98dc1..342855357fd2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java @@ -40,24 +40,49 @@ import java.io.IOException; public class MemoryTrackingTestCase extends SysuiTestCase { private static File sFilesDir = null; private static String sLatestTestClassName = null; + private static int sHeapCount = 0; + private static File sLatestBaselineHeapFile = null; - @Before public void grabFilesDir() { + // Ideally, we would do this in @BeforeClass just once, but we need mContext to get the files + // dir, and that does not exist until @Before on each test method. + @Before + public void grabFilesDir() throws IOException { + // This should happen only once per suite if (sFilesDir == null) { sFilesDir = mContext.getFilesDir(); } - sLatestTestClassName = getClass().getName(); + + // This will happen before the first test method in each class + if (sLatestTestClassName == null) { + sLatestTestClassName = getClass().getName(); + sLatestBaselineHeapFile = dump("baseline" + (++sHeapCount), "before-test"); + } } @AfterClass public static void dumpHeap() throws IOException { + File afterTestHeap = dump(sLatestTestClassName, "after-test"); + if (sLatestBaselineHeapFile != null && afterTestHeap != null) { + Log.w("MEMORY", "To compare heap to baseline (use go/ahat):"); + Log.w("MEMORY", " adb pull " + sLatestBaselineHeapFile); + Log.w("MEMORY", " adb pull " + afterTestHeap); + Log.w("MEMORY", + " java -jar ahat.jar --baseline " + sLatestBaselineHeapFile.getName() + " " + + afterTestHeap.getName()); + } + sLatestTestClassName = null; + } + + private static File dump(String basename, String heapKind) throws IOException { if (sFilesDir == null) { Log.e("MEMORY", "Somehow no test cases??"); - return; + return null; } mockitoTearDown(); - Log.w("MEMORY", "about to dump heap"); - File path = new File(sFilesDir, sLatestTestClassName + ".ahprof"); + Log.w("MEMORY", "about to dump " + heapKind + " heap"); + File path = new File(sFilesDir, basename + ".ahprof"); Debug.dumpHprofData(path.getPath()); - Log.w("MEMORY", "did it! Location: " + path); + Log.w("MEMORY", "Success! Location: " + path); + return path; } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt new file mode 100644 index 000000000000..f3e52de0d7a0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeBiometricRepository : BiometricRepository { + + private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false) + override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow() + + private val _isStrongBiometricAllowed = MutableStateFlow(false) + override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow() + + private val _isFingerprintEnabledByDevicePolicy = MutableStateFlow(false) + override val isFingerprintEnabledByDevicePolicy = + _isFingerprintEnabledByDevicePolicy.asStateFlow() + + fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) { + _isFingerprintEnrolled.value = isFingerprintEnrolled + } + + fun setStrongBiometricAllowed(isStrongBiometricAllowed: Boolean) { + _isStrongBiometricAllowed.value = isStrongBiometricAllowed + } + + fun setFingerprintEnabledByDevicePolicy(isFingerprintEnabledByDevicePolicy: Boolean) { + _isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 39d2ecaef51a..15b473640de7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -52,6 +52,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _isDozing = MutableStateFlow(false) override val isDozing: Flow<Boolean> = _isDozing + private val _isAodAvailable = MutableStateFlow(false) + override val isAodAvailable: Flow<Boolean> = _isAodAvailable + private val _isDreaming = MutableStateFlow(false) override val isDreaming: Flow<Boolean> = _isDreaming @@ -126,6 +129,10 @@ class FakeKeyguardRepository : KeyguardRepository { _isDozing.value = isDozing } + fun setAodAvailable(isAodAvailable: Boolean) { + _isAodAvailable.value = isAodAvailable + } + fun setDreamingWithOverlay(isDreaming: Boolean) { _isDreamingWithOverlay.value = isDreaming } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java index 045e6f19c667..7bcad456ff6e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; + import android.annotation.NonNull; import android.app.Notification; import android.app.NotificationChannel; @@ -57,6 +59,7 @@ public class RankingBuilder { private ShortcutInfo mShortcutInfo = null; private int mRankingAdjustment = 0; private boolean mIsBubble = false; + private int mProposedImportance = IMPORTANCE_UNSPECIFIED; public RankingBuilder() { } @@ -86,6 +89,7 @@ public class RankingBuilder { mShortcutInfo = ranking.getConversationShortcutInfo(); mRankingAdjustment = ranking.getRankingAdjustment(); mIsBubble = ranking.isBubble(); + mProposedImportance = ranking.getProposedImportance(); } public Ranking build() { @@ -114,7 +118,8 @@ public class RankingBuilder { mIsConversation, mShortcutInfo, mRankingAdjustment, - mIsBubble); + mIsBubble, + mProposedImportance); return ranking; } @@ -214,6 +219,11 @@ public class RankingBuilder { return this; } + public RankingBuilder setProposedImportance(@Importance int importance) { + mProposedImportance = importance; + return this; + } + public RankingBuilder setUserSentiment(int userSentiment) { mUserSentiment = userSentiment; return this; diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 4430bb4b3292..9d91b9753691 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -579,12 +579,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } - if (onClickIntent != null) { - views.setOnClickPendingIntent(android.R.id.background, - PendingIntent.getActivity(mContext, 0, onClickIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)); - } - Icon icon = appInfo.icon != 0 ? Icon.createWithResource(appInfo.packageName, appInfo.icon) : Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); @@ -596,6 +590,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku for (int j = 0; j < widgetCount; j++) { Widget widget = provider.widgets.get(j); if (targetWidget != null && targetWidget != widget) continue; + if (onClickIntent != null) { + views.setOnClickPendingIntent(android.R.id.background, + PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent, + PendingIntent.FLAG_UPDATE_CURRENT + | PendingIntent.FLAG_IMMUTABLE)); + } if (widget.replaceWithMaskedViewsLocked(views)) { scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked()); } @@ -817,7 +817,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (host != null) { host.callbacks = null; pruneHostLocked(host); - mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), false); + mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUidsIfBound(), + false); } } } @@ -888,12 +889,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Host host = lookupHostLocked(id); if (host != null) { - try { - mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUids(), false); - } catch (NullPointerException e) { - Slog.e(TAG, "setAppWidgetHidden(): Getting host uids: " + host.toString(), e); - throw e; - } + mAppOpsManagerInternal.updateAppWidgetVisibility(host.getWidgetUidsIfBound(), + false); } } } @@ -4345,14 +4342,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku PendingHostUpdate.appWidgetRemoved(appWidgetId)); } - public SparseArray<String> getWidgetUids() { + public SparseArray<String> getWidgetUidsIfBound() { final SparseArray<String> uids = new SparseArray<>(); for (int i = widgets.size() - 1; i >= 0; i--) { final Widget widget = widgets.get(i); if (widget.provider == null) { if (DEBUG) { - Slog.e(TAG, "Widget with no provider " + widget.toString()); + Slog.d(TAG, "Widget with no provider " + widget.toString()); } + continue; } final ProviderId providerId = widget.provider.id; uids.put(providerId.uid, providerId.componentName.getPackageName()); diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 677871f6c85f..8c2c964e2d2c 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -357,6 +357,7 @@ final class SaveUi { params.width = WindowManager.LayoutParams.MATCH_PARENT; params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title); params.windowAnimations = R.style.AutofillSaveAnimation; + params.setTrustedOverlay(); show(); } diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index a1e734dead77..b3fad8c41328 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -281,6 +281,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange mConnectedBtDevices.remove(id); mNearbyBleDevices.remove(id); mReportedSelfManagedDevices.remove(id); + mSimulated.remove(id); // Do NOT call mCallback.onDeviceDisappeared()! // CompanionDeviceManagerService will know that the association is removed, and will do diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 9669c060b716..c36e0700c723 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3420,6 +3420,11 @@ public final class ActiveServices { throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + className + " is not an isolatedProcess"); } + if (!mAm.getPackageManagerInternal().isSameApp(callingPackage, callingUid, + userId)) { + throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + + "calling package not owned by calling UID "); + } // Run the service under the calling package's application. ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo( callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 736914ace215..278c98f45c44 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -82,6 +82,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private final @NonNull AudioService mAudioService; private final @NonNull Context mContext; + private final @NonNull AudioSystemAdapter mAudioSystem; /** ID for Communication strategy retrieved form audio policy manager */ private int mCommunicationStrategyId = -1; @@ -156,12 +157,14 @@ import java.util.concurrent.atomic.AtomicBoolean; public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L; //------------------------------------------------------------------- - /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) { + /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service, + @NonNull AudioSystemAdapter audioSystem) { mContext = context; mAudioService = service; mBtHelper = new BtHelper(this); mDeviceInventory = new AudioDeviceInventory(this); mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext); + mAudioSystem = audioSystem; init(); } @@ -170,12 +173,14 @@ import java.util.concurrent.atomic.AtomicBoolean; * in system_server */ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service, @NonNull AudioDeviceInventory mockDeviceInventory, - @NonNull SystemServerAdapter mockSystemServer) { + @NonNull SystemServerAdapter mockSystemServer, + @NonNull AudioSystemAdapter audioSystem) { mContext = context; mAudioService = service; mBtHelper = new BtHelper(this); mDeviceInventory = mockDeviceInventory; mSystemServer = mockSystemServer; + mAudioSystem = audioSystem; init(); } @@ -450,7 +455,7 @@ import java.util.concurrent.atomic.AtomicBoolean; AudioAttributes attr = AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType( AudioSystem.STREAM_VOICE_CALL); - List<AudioDeviceAttributes> devices = AudioSystem.getDevicesForAttributes( + List<AudioDeviceAttributes> devices = mAudioSystem.getDevicesForAttributes( attr, false /* forVolume */); if (devices.isEmpty()) { if (mAudioService.isPlatformVoice()) { @@ -1225,7 +1230,7 @@ import java.util.concurrent.atomic.AtomicBoolean; Log.v(TAG, "onSetForceUse(useCase<" + useCase + ">, config<" + config + ">, fromA2dp<" + fromA2dp + ">, eventSource<" + eventSource + ">)"); } - AudioSystem.setForceUse(useCase, config); + mAudioSystem.setForceUse(useCase, config); } private void onSendBecomingNoisyIntent() { @@ -1863,9 +1868,9 @@ import java.util.concurrent.atomic.AtomicBoolean; if (preferredCommunicationDevice == null || preferredCommunicationDevice.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) { - AudioSystem.setParameters("BT_SCO=off"); + mAudioSystem.setParameters("BT_SCO=off"); } else { - AudioSystem.setParameters("BT_SCO=on"); + mAudioSystem.setParameters("BT_SCO=on"); } if (preferredCommunicationDevice == null) { AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice(); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c804ef2cf8b4..1bd8f1ea1c18 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -183,6 +183,7 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent; import com.android.server.audio.AudioServiceEvents.PhoneStateEvent; +import com.android.server.audio.AudioServiceEvents.VolChangedBroadcastEvent; import com.android.server.audio.AudioServiceEvents.VolumeEvent; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerInternal.UserRestrictionsListener; @@ -1205,7 +1206,7 @@ public class AudioService extends IAudioService.Stub mUseFixedVolume = mContext.getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); - mDeviceBroker = new AudioDeviceBroker(mContext, this); + mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem); mRecordMonitor = new RecordingActivityMonitor(mContext); mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true); @@ -1637,7 +1638,7 @@ public class AudioService extends IAudioService.Stub synchronized (mSettingsLock) { final int forDock = mDockAudioMediaEnabled ? - AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE; + AudioSystem.FORCE_DIGITAL_DOCK : AudioSystem.FORCE_NONE; mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied"); sendEncodedSurroundMode(mContentResolver, "onAudioServerDied"); sendEnabledSurroundFormats(mContentResolver, true); @@ -2258,9 +2259,10 @@ public class AudioService extends IAudioService.Stub SENDMSG_QUEUE, AudioSystem.FOR_DOCK, mDockAudioMediaEnabled ? - AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE, + AudioSystem.FORCE_DIGITAL_DOCK : AudioSystem.FORCE_NONE, new String("readDockAudioSettings"), 0); + } @@ -3601,9 +3603,11 @@ public class AudioService extends IAudioService.Stub setRingerMode(getNewRingerMode(stream, index, flags), TAG + ".onSetStreamVolume", false /*external*/); } - // setting non-zero volume for a muted stream unmutes the stream and vice versa, + // setting non-zero volume for a muted stream unmutes the stream and vice versa + // (only when changing volume for the current device), // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements - if (streamType != AudioSystem.STREAM_BLUETOOTH_SCO) { + if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) + && (getDeviceForStream(stream) == device)) { mStreamStates[stream].mute(index == 0); } } @@ -3741,19 +3745,30 @@ public class AudioService extends IAudioService.Stub Objects.requireNonNull(ada); Objects.requireNonNull(callingPackage); - AudioService.sVolumeLogger.loglogi("setDeviceVolume" + " from:" + callingPackage + " " - + vi + " " + ada, TAG); - if (!vi.hasStreamType()) { Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception()); return; } + int index = vi.getVolumeIndex(); if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) { throw new IllegalArgumentException( "changing device volume requires a volume index or mute command"); } + // force a cache clear to force reevaluating stream type to audio device selection + // that can interfere with the sending of the VOLUME_CHANGED_ACTION intent + // TODO change cache management to not rely only on invalidation, but on "do not trust" + // moments when routing is in flux. + mAudioSystem.clearRoutingCache(); + + // log the current device that will be used when evaluating the sending of the + // VOLUME_CHANGED_ACTION intent to see if the current device is the one being modified + final int currDev = getDeviceForStream(vi.getStreamType()); + + AudioService.sVolumeLogger.log(new DeviceVolumeEvent(vi.getStreamType(), index, ada, + currDev, callingPackage)); + // TODO handle unmuting of current audio device // if a stream is not muted but the VolumeInfo is for muting, set the volume index // for the device to min volume @@ -3837,11 +3852,11 @@ public class AudioService extends IAudioService.Stub return; } - final AudioEventLogger.Event event = (device == null) - ? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, - index/*val1*/, flags/*val2*/, callingPackage) - : new DeviceVolumeEvent(streamType, index, device, callingPackage); - sVolumeLogger.log(event); + if (device == null) { + // call was already logged in setDeviceVolume() + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, + index/*val1*/, flags/*val2*/, callingPackage)); + } setStreamVolume(streamType, index, flags, device, callingPackage, callingPackage, attributionTag, Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission()); @@ -4242,7 +4257,11 @@ public class AudioService extends IAudioService.Stub maybeSendSystemAudioStatusCommand(false); } } - sendVolumeUpdate(streamType, oldIndex, index, flags, device); + if (ada == null) { + // only non-null when coming here from setDeviceVolume + // TODO change test to check early if device is current device or not + sendVolumeUpdate(streamType, oldIndex, index, flags, device); + } } @@ -7982,6 +8001,8 @@ public class AudioService extends IAudioService.Stub mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS, mStreamVolumeAlias[mStreamType]); + AudioService.sVolumeLogger.log(new VolChangedBroadcastEvent( + mStreamType, mStreamVolumeAlias[mStreamType], index)); sendBroadcastToAll(mVolumeChanged); } } @@ -10155,7 +10176,7 @@ public class AudioService extends IAudioService.Stub static final int LOG_NB_EVENTS_PHONE_STATE = 20; static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 50; static final int LOG_NB_EVENTS_FORCE_USE = 20; - static final int LOG_NB_EVENTS_VOLUME = 40; + static final int LOG_NB_EVENTS_VOLUME = 100; static final int LOG_NB_EVENTS_DYN_POLICY = 10; static final int LOG_NB_EVENTS_SPATIAL = 30; diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 30a9e0a70e96..c2c3f028abdb 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -147,19 +147,42 @@ public class AudioServiceEvents { } } + static final class VolChangedBroadcastEvent extends AudioEventLogger.Event { + final int mStreamType; + final int mAliasStreamType; + final int mIndex; + + VolChangedBroadcastEvent(int stream, int alias, int index) { + mStreamType = stream; + mAliasStreamType = alias; + mIndex = index; + } + + @Override + public String eventToString() { + return new StringBuilder("sending VOLUME_CHANGED stream:") + .append(AudioSystem.streamToString(mStreamType)) + .append(" index:").append(mIndex) + .append(" alias:").append(AudioSystem.streamToString(mAliasStreamType)) + .toString(); + } + } + static final class DeviceVolumeEvent extends AudioEventLogger.Event { final int mStream; final int mVolIndex; final String mDeviceNativeType; final String mDeviceAddress; final String mCaller; + final int mDeviceForStream; DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device, - String callingPackage) { + int deviceForStream, String callingPackage) { mStream = streamType; mVolIndex = index; mDeviceNativeType = "0x" + Integer.toHexString(device.getInternalType()); mDeviceAddress = device.getAddress(); + mDeviceForStream = deviceForStream; mCaller = callingPackage; // log metrics new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT) @@ -180,7 +203,9 @@ public class AudioServiceEvents { .append(" index:").append(mVolIndex) .append(" device:").append(mDeviceNativeType) .append(" addr:").append(mDeviceAddress) - .append(") from ").append(mCaller).toString(); + .append(") from ").append(mCaller) + .append(" currDevForStream:Ox").append(Integer.toHexString(mDeviceForStream)) + .toString(); } } diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index c3754eb3e44c..258837116cd6 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -105,6 +105,13 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, } } + public void clearRoutingCache() { + if (DEBUG_CACHE) { + Log.d(TAG, "---- routing cache clear (from java) ----------"); + } + invalidateRoutingCache(); + } + /** * Implementation of AudioSystem.VolumeRangeInitRequestCallback */ @@ -337,6 +344,7 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, * @return */ public int setParameters(String keyValuePairs) { + invalidateRoutingCache(); return AudioSystem.setParameters(keyValuePairs); } diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 54be4bbd8f82..186294228d11 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -58,13 +58,15 @@ import java.util.function.Consumer; public final class PlaybackActivityMonitor implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer { - public static final String TAG = "AudioService.PlaybackActivityMonitor"; + public static final String TAG = "AS.PlayActivityMonitor"; /*package*/ static final boolean DEBUG = false; /*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1; /*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2; /*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3; + /*package*/ static final int VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID = 4; + // ducking settings for a "normal duck" at -14dB private static final VolumeShaper.Configuration DUCK_VSHAPE = new VolumeShaper.Configuration.Builder() .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID) @@ -78,6 +80,22 @@ public final class PlaybackActivityMonitor .build(); private static final VolumeShaper.Configuration DUCK_ID = new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_DUCK_ID); + + // ducking settings for a "strong duck" at -35dB (attenuation factor of 0.017783) + private static final VolumeShaper.Configuration STRONG_DUCK_VSHAPE = + new VolumeShaper.Configuration.Builder() + .setId(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID) + .setCurve(new float[] { 0.f, 1.f } /* times */, + new float[] { 1.f, 0.017783f } /* volumes */) + .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME) + .setDuration(MediaFocusControl.getFocusRampTimeMs( + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build())) + .build(); + private static final VolumeShaper.Configuration STRONG_DUCK_ID = + new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID); + private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED = new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY) .createIfNeeded() @@ -659,11 +677,23 @@ public final class PlaybackActivityMonitor // add the players eligible for ducking to the list, and duck them // (if apcsToDuck is empty, this will at least mark this uid as ducked, so when // players of the same uid start, they will be ducked by DuckingManager.checkDuck()) - mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck); + mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck, reqCausesStrongDuck(winner)); } return true; } + private boolean reqCausesStrongDuck(FocusRequester requester) { + if (requester.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) { + return false; + } + final int reqUsage = requester.getAudioAttributes().getUsage(); + if ((reqUsage == AudioAttributes.USAGE_ASSISTANT) + || (reqUsage == AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)) { + return true; + } + return false; + } + @Override public void restoreVShapedPlayers(@NonNull FocusRequester winner) { if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); } @@ -939,10 +969,11 @@ public final class PlaybackActivityMonitor private static final class DuckingManager { private final HashMap<Integer, DuckedApp> mDuckers = new HashMap<Integer, DuckedApp>(); - synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck) { + synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck, + boolean requestCausesStrongDuck) { if (DEBUG) { Log.v(TAG, "DuckingManager: duckUid() uid:"+ uid); } if (!mDuckers.containsKey(uid)) { - mDuckers.put(uid, new DuckedApp(uid)); + mDuckers.put(uid, new DuckedApp(uid, requestCausesStrongDuck)); } final DuckedApp da = mDuckers.get(uid); for (AudioPlaybackConfiguration apc : apcsToDuck) { @@ -989,10 +1020,13 @@ public final class PlaybackActivityMonitor private static final class DuckedApp { private final int mUid; + /** determines whether ducking is done with DUCK_VSHAPE or STRONG_DUCK_VSHAPE */ + private final boolean mUseStrongDuck; private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>(); - DuckedApp(int uid) { + DuckedApp(int uid, boolean useStrongDuck) { mUid = uid; + mUseStrongDuck = useStrongDuck; } void dump(PrintWriter pw) { @@ -1013,9 +1047,9 @@ public final class PlaybackActivityMonitor return; } try { - sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG)); + sEventLogger.log((new DuckEvent(apc, skipRamp, mUseStrongDuck)).printLog(TAG)); apc.getPlayerProxy().applyVolumeShaper( - DUCK_VSHAPE, + mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE, skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED); mDuckedPlayers.add(piid); } catch (Exception e) { @@ -1031,7 +1065,7 @@ public final class PlaybackActivityMonitor sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:" + piid)).printLog(TAG)); apc.getPlayerProxy().applyVolumeShaper( - DUCK_ID, + mUseStrongDuck ? STRONG_DUCK_ID : DUCK_ID, VolumeShaper.Operation.REVERSE); } catch (Exception e) { Log.e(TAG, "Error unducking player piid:" + piid + " uid:" + mUid, e); @@ -1146,13 +1180,17 @@ public final class PlaybackActivityMonitor } static final class DuckEvent extends VolumeShaperEvent { + final boolean mUseStrongDuck; + @Override String getVSAction() { - return "ducking"; + return mUseStrongDuck ? "ducking (strong)" : "ducking"; } - DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) { + DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck) + { super(apc, skipRamp); + mUseStrongDuck = useStrongDuck; } } diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java index 57ea812dbb3a..1924f3c92956 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java @@ -96,7 +96,11 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { Slog.d(TAG, "Remove onClientFinished: " + clientMonitor + ", success: " + success); - mCallback.onClientFinished(InternalCleanupClient.this, success); + if (mUnknownHALTemplates.isEmpty()) { + mCallback.onClientFinished(InternalCleanupClient.this, success); + } else { + startCleanupUnknownHalTemplates(); + } } }; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 05e83da6a107..787bfb00a554 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -16,15 +16,11 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskStackListener; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; import android.hardware.biometrics.common.ICancellationSignal; @@ -92,7 +88,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> private long mSideFpsLastAcquireStartTime; private Runnable mAuthSuccessRunnable; private final Clock mClock; - private boolean mDidFinishSfps; FingerprintAuthenticationClient( @NonNull Context context, @@ -198,9 +193,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @Override protected void handleLifecycleAfterAuth(boolean authenticated) { - if (authenticated && !mDidFinishSfps) { + if (authenticated) { mCallback.onClientFinished(this, true /* success */); - mDidFinishSfps = true; } } @@ -210,13 +204,11 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> return false; } - public void handleAuthenticate( + @Override + public void onAuthenticated( BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> token) { - if (authenticated && mSensorProps.isAnySidefpsType()) { - Slog.i(TAG, "(sideFPS): No power press detected, sending auth"); - } super.onAuthenticated(identifier, authenticated, token); if (authenticated) { mState = STATE_STOPPED; @@ -227,72 +219,11 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> } @Override - public void onAuthenticated( - BiometricAuthenticator.Identifier identifier, - boolean authenticated, - ArrayList<Byte> token) { - - mHandler.post( - () -> { - long delay = 0; - if (authenticated && mSensorProps.isAnySidefpsType()) { - delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp; - - if (mSideFpsLastAcquireStartTime != -1) { - delay = Math.max(0, - delay - (mClock.millis() - mSideFpsLastAcquireStartTime)); - } - - Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps " - + "waiting for power until: " + delay + "ms"); - } - - if (mHandler.hasMessages(MESSAGE_FINGER_UP)) { - Slog.i(TAG, "Finger up detected, sending auth"); - delay = 0; - } - - mAuthSuccessRunnable = - () -> handleAuthenticate(identifier, authenticated, token); - mHandler.postDelayed( - mAuthSuccessRunnable, - MESSAGE_AUTH_SUCCESS, - delay); - }); - } - - @Override public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) { // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off // for most ACQUIRED messages. See BiometricFingerprintConstants#FingerprintAcquired mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo)); super.onAcquired(acquiredInfo, vendorCode); - if (mSensorProps.isAnySidefpsType()) { - if (acquiredInfo == FINGERPRINT_ACQUIRED_START) { - mSideFpsLastAcquireStartTime = mClock.millis(); - } - final boolean shouldLookForVendor = - mSkipWaitForPowerAcquireMessage == FINGERPRINT_ACQUIRED_VENDOR; - final boolean acquireMessageMatch = acquiredInfo == mSkipWaitForPowerAcquireMessage; - final boolean vendorMessageMatch = vendorCode == mSkipWaitForPowerVendorAcquireMessage; - final boolean ignorePowerPress = - acquireMessageMatch && (!shouldLookForVendor || vendorMessageMatch); - - if (ignorePowerPress) { - Slog.d(TAG, "(sideFPS) onFingerUp"); - mHandler.post(() -> { - if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) { - Slog.d(TAG, "(sideFPS) skipping wait for power"); - mHandler.removeMessages(MESSAGE_AUTH_SUCCESS); - mHandler.post(mAuthSuccessRunnable); - } else { - mHandler.postDelayed(() -> { - }, MESSAGE_FINGER_UP, mFingerUpIgnoresPower); - } - }); - } - } - } @Override @@ -488,22 +419,5 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> } @Override - public void onPowerPressed() { - if (mSensorProps.isAnySidefpsType()) { - Slog.i(TAG, "(sideFPS): onPowerPressed"); - mHandler.post(() -> { - if (mDidFinishSfps) { - return; - } - Slog.i(TAG, "(sideFPS): finishing auth"); - // Ignore auths after a power has been detected - mHandler.removeMessages(MESSAGE_AUTH_SUCCESS); - // Do not call onError() as that will send an additional callback to coex. - mDidFinishSfps = true; - onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true); - stopHalOperation(); - mSensorOverlays.hide(getSensorId()); - }); - } - } + public void onPowerPressed() { } } diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index 9dd2f8408c56..b9ca57e70b3e 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -76,6 +76,10 @@ class DeviceStateToLayoutMap { return layout; } + int size() { + return mLayoutMap.size(); + } + private Layout createLayout(int state) { if (mLayoutMap.contains(state)) { Slog.e(TAG, "Attempted to create a second layout for state " + state); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 927874373df8..2b7fbfb71901 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -402,6 +402,8 @@ public class DisplayDeviceConfig { private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d"; private static final String NO_SUFFIX_FORMAT = "%d"; private static final long STABLE_FLAG = 1L << 62; + private static final int DEFAULT_PEAK_REFRESH_RATE = 0; + private static final int DEFAULT_REFRESH_RATE = 60; private static final int DEFAULT_LOW_REFRESH_RATE = 60; private static final int DEFAULT_HIGH_REFRESH_RATE = 0; private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{}; @@ -570,17 +572,29 @@ public class DisplayDeviceConfig { * using higher refresh rates, even if display modes with higher refresh rates are available * from hardware composer. Only has an effect if the value is non-zero. */ - private int mDefaultHighRefreshRate = DEFAULT_HIGH_REFRESH_RATE; + private int mDefaultPeakRefreshRate = DEFAULT_PEAK_REFRESH_RATE; /** * The default refresh rate for a given device. This value sets the higher default * refresh rate. If the hardware composer on the device supports display modes with * a higher refresh rate than the default value specified here, the framework may use those * higher refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling - * setFrameRate(). We have historically allowed fallback to mDefaultHighRefreshRate if - * mDefaultLowRefreshRate is set to 0, but this is not supported anymore. + * setFrameRate(). We have historically allowed fallback to mDefaultPeakRefreshRate if + * mDefaultRefreshRate is set to 0, but this is not supported anymore. */ - private int mDefaultLowRefreshRate = DEFAULT_LOW_REFRESH_RATE; + private int mDefaultRefreshRate = DEFAULT_REFRESH_RATE; + + /** + * Default refresh rate in the high zone defined by brightness and ambient thresholds. + * If non-positive, then the refresh rate is unchanged even if thresholds are configured. + */ + private int mDefaultHighBlockingZoneRefreshRate = DEFAULT_HIGH_REFRESH_RATE; + + /** + * Default refresh rate in the zone defined by brightness and ambient thresholds. + * If non-positive, then the refresh rate is unchanged even if thresholds are configured. + */ + private int mDefaultLowBlockingZoneRefreshRate = DEFAULT_LOW_REFRESH_RATE; /** * The display uses different gamma curves for different refresh rates. It's hard for panel @@ -1296,15 +1310,29 @@ public class DisplayDeviceConfig { /** * @return Default peak refresh rate of the associated display */ - public int getDefaultHighRefreshRate() { - return mDefaultHighRefreshRate; + public int getDefaultPeakRefreshRate() { + return mDefaultPeakRefreshRate; } /** * @return Default refresh rate of the associated display */ - public int getDefaultLowRefreshRate() { - return mDefaultLowRefreshRate; + public int getDefaultRefreshRate() { + return mDefaultRefreshRate; + } + + /** + * @return Default refresh rate in the higher blocking zone of the associated display + */ + public int getDefaultHighBlockingZoneRefreshRate() { + return mDefaultHighBlockingZoneRefreshRate; + } + + /** + * @return Default refresh rate in the lower blocking zone of the associated display + */ + public int getDefaultLowBlockingZoneRefreshRate() { + return mDefaultLowBlockingZoneRefreshRate; } /** @@ -1442,8 +1470,10 @@ public class DisplayDeviceConfig { + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable + "\n" - + ", mDefaultRefreshRate= " + mDefaultLowRefreshRate - + ", mDefaultPeakRefreshRate= " + mDefaultHighRefreshRate + + ", mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate + + ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate + + ", mDefaultPeakRefreshRate= " + mDefaultPeakRefreshRate + + ", mDefaultRefreshRate= " + mDefaultRefreshRate + ", mLowDisplayBrightnessThresholds= " + Arrays.toString(mLowDisplayBrightnessThresholds) + ", mLowAmbientBrightnessThresholds= " @@ -1757,10 +1787,31 @@ public class DisplayDeviceConfig { BlockingZoneConfig higherBlockingZoneConfig = (refreshRateConfigs == null) ? null : refreshRateConfigs.getHigherBlockingZoneConfigs(); + loadPeakDefaultRefreshRate(refreshRateConfigs); + loadDefaultRefreshRate(refreshRateConfigs); loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig); loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig); } + private void loadPeakDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) { + if (refreshRateConfigs == null || refreshRateConfigs.getDefaultPeakRefreshRate() == null) { + mDefaultPeakRefreshRate = mContext.getResources().getInteger( + R.integer.config_defaultPeakRefreshRate); + } else { + mDefaultPeakRefreshRate = + refreshRateConfigs.getDefaultPeakRefreshRate().intValue(); + } + } + + private void loadDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) { + if (refreshRateConfigs == null || refreshRateConfigs.getDefaultRefreshRate() == null) { + mDefaultRefreshRate = mContext.getResources().getInteger( + R.integer.config_defaultRefreshRate); + } else { + mDefaultRefreshRate = + refreshRateConfigs.getDefaultRefreshRate().intValue(); + } + } /** * Loads the refresh rate configurations pertaining to the upper blocking zones. @@ -1785,10 +1836,10 @@ public class DisplayDeviceConfig { private void loadHigherBlockingZoneDefaultRefreshRate( BlockingZoneConfig upperBlockingZoneConfig) { if (upperBlockingZoneConfig == null) { - mDefaultHighRefreshRate = mContext.getResources().getInteger( - com.android.internal.R.integer.config_defaultPeakRefreshRate); + mDefaultHighBlockingZoneRefreshRate = mContext.getResources().getInteger( + com.android.internal.R.integer.config_fixedRefreshRateInHighZone); } else { - mDefaultHighRefreshRate = + mDefaultHighBlockingZoneRefreshRate = upperBlockingZoneConfig.getDefaultRefreshRate().intValue(); } } @@ -1800,10 +1851,10 @@ public class DisplayDeviceConfig { private void loadLowerBlockingZoneDefaultRefreshRate( BlockingZoneConfig lowerBlockingZoneConfig) { if (lowerBlockingZoneConfig == null) { - mDefaultLowRefreshRate = mContext.getResources().getInteger( - com.android.internal.R.integer.config_defaultRefreshRate); + mDefaultLowBlockingZoneRefreshRate = mContext.getResources().getInteger( + com.android.internal.R.integer.config_defaultRefreshRateInZone); } else { - mDefaultLowRefreshRate = + mDefaultLowBlockingZoneRefreshRate = lowerBlockingZoneConfig.getDefaultRefreshRate().intValue(); } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 8f35924128bb..5cfe65baeb9d 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -104,6 +104,7 @@ import android.os.UserManager; import android.provider.Settings; import android.sysprop.DisplayProperties; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; import android.util.IntArray; @@ -256,6 +257,13 @@ public final class DisplayManagerService extends SystemService { final SparseArray<Pair<IVirtualDevice, DisplayWindowPolicyController>> mDisplayWindowPolicyControllers = new SparseArray<>(); + /** + * Map of every internal primary display device {@link HighBrightnessModeMetadata}s indexed by + * {@link DisplayDevice#mUniqueId}. + */ + public final ArrayMap<String, HighBrightnessModeMetadata> mHighBrightnessModeMetadataMap = + new ArrayMap<>(); + // List of all currently registered display adapters. private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>(); @@ -1570,7 +1578,16 @@ public final class DisplayManagerService extends SystemService { DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { - dpc.onDisplayChanged(); + final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); + if (device == null) { + Slog.wtf(TAG, "Display Device is null in DisplayManagerService for display: " + + display.getDisplayIdLocked()); + return; + } + + final String uniqueId = device.getUniqueId(); + HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMap.get(uniqueId); + dpc.onDisplayChanged(hbmMetadata); } } @@ -1627,7 +1644,15 @@ public final class DisplayManagerService extends SystemService { final int displayId = display.getDisplayIdLocked(); final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId); if (dpc != null) { - dpc.onDisplayChanged(); + final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); + if (device == null) { + Slog.wtf(TAG, "Display Device is null in DisplayManagerService for display: " + + display.getDisplayIdLocked()); + return; + } + final String uniqueId = device.getUniqueId(); + HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMap.get(uniqueId); + dpc.onDisplayChanged(hbmMetadata); } } @@ -2611,6 +2636,31 @@ public final class DisplayManagerService extends SystemService { mLogicalDisplayMapper.forEachLocked(this::addDisplayPowerControllerLocked); } + private HighBrightnessModeMetadata getHighBrightnessModeMetadata(LogicalDisplay display) { + final DisplayDevice device = display.getPrimaryDisplayDeviceLocked(); + if (device == null) { + Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: " + + display.getDisplayIdLocked()); + return null; + } + + // HBM brightness mode is only applicable to internal physical displays. + if (display.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) { + return null; + } + + final String uniqueId = device.getUniqueId(); + + if (mHighBrightnessModeMetadataMap.containsKey(uniqueId)) { + return mHighBrightnessModeMetadataMap.get(uniqueId); + } + + // HBM Time info not present. Create a new one for this physical display. + HighBrightnessModeMetadata hbmInfo = new HighBrightnessModeMetadata(); + mHighBrightnessModeMetadataMap.put(uniqueId, hbmInfo); + return hbmInfo; + } + private void addDisplayPowerControllerLocked(LogicalDisplay display) { if (mPowerHandler == null) { // initPowerManagement has not yet been called. @@ -2622,10 +2672,18 @@ public final class DisplayManagerService extends SystemService { final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore, display, mSyncRoot); + + // If display is internal and has a HighBrightnessModeMetadata mapping, use that. + // Or create a new one and use that. + // We also need to pass a mapping of the HighBrightnessModeTimeInfoMap to + // displayPowerController, so the hbm info can be correctly associated + // with the corresponding displaydevice. + HighBrightnessModeMetadata hbmMetadata = getHighBrightnessModeMetadata(display); + final DisplayPowerController displayPowerController = new DisplayPowerController( mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, - () -> handleBrightnessChange(display)); + () -> handleBrightnessChange(display), hbmMetadata); mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); } diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index aafba5a2a1b4..fdfc20afee79 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -1169,7 +1169,7 @@ public class DisplayModeDirector { mDefaultRefreshRate = (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( R.integer.config_defaultRefreshRate) - : (float) displayDeviceConfig.getDefaultLowRefreshRate(); + : (float) displayDeviceConfig.getDefaultRefreshRate(); } public void observe() { @@ -1256,7 +1256,7 @@ public class DisplayModeDirector { defaultPeakRefreshRate = (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( R.integer.config_defaultPeakRefreshRate) - : (float) displayDeviceConfig.getDefaultHighRefreshRate(); + : (float) displayDeviceConfig.getDefaultPeakRefreshRate(); } mDefaultPeakRefreshRate = defaultPeakRefreshRate; } @@ -1612,8 +1612,26 @@ public class DisplayModeDirector { return mHighAmbientBrightnessThresholds; } + /** + * @return the refresh rate to lock to when in a high brightness zone + */ + @VisibleForTesting + int getRefreshRateInHighZone() { + return mRefreshRateInHighZone; + } + + /** + * @return the refresh rate to lock to when in a low brightness zone + */ + @VisibleForTesting + int getRefreshRateInLowZone() { + return mRefreshRateInLowZone; + } + private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) { + loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig); + loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig); mLowDisplayBrightnessThresholds = loadBrightnessThresholds( () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), @@ -1634,6 +1652,44 @@ 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) { + try { + refreshRateInLowZone = mDeviceConfig.getInt( + DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, + refreshRateInLowZone); + } catch (Exception exception) { + // Do nothing + } + } + mRefreshRateInLowZone = refreshRateInLowZone; + } + + private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig, + boolean attemptLoadingFromDeviceConfig) { + int refreshRateInHighZone = + (displayDeviceConfig == null) ? mContext.getResources().getInteger( + R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig + .getDefaultHighBlockingZoneRefreshRate(); + if (attemptLoadingFromDeviceConfig) { + try { + refreshRateInHighZone = mDeviceConfig.getInt( + DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, + refreshRateInHighZone); + } catch (Exception exception) { + // Do nothing + } + } + mRefreshRateInHighZone = refreshRateInHighZone; + } + private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) { mHighDisplayBrightnessThresholds = loadBrightnessThresholds( @@ -1687,14 +1743,6 @@ public class DisplayModeDirector { } /** - * @return the refresh to lock to when in a low brightness zone - */ - @VisibleForTesting - int getRefreshRateInLowZone() { - return mRefreshRateInLowZone; - } - - /** * @return the display brightness thresholds for the low brightness zones */ @VisibleForTesting @@ -1739,8 +1787,17 @@ public class DisplayModeDirector { mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds; } - mRefreshRateInLowZone = mDeviceConfigDisplaySettings.getRefreshRateInLowZone(); - mRefreshRateInHighZone = mDeviceConfigDisplaySettings.getRefreshRateInHighZone(); + final int refreshRateInLowZone = mDeviceConfigDisplaySettings + .getRefreshRateInLowZone(); + if (refreshRateInLowZone != -1) { + mRefreshRateInLowZone = refreshRateInLowZone; + } + + final int refreshRateInHighZone = mDeviceConfigDisplaySettings + .getRefreshRateInHighZone(); + if (refreshRateInHighZone != -1) { + mRefreshRateInHighZone = refreshRateInHighZone; + } restartObserver(); mDeviceConfigDisplaySettings.startListening(); @@ -1794,6 +1851,10 @@ public class DisplayModeDirector { restartObserver(); } + /** + * Used to reload the lower blocking zone refresh rate in case of changes in the + * DeviceConfig properties. + */ public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) { if (refreshRate != mRefreshRateInLowZone) { mRefreshRateInLowZone = refreshRate; @@ -1817,6 +1878,10 @@ public class DisplayModeDirector { restartObserver(); } + /** + * Used to reload the higher blocking zone refresh rate in case of changes in the + * DeviceConfig properties. + */ public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) { if (refreshRate != mRefreshRateInHighZone) { mRefreshRateInHighZone = refreshRate; @@ -2664,15 +2729,10 @@ public class DisplayModeDirector { } public int getRefreshRateInLowZone() { - int defaultRefreshRateInZone = mContext.getResources().getInteger( - R.integer.config_defaultRefreshRateInZone); - - int refreshRate = mDeviceConfig.getInt( + return mDeviceConfig.getInt( DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, - defaultRefreshRateInZone); + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1); - return refreshRate; } /* @@ -2694,15 +2754,10 @@ public class DisplayModeDirector { } public int getRefreshRateInHighZone() { - int defaultRefreshRateInZone = mContext.getResources().getInteger( - R.integer.config_fixedRefreshRateInHighZone); - - int refreshRate = mDeviceConfig.getInt( + return mDeviceConfig.getInt( DeviceConfig.NAMESPACE_DISPLAY_MANAGER, DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, - defaultRefreshRateInZone); - - return refreshRate; + -1); } public int getRefreshRateInHbmSunlight() { @@ -2750,23 +2805,29 @@ public class DisplayModeDirector { int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds(); int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds(); - int refreshRateInLowZone = getRefreshRateInLowZone(); + final int refreshRateInLowZone = getRefreshRateInLowZone(); mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED, new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds)) .sendToTarget(); - mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0) + + if (refreshRateInLowZone != -1) { + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone) .sendToTarget(); + } int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds(); int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds(); - int refreshRateInHighZone = getRefreshRateInHighZone(); + final int refreshRateInHighZone = getRefreshRateInHighZone(); mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED, new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds)) .sendToTarget(); - mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0) + + if (refreshRateInHighZone != -1) { + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone) .sendToTarget(); + } final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight(); mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED, diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index b43d830e7935..b431306e294d 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -391,6 +391,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private float[] mNitsRange; private final HighBrightnessModeController mHbmController; + private final HighBrightnessModeMetadata mHighBrightnessModeMetadata; private final BrightnessThrottler mBrightnessThrottler; @@ -511,7 +512,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, - Runnable onBrightnessChangeRunnable) { + Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) { mLogicalDisplay = logicalDisplay; mDisplayId = mLogicalDisplay.getDisplayIdLocked(); final String displayIdStr = "[" + mDisplayId + "]"; @@ -521,6 +522,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mSuspendBlockerIdProxPositive = displayIdStr + "prox positive"; mSuspendBlockerIdProxNegative = displayIdStr + "prox negative"; mSuspendBlockerIdProxDebounce = displayIdStr + "prox debounce"; + mHighBrightnessModeMetadata = hbmMetadata; mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); @@ -793,7 +795,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * of each display need to be properly reflected in AutomaticBrightnessController. */ @GuardedBy("DisplayManagerService.mSyncRoot") - public void onDisplayChanged() { + public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata) { final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); if (device == null) { Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: " @@ -815,9 +817,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; - loadFromDisplayDeviceConfig(token, info); + loadFromDisplayDeviceConfig(token, info, hbmMetadata); - // Since the underlying display-device changed, we really don't know the + /// Since the underlying display-device changed, we really don't know the // last command that was sent to change it's state. Lets assume it is unknown so // that we trigger a change immediately. mPowerState.resetScreenState(); @@ -872,7 +874,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) { + private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info, + HighBrightnessModeMetadata hbmMetadata) { // All properties that depend on the associated DisplayDevice and the DDC must be // updated here. loadBrightnessRampRates(); @@ -885,6 +888,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessRampIncreaseMaxTimeMillis, mBrightnessRampDecreaseMaxTimeMillis); } + mHbmController.setHighBrightnessModeMetadata(hbmMetadata); mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, mDisplayDeviceConfig.getHighBrightnessModeData(), new HighBrightnessModeController.HdrBrightnessDeviceConfig() { @@ -1965,7 +1969,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mAutomaticBrightnessController != null) { mAutomaticBrightnessController.update(); } - }, mContext); + }, mHighBrightnessModeMetadata, mContext); } private BrightnessThrottler createBrightnessThrottlerLocked() { diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index f650b118b815..2c257a17af91 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -506,6 +506,8 @@ final class DisplayPowerState { boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState); boolean changed = stateChanged || backlightChanged; if (!valid || !changed) { + mStateChangeInProgress = false; + mBacklightChangeInProgress = false; try { mLock.wait(); } catch (InterruptedException ex) { diff --git a/services/core/java/com/android/server/display/HbmEvent.java b/services/core/java/com/android/server/display/HbmEvent.java new file mode 100644 index 000000000000..5675e2f69230 --- /dev/null +++ b/services/core/java/com/android/server/display/HbmEvent.java @@ -0,0 +1,46 @@ +/* + * 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; + + +/** + * Represents an event in which High Brightness Mode was enabled. + */ +class HbmEvent { + private long mStartTimeMillis; + private long mEndTimeMillis; + + HbmEvent(long startTimeMillis, long endTimeMillis) { + this.mStartTimeMillis = startTimeMillis; + this.mEndTimeMillis = endTimeMillis; + } + + public long getStartTimeMillis() { + return mStartTimeMillis; + } + + public long getEndTimeMillis() { + return mEndTimeMillis; + } + + @Override + public String toString() { + return "HbmEvent: {startTimeMillis:" + mStartTimeMillis + ", endTimeMillis: " + + mEndTimeMillis + "}, total: " + + ((mEndTimeMillis - mStartTimeMillis) / 1000) + "]"; + } +} diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 0b9d4debd16f..ac32d53daeab 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -42,8 +42,8 @@ import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; import com.android.server.display.DisplayManagerService.Clock; import java.io.PrintWriter; +import java.util.ArrayDeque; import java.util.Iterator; -import java.util.LinkedList; /** * Controls the status of high-brightness mode for devices that support it. This class assumes that @@ -105,30 +105,24 @@ class HighBrightnessModeController { private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; /** - * If HBM is currently running, this is the start time for the current HBM session. + * If HBM is currently running, this is the start time and set of all events, + * for the current HBM session. */ - private long mRunningStartTimeMillis = -1; - - /** - * List of previous HBM-events ordered from most recent to least recent. - * Meant to store only the events that fall into the most recent - * {@link mHbmData.timeWindowMillis}. - */ - private LinkedList<HbmEvent> mEvents = new LinkedList<>(); + private HighBrightnessModeMetadata mHighBrightnessModeMetadata = null; HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, - Runnable hbmChangeCallback, Context context) { + Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) { this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin, - brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, context); + brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context); } @VisibleForTesting HighBrightnessModeController(Injector injector, Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, - Runnable hbmChangeCallback, Context context) { + Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) { mInjector = injector; mContext = context; mClock = injector.getClock(); @@ -137,6 +131,7 @@ class HighBrightnessModeController { mBrightnessMin = brightnessMin; mBrightnessMax = brightnessMax; mHbmChangeCallback = hbmChangeCallback; + mHighBrightnessModeMetadata = hbmMetadata; mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); mSettingsObserver = new SettingsObserver(mHandler); mRecalcRunnable = this::recalculateTimeAllowance; @@ -222,19 +217,22 @@ class HighBrightnessModeController { // If we are starting or ending a high brightness mode session, store the current // session in mRunningStartTimeMillis, or the old one in mEvents. - final boolean wasHbmDrainingAvailableTime = mRunningStartTimeMillis != -1; + final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis(); + final boolean wasHbmDrainingAvailableTime = runningStartTime != -1; final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint && !mIsHdrLayerPresent; if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) { final long currentTime = mClock.uptimeMillis(); if (shouldHbmDrainAvailableTime) { - mRunningStartTimeMillis = currentTime; + mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime); } else { - mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime)); - mRunningStartTimeMillis = -1; + final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime); + mHighBrightnessModeMetadata.addHbmEvent(hbmEvent); + mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1); if (DEBUG) { - Slog.d(TAG, "New HBM event: " + mEvents.getFirst()); + Slog.d(TAG, "New HBM event: " + + mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst()); } } } @@ -260,6 +258,10 @@ class HighBrightnessModeController { mSettingsObserver.stopObserving(); } + void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) { + mHighBrightnessModeMetadata = hbmInfo; + } + void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) { mWidth = width; @@ -316,20 +318,22 @@ class HighBrightnessModeController { pw.println(" mBrightnessMax=" + mBrightnessMax); pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis())); pw.println(" mIsTimeAvailable= " + mIsTimeAvailable); - pw.println(" mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis)); + pw.println(" mRunningStartTimeMillis=" + + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis())); pw.println(" mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit); pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode); pw.println(" width*height=" + mWidth + "*" + mHeight); pw.println(" mEvents="); final long currentTime = mClock.uptimeMillis(); long lastStartTime = currentTime; - if (mRunningStartTimeMillis != -1) { - lastStartTime = dumpHbmEvent(pw, new HbmEvent(mRunningStartTimeMillis, currentTime)); + long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis(); + if (runningStartTimeMillis != -1) { + lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime)); } - for (HbmEvent event : mEvents) { - if (lastStartTime > event.endTimeMillis) { + for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) { + if (lastStartTime > event.getEndTimeMillis()) { pw.println(" event: [normal brightness]: " - + TimeUtils.formatDuration(lastStartTime - event.endTimeMillis)); + + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis())); } lastStartTime = dumpHbmEvent(pw, event); } @@ -338,12 +342,12 @@ class HighBrightnessModeController { } private long dumpHbmEvent(PrintWriter pw, HbmEvent event) { - final long duration = event.endTimeMillis - event.startTimeMillis; + final long duration = event.getEndTimeMillis() - event.getStartTimeMillis(); pw.println(" event: [" - + TimeUtils.formatUptime(event.startTimeMillis) + ", " - + TimeUtils.formatUptime(event.endTimeMillis) + "] (" + + TimeUtils.formatUptime(event.getStartTimeMillis()) + ", " + + TimeUtils.formatUptime(event.getEndTimeMillis()) + "] (" + TimeUtils.formatDuration(duration) + ")"); - return event.startTimeMillis; + return event.getStartTimeMillis(); } private boolean isCurrentlyAllowed() { @@ -372,13 +376,15 @@ class HighBrightnessModeController { // First, lets see how much time we've taken for any currently running // session of HBM. - if (mRunningStartTimeMillis > 0) { - if (mRunningStartTimeMillis > currentTime) { + long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis(); + if (runningStartTimeMillis > 0) { + if (runningStartTimeMillis > currentTime) { Slog.e(TAG, "Start time set to the future. curr: " + currentTime - + ", start: " + mRunningStartTimeMillis); - mRunningStartTimeMillis = currentTime; + + ", start: " + runningStartTimeMillis); + mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime); + runningStartTimeMillis = currentTime; } - timeAlreadyUsed = currentTime - mRunningStartTimeMillis; + timeAlreadyUsed = currentTime - runningStartTimeMillis; } if (DEBUG) { @@ -387,18 +393,19 @@ class HighBrightnessModeController { // Next, lets iterate through the history of previous sessions and add those times. final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis; - Iterator<HbmEvent> it = mEvents.iterator(); + Iterator<HbmEvent> it = mHighBrightnessModeMetadata.getHbmEventQueue().iterator(); while (it.hasNext()) { final HbmEvent event = it.next(); // If this event ended before the current Timing window, discard forever and ever. - if (event.endTimeMillis < windowstartTimeMillis) { + if (event.getEndTimeMillis() < windowstartTimeMillis) { it.remove(); continue; } - final long startTimeMillis = Math.max(event.startTimeMillis, windowstartTimeMillis); - timeAlreadyUsed += event.endTimeMillis - startTimeMillis; + final long startTimeMillis = Math.max(event.getStartTimeMillis(), + windowstartTimeMillis); + timeAlreadyUsed += event.getEndTimeMillis() - startTimeMillis; } if (DEBUG) { @@ -425,17 +432,18 @@ class HighBrightnessModeController { // Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or // brightness change doesn't happen before then. long nextTimeout = -1; + final ArrayDeque<HbmEvent> hbmEvents = mHighBrightnessModeMetadata.getHbmEventQueue(); if (mBrightness > mHbmData.transitionPoint) { // if we're in high-lux now, timeout when we run out of allowed time. nextTimeout = currentTime + remainingTime; - } else if (!mIsTimeAvailable && mEvents.size() > 0) { + } else if (!mIsTimeAvailable && hbmEvents.size() > 0) { // If we are not allowed...timeout when the oldest event moved outside of the timing // window by at least minTime. Basically, we're calculating the soonest time we can // get {@code timeMinMillis} back to us. final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis; - final HbmEvent lastEvent = mEvents.getLast(); + final HbmEvent lastEvent = hbmEvents.peekLast(); final long startTimePlusMinMillis = - Math.max(windowstartTimeMillis, lastEvent.startTimeMillis) + Math.max(windowstartTimeMillis, lastEvent.getStartTimeMillis()) + mHbmData.timeMinMillis; final long timeWhenMinIsGainedBack = currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime; @@ -459,9 +467,10 @@ class HighBrightnessModeController { + ", mUnthrottledBrightness: " + mUnthrottledBrightness + ", mThrottlingReason: " + BrightnessInfo.briMaxReasonToString(mThrottlingReason) - + ", RunningStartTimeMillis: " + mRunningStartTimeMillis + + ", RunningStartTimeMillis: " + + mHighBrightnessModeMetadata.getRunningStartTimeMillis() + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1) - + ", events: " + mEvents); + + ", events: " + hbmEvents); } if (nextTimeout != -1) { @@ -588,25 +597,6 @@ class HighBrightnessModeController { } } - /** - * Represents an event in which High Brightness Mode was enabled. - */ - private static class HbmEvent { - public long startTimeMillis; - public long endTimeMillis; - - HbmEvent(long startTimeMillis, long endTimeMillis) { - this.startTimeMillis = startTimeMillis; - this.endTimeMillis = endTimeMillis; - } - - @Override - public String toString() { - return "[Event: {" + startTimeMillis + ", " + endTimeMillis + "}, total: " - + ((endTimeMillis - startTimeMillis) / 1000) + "]"; - } - } - @VisibleForTesting class HdrListener extends SurfaceControlHdrLayerInfoListener { @Override diff --git a/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java new file mode 100644 index 000000000000..37234ff0bf19 --- /dev/null +++ b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java @@ -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.server.display; + +import java.util.ArrayDeque; + + +/** + * Represents High Brightness Mode metadata associated + * with a specific internal physical display. + * Required for separately storing data like time information, + * and related events when display was in HBM mode per + * physical internal display. + */ +class HighBrightnessModeMetadata { + /** + * Queue of previous HBM-events ordered from most recent to least recent. + * Meant to store only the events that fall into the most recent + * {@link HighBrightnessModeData#timeWindowMillis mHbmData.timeWindowMillis}. + */ + private final ArrayDeque<HbmEvent> mEvents = new ArrayDeque<>(); + + /** + * If HBM is currently running, this is the start time for the current HBM session. + */ + private long mRunningStartTimeMillis = -1; + + public long getRunningStartTimeMillis() { + return mRunningStartTimeMillis; + } + + public void setRunningStartTimeMillis(long setTime) { + mRunningStartTimeMillis = setTime; + } + + public ArrayDeque<HbmEvent> getHbmEventQueue() { + return mEvents; + } + + public void addHbmEvent(HbmEvent hbmEvent) { + mEvents.addFirst(hbmEvent); + } +} + diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index bf576b8909f2..375e51c672b5 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -372,12 +372,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { void setDeviceStateLocked(int state, boolean isOverrideActive) { Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted); + mPendingDeviceState = state; + + if (!mBootCompleted) { + // The boot animation might still be in progress, we do not want to switch states now + // as the boot animation would end up with an incorrect size. + if (DEBUG) { + Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState + + " until boot is completed"); + } + return; + } + // As part of a state transition, we may need to turn off some displays temporarily so that // the transition is smooth. Plus, on some devices, only one internal displays can be // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be // temporarily turned off. resetLayoutLocked(mDeviceState, state, /* isStateChangeStarting= */ true); - mPendingDeviceState = state; final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState, @@ -424,6 +435,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { void onBootCompleted() { synchronized (mSyncRoot) { mBootCompleted = true; + if (mPendingDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) { + setDeviceStateLocked(mPendingDeviceState, /* isOverrideActive= */ false); + } } } @@ -926,6 +940,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final int layerStack = assignLayerStackLocked(displayId); final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); display.updateLocked(mDisplayDeviceRepo); + + final DisplayInfo info = display.getDisplayInfoLocked(); + if (info.type == Display.TYPE_INTERNAL && mDeviceStateToLayoutMap.size() > 1) { + // If this is an internal display and the device uses a display layout configuration, + // the display should be disabled as later we will receive a device state update, which + // will tell us which internal displays should be enabled and which should be disabled. + display.setEnabledLocked(false); + } + mLogicalDisplays.put(displayId, display); return display; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 4d44c886fa22..6bc85824763c 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -239,7 +239,6 @@ import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationRecordProto; import android.service.notification.NotificationServiceDumpProto; import android.service.notification.NotificationStats; -import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeProto; @@ -8526,95 +8525,6 @@ public class NotificationManagerService extends SystemService { } } - static class NotificationRecordExtractorData { - // Class that stores any field in a NotificationRecord that can change via an extractor. - // Used to cache previous data used in a sort. - int mPosition; - int mVisibility; - boolean mShowBadge; - boolean mAllowBubble; - boolean mIsBubble; - NotificationChannel mChannel; - String mGroupKey; - ArrayList<String> mOverridePeople; - ArrayList<SnoozeCriterion> mSnoozeCriteria; - Integer mUserSentiment; - Integer mSuppressVisually; - ArrayList<Notification.Action> mSystemSmartActions; - ArrayList<CharSequence> mSmartReplies; - int mImportance; - - // These fields may not trigger a reranking but diffs here may be logged. - float mRankingScore; - boolean mIsConversation; - - NotificationRecordExtractorData(int position, int visibility, boolean showBadge, - boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey, - ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria, - Integer userSentiment, Integer suppressVisually, - ArrayList<Notification.Action> systemSmartActions, - ArrayList<CharSequence> smartReplies, int importance, float rankingScore, - boolean isConversation) { - mPosition = position; - mVisibility = visibility; - mShowBadge = showBadge; - mAllowBubble = allowBubble; - mIsBubble = isBubble; - mChannel = channel; - mGroupKey = groupKey; - mOverridePeople = overridePeople; - mSnoozeCriteria = snoozeCriteria; - mUserSentiment = userSentiment; - mSuppressVisually = suppressVisually; - mSystemSmartActions = systemSmartActions; - mSmartReplies = smartReplies; - mImportance = importance; - mRankingScore = rankingScore; - mIsConversation = isConversation; - } - - // Returns whether the provided NotificationRecord differs from the cached data in any way. - // Should be guarded by mNotificationLock; not annotated here as this class is static. - boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) { - return mPosition != newPosition - || mVisibility != r.getPackageVisibilityOverride() - || mShowBadge != r.canShowBadge() - || mAllowBubble != r.canBubble() - || mIsBubble != r.getNotification().isBubbleNotification() - || !Objects.equals(mChannel, r.getChannel()) - || !Objects.equals(mGroupKey, r.getGroupKey()) - || !Objects.equals(mOverridePeople, r.getPeopleOverride()) - || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria()) - || !Objects.equals(mUserSentiment, r.getUserSentiment()) - || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects()) - || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions()) - || !Objects.equals(mSmartReplies, r.getSmartReplies()) - || mImportance != r.getImportance(); - } - - // Returns whether the NotificationRecord has a change from this data for which we should - // log an update. This method specifically targets fields that may be changed via - // adjustments from the assistant. - // - // Fields here are the union of things in NotificationRecordLogger.shouldLogReported - // and NotificationRecord.applyAdjustments. - // - // Should be guarded by mNotificationLock; not annotated here as this class is static. - boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) { - return mPosition != newPosition - || !Objects.equals(mChannel, r.getChannel()) - || !Objects.equals(mGroupKey, r.getGroupKey()) - || !Objects.equals(mOverridePeople, r.getPeopleOverride()) - || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria()) - || !Objects.equals(mUserSentiment, r.getUserSentiment()) - || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions()) - || !Objects.equals(mSmartReplies, r.getSmartReplies()) - || mImportance != r.getImportance() - || !r.rankingScoreMatches(mRankingScore) - || mIsConversation != r.isConversation(); - } - } - void handleRankingSort() { if (mRankingHelper == null) return; synchronized (mNotificationLock) { @@ -8640,7 +8550,8 @@ public class NotificationManagerService extends SystemService { r.getSmartReplies(), r.getImportance(), r.getRankingScore(), - r.isConversation()); + r.isConversation(), + r.getProposedImportance()); extractorDataBefore.put(r.getKey(), extractorData); mRankingHelper.extractSignals(r); } @@ -9935,7 +9846,8 @@ public class NotificationManagerService extends SystemService { record.getRankingScore() == 0 ? RANKING_UNCHANGED : (record.getRankingScore() > 0 ? RANKING_PROMOTED : RANKING_DEMOTED), - record.getNotification().isBubbleNotification() + record.getNotification().isBubbleNotification(), + record.getProposedImportance() ); rankings.add(ranking); } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index cbaf485c077f..d3443066155f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -210,6 +210,7 @@ public final class NotificationRecord { // Whether this notification record should have an update logged the next time notifications // are sorted. private boolean mPendingLogUpdate = false; + private int mProposedImportance = IMPORTANCE_UNSPECIFIED; public NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel) { @@ -499,6 +500,8 @@ public final class NotificationRecord { pw.println(prefix + "mImportance=" + NotificationListenerService.Ranking.importanceToString(mImportance)); pw.println(prefix + "mImportanceExplanation=" + getImportanceExplanation()); + pw.println(prefix + "mProposedImportance=" + + NotificationListenerService.Ranking.importanceToString(mProposedImportance)); pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked); pw.println(prefix + "mIntercept=" + mIntercept); pw.println(prefix + "mHidden==" + mHidden); @@ -738,6 +741,12 @@ public final class NotificationRecord { Adjustment.KEY_NOT_CONVERSATION, Boolean.toString(mIsNotConversationOverride)); } + if (signals.containsKey(Adjustment.KEY_IMPORTANCE_PROPOSAL)) { + mProposedImportance = signals.getInt(Adjustment.KEY_IMPORTANCE_PROPOSAL); + EventLogTags.writeNotificationAdjusted(getKey(), + Adjustment.KEY_IMPORTANCE_PROPOSAL, + Integer.toString(mProposedImportance)); + } if (!signals.isEmpty() && adjustment.getIssuer() != null) { mAdjustmentIssuer = adjustment.getIssuer(); } @@ -870,6 +879,10 @@ public final class NotificationRecord { return stats.naturalImportance; } + public int getProposedImportance() { + return mProposedImportance; + } + public float getRankingScore() { return mRankingScore; } diff --git a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java new file mode 100644 index 000000000000..6dc9029f8928 --- /dev/null +++ b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java @@ -0,0 +1,118 @@ +/* + * 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.notification; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.service.notification.SnoozeCriterion; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Class that stores any field in a NotificationRecord that can change via an extractor. + * Used to cache previous data used in a sort. + */ +public final class NotificationRecordExtractorData { + private final int mPosition; + private final int mVisibility; + private final boolean mShowBadge; + private final boolean mAllowBubble; + private final boolean mIsBubble; + private final NotificationChannel mChannel; + private final String mGroupKey; + private final ArrayList<String> mOverridePeople; + private final ArrayList<SnoozeCriterion> mSnoozeCriteria; + private final Integer mUserSentiment; + private final Integer mSuppressVisually; + private final ArrayList<Notification.Action> mSystemSmartActions; + private final ArrayList<CharSequence> mSmartReplies; + private final int mImportance; + + // These fields may not trigger a reranking but diffs here may be logged. + private final float mRankingScore; + private final boolean mIsConversation; + private final int mProposedImportance; + + NotificationRecordExtractorData(int position, int visibility, boolean showBadge, + boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey, + ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria, + Integer userSentiment, Integer suppressVisually, + ArrayList<Notification.Action> systemSmartActions, + ArrayList<CharSequence> smartReplies, int importance, float rankingScore, + boolean isConversation, int proposedImportance) { + mPosition = position; + mVisibility = visibility; + mShowBadge = showBadge; + mAllowBubble = allowBubble; + mIsBubble = isBubble; + mChannel = channel; + mGroupKey = groupKey; + mOverridePeople = overridePeople; + mSnoozeCriteria = snoozeCriteria; + mUserSentiment = userSentiment; + mSuppressVisually = suppressVisually; + mSystemSmartActions = systemSmartActions; + mSmartReplies = smartReplies; + mImportance = importance; + mRankingScore = rankingScore; + mIsConversation = isConversation; + mProposedImportance = proposedImportance; + } + + // Returns whether the provided NotificationRecord differs from the cached data in any way. + // Should be guarded by mNotificationLock; not annotated here as this class is static. + boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) { + return mPosition != newPosition + || mVisibility != r.getPackageVisibilityOverride() + || mShowBadge != r.canShowBadge() + || mAllowBubble != r.canBubble() + || mIsBubble != r.getNotification().isBubbleNotification() + || !Objects.equals(mChannel, r.getChannel()) + || !Objects.equals(mGroupKey, r.getGroupKey()) + || !Objects.equals(mOverridePeople, r.getPeopleOverride()) + || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria()) + || !Objects.equals(mUserSentiment, r.getUserSentiment()) + || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects()) + || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions()) + || !Objects.equals(mSmartReplies, r.getSmartReplies()) + || mImportance != r.getImportance() + || mProposedImportance != r.getProposedImportance(); + } + + // Returns whether the NotificationRecord has a change from this data for which we should + // log an update. This method specifically targets fields that may be changed via + // adjustments from the assistant. + // + // Fields here are the union of things in NotificationRecordLogger.shouldLogReported + // and NotificationRecord.applyAdjustments. + // + // Should be guarded by mNotificationLock; not annotated here as this class is static. + boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) { + return mPosition != newPosition + || !Objects.equals(mChannel, r.getChannel()) + || !Objects.equals(mGroupKey, r.getGroupKey()) + || !Objects.equals(mOverridePeople, r.getPeopleOverride()) + || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria()) + || !Objects.equals(mUserSentiment, r.getUserSentiment()) + || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions()) + || !Objects.equals(mSmartReplies, r.getSmartReplies()) + || mImportance != r.getImportance() + || !r.rankingScoreMatches(mRankingScore) + || mIsConversation != r.isConversation() + || mProposedImportance != r.getProposedImportance(); + } +} diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index d8aa469bcd81..cdcf6c4ea206 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1005,6 +1005,7 @@ public class PreferencesHelper implements RankingConfig { channel.setAllowBubbles(existing != null ? existing.getAllowBubbles() : NotificationChannel.DEFAULT_ALLOW_BUBBLE); + channel.setImportantConversation(false); } clearLockedFieldsLocked(channel); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 00fb0651adc4..866a995585cd 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -91,6 +91,7 @@ import android.security.GateKeeper; import android.service.gatekeeper.IGateKeeperService; import android.service.voice.VoiceInteractionManagerInternal; import android.stats.devicepolicy.DevicePolicyEnums; +import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -1760,6 +1761,63 @@ public class UserManagerService extends IUserManager.Stub { } } + /** + * Returns whether switching users is currently allowed for the provided user. + * <p> + * Switching users is not allowed in the following cases: + * <li>the user is in a phone call</li> + * <li>{@link UserManager#DISALLOW_USER_SWITCH} is set</li> + * <li>system user hasn't been unlocked yet</li> + * + * @return A {@link UserManager.UserSwitchabilityResult} flag indicating if the user is + * switchable. + */ + public @UserManager.UserSwitchabilityResult int getUserSwitchability(int userId) { + checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserSwitchability"); + + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("getUserSwitchability-" + userId); + + int flags = UserManager.SWITCHABILITY_STATUS_OK; + + t.traceBegin("TM.isInCall"); + final long identity = Binder.clearCallingIdentity(); + try { + final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); + if (telecomManager != null && telecomManager.isInCall()) { + flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL; + } + } finally { + Binder.restoreCallingIdentity(identity); + } + t.traceEnd(); + + t.traceBegin("hasUserRestriction-DISALLOW_USER_SWITCH"); + if (mLocalService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)) { + flags |= UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED; + } + t.traceEnd(); + + // System User is always unlocked in Headless System User Mode, so ignore this flag + if (!UserManager.isHeadlessSystemUserMode()) { + t.traceBegin("getInt-ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED"); + final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0; + t.traceEnd(); + t.traceBegin("isUserUnlocked-USER_SYSTEM"); + final boolean systemUserUnlocked = mLocalService.isUserUnlocked(UserHandle.USER_SYSTEM); + t.traceEnd(); + + if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) { + flags |= UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED; + } + } + t.traceEnd(); + + return flags; + } + @Override public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) { boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(), diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index e80c2607a0ad..0bfc48b4b54c 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -98,7 +98,7 @@ class AppTaskImpl extends IAppTask.Stub { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } return mService.getRecentTasks().createRecentTaskInfo(task, - false /* stripExtras */); + false /* stripExtras */, true /* getTasksAllowed */); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index abaa3630ff7b..0ea6157dd2a4 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -892,7 +892,7 @@ public class AppTransitionController { * * TODO(b/213312721): Remove this predicate and its callers once ShellTransition is enabled. */ - private static boolean isTaskViewTask(WindowContainer wc) { + static boolean isTaskViewTask(WindowContainer wc) { // We use Task#mRemoveWithTaskOrganizer to identify an embedded Task, but this is a hack and // it is not guaranteed to work this logic in the future version. return wc instanceof Task && ((Task) wc).mRemoveWithTaskOrganizer; diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index ba0413df6325..c6037dab6568 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -203,8 +203,11 @@ final class DisplayRotationCompatPolicy { || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) { return; } - boolean cycleThroughStop = mWmService.mLetterboxConfiguration - .isCameraCompatRefreshCycleThroughStopEnabled(); + boolean cycleThroughStop = + mWmService.mLetterboxConfiguration + .isCameraCompatRefreshCycleThroughStopEnabled() + && !activity.mLetterboxUiController + .shouldRefreshActivityViaPauseForCameraCompat(); try { activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true); ProtoLog.v(WM_DEBUG_STATES, @@ -255,7 +258,8 @@ final class DisplayRotationCompatPolicy { Configuration lastReportedConfig) { return newConfig.windowConfiguration.getDisplayRotation() != lastReportedConfig.windowConfiguration.getDisplayRotation() - && isTreatmentEnabledForActivity(activity); + && isTreatmentEnabledForActivity(activity) + && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat(); } /** @@ -294,7 +298,8 @@ final class DisplayRotationCompatPolicy { // handle dynamic changes so we shouldn't force rotate them. && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED - && mCameraIdPackageBiMap.containsPackageName(activity.packageName); + && mCameraIdPackageBiMap.containsPackageName(activity.packageName) + && activity.mLetterboxUiController.shouldForceRotateForCameraCompat(); } private synchronized void notifyCameraOpened( diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 0c8a6453e6fb..75ba2146267d 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -17,12 +17,18 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ActivityInfo.screenOrientationToString; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; @@ -132,6 +138,15 @@ final class LetterboxUiController { @Nullable private Letterbox mLetterbox; + @Nullable + private final Boolean mBooleanPropertyCameraCompatAllowForceRotation; + + @Nullable + private final Boolean mBooleanPropertyCameraCompatAllowRefresh; + + @Nullable + private final Boolean mBooleanPropertyCameraCompatEnableRefreshViaPause; + // Whether activity "refresh" was requested but not finished in // ActivityRecord#activityResumedLocked following the camera compat force rotation in // DisplayRotationCompatPolicy. @@ -154,8 +169,33 @@ final class LetterboxUiController { readComponentProperty(packageManager, mActivityRecord.packageName, mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); + mBooleanPropertyCameraCompatAllowForceRotation = + readComponentProperty(packageManager, mActivityRecord.packageName, + () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( + /* checkDeviceConfig */ true), + PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION); + mBooleanPropertyCameraCompatAllowRefresh = + readComponentProperty(packageManager, mActivityRecord.packageName, + () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( + /* checkDeviceConfig */ true), + PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH); + mBooleanPropertyCameraCompatEnableRefreshViaPause = + readComponentProperty(packageManager, mActivityRecord.packageName, + () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( + /* checkDeviceConfig */ true), + PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); } + /** + * Reads a {@link Boolean} component property fot a given {@code packageName} and a {@code + * propertyName}. Returns {@code null} if {@code gatingCondition} is {@code false} or if the + * property isn't specified for the package. + * + * <p>Return value is {@link Boolean} rather than {@code boolean} so we can know when the + * property is unset. Particularly, when this returns {@code null}, {@link + * #shouldEnableWithOverrideAndProperty} will check the value of override for the final + * decision. + */ @Nullable private static Boolean readComponentProperty(PackageManager packageManager, String packageName, BooleanSupplier gatingCondition, String propertyName) { @@ -210,15 +250,11 @@ final class LetterboxUiController { * </ul> */ boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) { - if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) { - return false; - } - if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) { - return false; - } - if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation) - && !mActivityRecord.info.isChangeEnabled( - OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) { + if (!shouldEnableWithOverrideAndProperty( + /* gatingCondition */ mLetterboxConfiguration + ::isPolicyForIgnoringRequestedOrientationEnabled, + OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION, + mBooleanPropertyIgnoreRequestedOrientation)) { return false; } if (mIsRelauchingAfterRequestedOrientationChanged) { @@ -262,6 +298,109 @@ final class LetterboxUiController { mIsRefreshAfterRotationRequested = isRequested; } + /** + * Whether activity is eligible for activity "refresh" after camera compat force rotation + * treatment. See {@link DisplayRotationCompatPolicy} for context. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the camera compat treatment is enabled. + * <li>Activity isn't opted out by the device manufacturer with override or by the app + * developers with the component property. + * </ul> + */ + boolean shouldRefreshActivityForCameraCompat() { + return shouldEnableWithOptOutOverrideAndProperty( + /* gatingCondition */ () -> mLetterboxConfiguration + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), + OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH, + mBooleanPropertyCameraCompatAllowRefresh); + } + + /** + * Whether activity should be "refreshed" after the camera compat force rotation treatment + * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped + * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the camera compat treatment is enabled. + * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the + * component property by the app developers. + * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device + * manufacturer with override / by the app developers with the component property. + * </ul> + */ + boolean shouldRefreshActivityViaPauseForCameraCompat() { + return shouldEnableWithOverrideAndProperty( + /* gatingCondition */ () -> mLetterboxConfiguration + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), + OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, + mBooleanPropertyCameraCompatEnableRefreshViaPause); + } + + /** + * Whether activity is eligible for camera compat force rotation treatment. See {@link + * DisplayRotationCompatPolicy} for context. + * + * <p>This treatment is enabled when the following conditions are met: + * <ul> + * <li>Flag gating the camera compat treatment is enabled. + * <li>Activity isn't opted out by the device manufacturer with override or by the app + * developers with the component property. + * </ul> + */ + boolean shouldForceRotateForCameraCompat() { + return shouldEnableWithOptOutOverrideAndProperty( + /* gatingCondition */ () -> mLetterboxConfiguration + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), + OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION, + mBooleanPropertyCameraCompatAllowForceRotation); + } + + /** + * Returns {@code true} when the following conditions are met: + * <ul> + * <li>{@code gatingCondition} isn't {@code false} + * <li>OEM didn't opt out with a {@code overrideChangeId} override + * <li>App developers didn't opt out with a component {@code property} + * </ul> + * + * <p>This is used for the treatments that are enabled based with the heuristic but can be + * disabled on per-app basis by OEMs or app developers. + */ + private boolean shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition, + long overrideChangeId, Boolean property) { + if (!gatingCondition.getAsBoolean()) { + return false; + } + return !Boolean.FALSE.equals(property) + && !mActivityRecord.info.isChangeEnabled(overrideChangeId); + } + + /** + * Returns {@code true} when the following conditions are met: + * <ul> + * <li>{@code gatingCondition} isn't {@code false} + * <li>App developers didn't opt out with a component {@code property} + * <li>App developers opted in with a component {@code property} or an OEM opted in with a + * component {@code property} + * </ul> + * + * <p>This is used for the treatments that are enabled only on per-app basis. + */ + private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition, + long overrideChangeId, Boolean property) { + if (!gatingCondition.getAsBoolean()) { + return false; + } + if (Boolean.FALSE.equals(property)) { + return false; + } + return Boolean.TRUE.equals(property) + || mActivityRecord.info.isChangeEnabled(overrideChangeId); + } + boolean hasWallpaperBackgroundForLetterbox() { return mShowWallpaperForLetterboxBackground; } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 4860762a5f7f..1fc061b2ca78 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -976,7 +976,7 @@ class RecentTasks { continue; } - res.add(createRecentTaskInfo(task, true /* stripExtras */)); + res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed)); } return res; } @@ -1895,7 +1895,8 @@ class RecentTasks { /** * Creates a new RecentTaskInfo from a Task. */ - ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) { + ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras, + boolean getTasksAllowed) { final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); // If the recent Task is detached, we consider it will be re-attached to the default // TaskDisplayArea because we currently only support recent overview in the default TDA. @@ -1907,6 +1908,9 @@ class RecentTasks { rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID; rti.persistentId = rti.taskId; rti.lastSnapshotData.set(tr.mLastTaskSnapshotData); + if (!getTasksAllowed) { + Task.trimIneffectiveInfo(tr, rti); + } // Fill in organized child task info for the task created by organizer. if (tr.mCreatedByOrganizer) { diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 120fec0fe0e6..0e60274ba381 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -142,6 +142,10 @@ class RunningTasks { task.fillTaskInfo(rti, !mKeepIntentExtra); // Fill in some deprecated values rti.id = rti.taskId; + + if (!mAllowed) { + Task.trimIneffectiveInfo(task, rti); + } return rti; } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 5806f7917bab..a464112f0492 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3467,6 +3467,54 @@ class Task extends TaskFragment { info.isSleeping = shouldSleepActivities(); } + /** + * Removes the activity info if the activity belongs to a different uid, which is + * different from the app that hosts the task. + */ + static void trimIneffectiveInfo(Task task, TaskInfo info) { + final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing, + false /* traverseTopToBottom */); + final int baseActivityUid = + baseActivity != null ? baseActivity.getUid() : task.effectiveUid; + + if (info.topActivityInfo != null + && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) { + // Making a copy to prevent eliminating the info in the original ActivityRecord. + info.topActivityInfo = new ActivityInfo(info.topActivityInfo); + info.topActivityInfo.applicationInfo = + new ApplicationInfo(info.topActivityInfo.applicationInfo); + + // Strip the sensitive info. + info.topActivity = new ComponentName("", ""); + info.topActivityInfo.packageName = ""; + info.topActivityInfo.taskAffinity = ""; + info.topActivityInfo.processName = ""; + info.topActivityInfo.name = ""; + info.topActivityInfo.parentActivityName = ""; + info.topActivityInfo.targetActivity = ""; + info.topActivityInfo.splitName = ""; + info.topActivityInfo.applicationInfo.className = ""; + info.topActivityInfo.applicationInfo.credentialProtectedDataDir = ""; + info.topActivityInfo.applicationInfo.dataDir = ""; + info.topActivityInfo.applicationInfo.deviceProtectedDataDir = ""; + info.topActivityInfo.applicationInfo.manageSpaceActivityName = ""; + info.topActivityInfo.applicationInfo.nativeLibraryDir = ""; + info.topActivityInfo.applicationInfo.nativeLibraryRootDir = ""; + info.topActivityInfo.applicationInfo.processName = ""; + info.topActivityInfo.applicationInfo.publicSourceDir = ""; + info.topActivityInfo.applicationInfo.scanPublicSourceDir = ""; + info.topActivityInfo.applicationInfo.scanSourceDir = ""; + info.topActivityInfo.applicationInfo.sourceDir = ""; + info.topActivityInfo.applicationInfo.taskAffinity = ""; + info.topActivityInfo.applicationInfo.name = ""; + info.topActivityInfo.applicationInfo.packageName = ""; + } + + if (task.effectiveUid != baseActivityUid) { + info.baseActivity = new ComponentName("", ""); + } + } + @Nullable PictureInPictureParams getPictureInPictureParams() { final Task topTask = getTopMostTask(); if (topTask == null) return null; @@ -6363,6 +6411,11 @@ class Task extends TaskFragment { return this; } + Builder setRemoveWithTaskOrganizer(boolean removeWithTaskOrganizer) { + mRemoveWithTaskOrganizer = removeWithTaskOrganizer; + return this; + } + private Builder setUserId(int userId) { mUserId = userId; return this; @@ -6560,7 +6613,7 @@ class Task extends TaskFragment { mCallingPackage = mActivityInfo.packageName; mResizeMode = mActivityInfo.resizeMode; mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture(); - if (mActivityOptions != null) { + if (!mRemoveWithTaskOrganizer && mActivityOptions != null) { mRemoveWithTaskOrganizer = mActivityOptions.getRemoveWithTaskOranizer(); } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index d619547dbbd1..d780cae9e845 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -783,7 +783,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie) { + public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie, + boolean removeWithTaskOrganizer) { enforceTaskPermission("createRootTask()"); final long origId = Binder.clearCallingIdentity(); try { @@ -795,7 +796,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { return; } - createRootTask(display, windowingMode, launchCookie); + createRootTask(display, windowingMode, launchCookie, removeWithTaskOrganizer); } } finally { Binder.restoreCallingIdentity(origId); @@ -804,6 +805,12 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { @VisibleForTesting Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie) { + return createRootTask(display, windowingMode, launchCookie, + false /* removeWithTaskOrganizer */); + } + + Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie, + boolean removeWithTaskOrganizer) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d", display.mDisplayId, windowingMode); // We want to defer the task appear signal until the task is fully created and attached to @@ -816,6 +823,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { .setDeferTaskAppear(true) .setLaunchCookie(launchCookie) .setParent(display.getDefaultTaskDisplayArea()) + .setRemoveWithTaskOrganizer(removeWithTaskOrganizer) .build(); task.setDeferTaskAppear(false /* deferTaskAppear */); return task; diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 1d25dbc0d533..b2dab78b993d 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -371,7 +371,7 @@ class WallpaperController { boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) { // Size of the display the wallpaper is rendered on. - final Rect lastWallpaperBounds = wallpaperWin.getLastReportedBounds(); + final Rect lastWallpaperBounds = wallpaperWin.getParentFrame(); // Full size of the wallpaper (usually larger than bounds above to parallax scroll when // swiping through Launcher pages). final Rect wallpaperFrame = wallpaperWin.getFrame(); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index fb584feeaf04..8bdab9c22ab7 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3197,11 +3197,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) { - if (isOrganized() + if (AppTransitionController.isTaskViewTask(this) || (isOrganized() // TODO(b/161711458): Clean-up when moved to shell. && getWindowingMode() != WINDOWING_MODE_FULLSCREEN && getWindowingMode() != WINDOWING_MODE_FREEFORM - && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW) { + && getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW)) { return null; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 3187337de051..38613a655d70 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1949,6 +1949,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub creationParams.getPairedPrimaryFragmentToken()); final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment); position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP; + } else if (creationParams.getPairedActivityToken() != null) { + // When there is a paired Activity, we want to place the new TaskFragment right above + // the paired Activity to make sure the Activity position is not changed after reparent. + final ActivityRecord pairedActivity = ActivityRecord.forTokenLocked( + creationParams.getPairedActivityToken()); + final int pairedPosition = ownerTask.mChildren.indexOf(pairedActivity); + position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP; } else { position = POSITION_TOP; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 04699619bfc8..63607ad19f5a 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3081,12 +3081,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mLastReportedConfiguration.getMergedConfiguration(); } - /** Returns the last window configuration bounds reported to the client. */ - Rect getLastReportedBounds() { - final Rect bounds = getLastReportedConfiguration().windowConfiguration.getBounds(); - return !bounds.isEmpty() ? bounds : getBounds(); - } - void adjustStartingWindowFlags() { if (mAttrs.type == TYPE_BASE_APPLICATION && mActivityRecord != null && mActivityRecord.mStartingWindow != null) { @@ -4421,6 +4415,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.print("null"); } + if (mXOffset != 0 || mYOffset != 0) { + pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset); + } if (mHScale != 1 || mVScale != 1) { pw.println(prefix + "mHScale=" + mHScale + " mVScale=" + mVScale); @@ -5573,7 +5570,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSurfacePosition); if (mWallpaperScale != 1f) { - final Rect bounds = getLastReportedBounds(); + final Rect bounds = getParentFrame(); Matrix matrix = mTmpMatrix; matrix.setTranslate(mXOffset, mYOffset); matrix.postScale(mWallpaperScale, mWallpaperScale, bounds.exactCenterX(), @@ -5686,6 +5683,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP && imeTarget.compareTo(this) <= 0; return inTokenWithAndAboveImeTarget; } + + // The condition is for the system dialog not belonging to any Activity. + // (^FLAG_NOT_FOCUSABLE & FLAG_ALT_FOCUSABLE_IM) means the dialog is still focusable but + // should be placed above the IME window. + if ((mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)) + == FLAG_ALT_FOCUSABLE_IM && isTrustedOverlay() && canAddInternalSystemWindow()) { + return true; + } return false; } diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index f628fbad892d..abe48f894e59 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -464,6 +464,14 @@ </xs:complexType> <xs:complexType name="refreshRateConfigs"> + <xs:element name="defaultRefreshRate" type="xs:nonNegativeInteger" + minOccurs="0" maxOccurs="1"> + <xs:annotation name="final"/> + </xs:element> + <xs:element name="defaultPeakRefreshRate" type="xs:nonNegativeInteger" + minOccurs="0" maxOccurs="1"> + <xs:annotation name="final"/> + </xs:element> <xs:element name="lowerBlockingZoneConfigs" type="blockingZoneConfig" minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index cb081791ffce..2c97af55f092 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -186,8 +186,12 @@ package com.android.server.display.config { public class RefreshRateConfigs { ctor public RefreshRateConfigs(); + method public final java.math.BigInteger getDefaultPeakRefreshRate(); + method public final java.math.BigInteger getDefaultRefreshRate(); method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs(); method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs(); + method public final void setDefaultPeakRefreshRate(java.math.BigInteger); + method public final void setDefaultRefreshRate(java.math.BigInteger); method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig); method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig); } diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index dad9fe8648b2..31599eed539d 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -74,7 +74,7 @@ public class AudioDeviceBrokerTest { mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem)); mSpySystemServer = spy(new NoOpSystemServerAdapter()); mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory, - mSpySystemServer); + mSpySystemServer, mSpyAudioSystem); mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 666d4010e921..3c735e335e75 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -41,7 +41,6 @@ import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.Fingerprint; -import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -55,7 +54,6 @@ import android.testing.TestableContext; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.R; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; @@ -369,274 +367,6 @@ public class FingerprintAuthenticationClientTest { verify(mCancellationSignal).cancel(); } - @Test - public void fingerprintPowerIgnoresAuthInWindow() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - client.onPowerPressed(); - client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - mLooper.moveTimeForward(1000); - mLooper.dispatchAll(); - - verify(mCallback).onClientFinished(any(), eq(false)); - verify(mCancellationSignal).cancel(); - } - - @Test - public void fingerprintAuthIgnoredWaitingForPower() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - client.onAuthenticated(new Fingerprint("friendly", 3 /* fingerId */, 4 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - client.onPowerPressed(); - mLooper.moveTimeForward(1000); - mLooper.dispatchAll(); - - verify(mCallback).onClientFinished(any(), eq(false)); - verify(mCancellationSignal).cancel(); - } - - @Test - public void fingerprintAuthFailsWhenAuthAfterPower() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - client.onPowerPressed(); - mLooper.dispatchAll(); - mLooper.moveTimeForward(1000); - mLooper.dispatchAll(); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - mLooper.dispatchAll(); - mLooper.moveTimeForward(1000); - mLooper.dispatchAll(); - - verify(mCallback, never()).onClientFinished(any(), eq(true)); - verify(mCallback).onClientFinished(any(), eq(false)); - when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal); - } - - @Test - public void sideFingerprintDoesntSendAuthImmediately() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - mLooper.dispatchAll(); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - mLooper.dispatchAll(); - - verify(mCallback, never()).onClientFinished(any(), anyBoolean()); - } - - @Test - public void sideFingerprintSkipsWindowIfFingerUp() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - mLooper.dispatchAll(); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - client.onAcquired(FINGER_UP, 0); - mLooper.dispatchAll(); - - verify(mCallback).onClientFinished(any(), eq(true)); - } - - @Test - public void sideFingerprintSkipsWindowIfVendorMessageMatch() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - final int vendorAcquireMessage = 1234; - - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, - FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR); - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage, - vendorAcquireMessage); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - mLooper.dispatchAll(); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, vendorAcquireMessage); - mLooper.dispatchAll(); - - verify(mCallback).onClientFinished(any(), eq(true)); - } - - @Test - public void sideFingerprintDoesNotSkipWindowOnVendorErrorMismatch() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - final int vendorAcquireMessage = 1234; - - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, - FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR); - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage, - vendorAcquireMessage); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - mLooper.dispatchAll(); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 1); - mLooper.dispatchAll(); - - verify(mCallback, never()).onClientFinished(any(), anyBoolean()); - } - - @Test - public void sideFingerprintSendsAuthIfFingerUp() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - mLooper.dispatchAll(); - client.onAcquired(FINGER_UP, 0); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - mLooper.dispatchAll(); - - verify(mCallback).onClientFinished(any(), eq(true)); - } - - @Test - public void sideFingerprintShortCircuitExpires() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - - final int timeBeforeAuthSent = 500; - - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsKeyguardPowerPressWindow, timeBeforeAuthSent); - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - mLooper.dispatchAll(); - client.onAcquired(FINGER_UP, 0); - mLooper.dispatchAll(); - - mLooper.moveTimeForward(500); - mLooper.dispatchAll(); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - mLooper.dispatchAll(); - verify(mCallback, never()).onClientFinished(any(), anyBoolean()); - - mLooper.moveTimeForward(500); - mLooper.dispatchAll(); - verify(mCallback).onClientFinished(any(), eq(true)); - } - - @Test - public void sideFingerprintPowerWindowStartsOnAcquireStart() throws Exception { - final int powerWindow = 500; - final long authStart = 300; - - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsBpPowerPressWindow, powerWindow); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - - // Acquire start occurs at time = 0ms - when(mClock.millis()).thenReturn(0L); - client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); - - // Auth occurs at time = 300 - when(mClock.millis()).thenReturn(authStart); - // At this point the delay should be 500 - (300 - 0) == 200 milliseconds. - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - mLooper.dispatchAll(); - verify(mCallback, never()).onClientFinished(any(), anyBoolean()); - - // After waiting 200 milliseconds, auth should succeed. - mLooper.moveTimeForward(powerWindow - authStart); - mLooper.dispatchAll(); - verify(mCallback).onClientFinished(any(), eq(true)); - } - - @Test - public void sideFingerprintPowerWindowStartsOnLastAcquireStart() throws Exception { - final int powerWindow = 500; - - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - mContext.getOrCreateTestableResources().addOverride( - R.integer.config_sidefpsBpPowerPressWindow, powerWindow); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - // Acquire start occurs at time = 0ms - when(mClock.millis()).thenReturn(0L); - client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); - - // Auth reject occurs at time = 300ms - when(mClock.millis()).thenReturn(300L); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - false /* authenticated */, new ArrayList<>()); - mLooper.dispatchAll(); - - mLooper.moveTimeForward(300); - mLooper.dispatchAll(); - verify(mCallback, never()).onClientFinished(any(), anyBoolean()); - - when(mClock.millis()).thenReturn(1300L); - client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */); - - // If code is correct, the new acquired start timestamp should be used - // and the code should only have to wait 500 - (1500-1300)ms. - when(mClock.millis()).thenReturn(1500L); - client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */), - true /* authenticated */, new ArrayList<>()); - mLooper.dispatchAll(); - - mLooper.moveTimeForward(299); - mLooper.dispatchAll(); - verify(mCallback, never()).onClientFinished(any(), anyBoolean()); - - mLooper.moveTimeForward(1); - mLooper.dispatchAll(); - verify(mCallback).onClientFinished(any(), eq(true)); - } - - @Test - public void sideFpsPowerPressCancelsIsntantly() throws Exception { - when(mSensorProps.isAnySidefpsType()).thenReturn(true); - - final FingerprintAuthenticationClient client = createClient(1); - client.start(mCallback); - - client.onPowerPressed(); - mLooper.dispatchAll(); - - verify(mCallback, never()).onClientFinished(any(), eq(true)); - verify(mCallback).onClientFinished(any(), eq(false)); - } - private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java index 86c59379a61e..77e5d1d60cb5 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -51,6 +51,8 @@ import java.nio.file.Path; public final class DisplayDeviceConfigTest { private static final int DEFAULT_PEAK_REFRESH_RATE = 75; private static final int DEFAULT_REFRESH_RATE = 120; + private static final int DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55; + private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95; private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30}; private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21}; private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160}; @@ -150,8 +152,10 @@ public final class DisplayDeviceConfigTest { assertEquals("ProximitySensor123", mDisplayDeviceConfig.getProximitySensor().name); assertEquals("prox_type_1", mDisplayDeviceConfig.getProximitySensor().type); - assertEquals(75, mDisplayDeviceConfig.getDefaultLowRefreshRate()); - assertEquals(90, mDisplayDeviceConfig.getDefaultHighRefreshRate()); + assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()); + assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()); + assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate()); + assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate()); assertArrayEquals(new int[]{45, 55}, mDisplayDeviceConfig.getLowDisplayBrightnessThresholds()); assertArrayEquals(new int[]{50, 60}, @@ -230,8 +234,12 @@ public final class DisplayDeviceConfigTest { mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA); assertArrayEquals(new float[]{29, 30, 31}, mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA); - assertEquals(mDisplayDeviceConfig.getDefaultLowRefreshRate(), DEFAULT_REFRESH_RATE); - assertEquals(mDisplayDeviceConfig.getDefaultHighRefreshRate(), DEFAULT_PEAK_REFRESH_RATE); + assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(), + DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE); + assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(), + DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE); + assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE); + assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE); assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE); assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), @@ -449,6 +457,8 @@ public final class DisplayDeviceConfigTest { + "<type>prox_type_1</type>\n" + "</proxSensor>\n" + "<refreshRate>\n" + + "<defaultRefreshRate>45</defaultRefreshRate>\n" + + "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n" + "<lowerBlockingZoneConfigs>\n" + "<defaultRefreshRate>75</defaultRefreshRate>\n" + "<blockingZoneThreshold>\n" @@ -550,10 +560,14 @@ public final class DisplayDeviceConfigTest { .thenReturn(new int[]{370, 380, 390}); // Configs related to refresh rates and blocking zones - when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate)) + when(mResources.getInteger(R.integer.config_defaultPeakRefreshRate)) .thenReturn(DEFAULT_PEAK_REFRESH_RATE); - when(mResources.getInteger(com.android.internal.R.integer.config_defaultRefreshRate)) + when(mResources.getInteger(R.integer.config_defaultRefreshRate)) .thenReturn(DEFAULT_REFRESH_RATE); + when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone)) + .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE); + when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone)) + .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE); when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate)) .thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE); when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate)) diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index b133a2a44fcf..af39dd44065e 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -1869,6 +1869,10 @@ public class DisplayModeDirectorTest { .thenReturn(75); when(resources.getInteger(R.integer.config_defaultRefreshRate)) .thenReturn(45); + when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone)) + .thenReturn(65); + when(resources.getInteger(R.integer.config_defaultRefreshRateInZone)) + .thenReturn(85); when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate)) .thenReturn(new int[]{5}); when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate)) @@ -1888,6 +1892,8 @@ public class DisplayModeDirectorTest { assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0); assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75, 0.0); + assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65); + assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85); assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(), new int[]{250}); assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(), @@ -1899,17 +1905,21 @@ public class DisplayModeDirectorTest { // Notify that the default display is updated, such that DisplayDeviceConfig has new values DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class); - when(displayDeviceConfig.getDefaultLowRefreshRate()).thenReturn(50); - when(displayDeviceConfig.getDefaultHighRefreshRate()).thenReturn(55); + when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50); + when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55); + when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(60); + when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(65); when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25}); when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30}); when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210}); when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100}); director.defaultDisplayDeviceUpdated(displayDeviceConfig); - assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0); - assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 55, + assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); + assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65, 0.0); + assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55); + assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50); assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(), new int[]{210}); assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(), @@ -1922,6 +1932,8 @@ public class DisplayModeDirectorTest { // Notify that the default display is updated, such that DeviceConfig has new values FakeDeviceConfig config = mInjector.getDeviceConfig(); config.setDefaultPeakRefreshRate(60); + config.setRefreshRateInHighZone(65); + config.setRefreshRateInLowZone(70); config.setLowAmbientBrightnessThresholds(new int[]{20}); config.setLowDisplayBrightnessThresholds(new int[]{10}); config.setHighDisplayBrightnessThresholds(new int[]{255}); @@ -1929,9 +1941,11 @@ public class DisplayModeDirectorTest { director.defaultDisplayDeviceUpdated(displayDeviceConfig); - assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0); + assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60, 0.0); + assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65); + assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 70); assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(), new int[]{255}); assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(), @@ -1971,8 +1985,8 @@ public class DisplayModeDirectorTest { any(Handler.class)); DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class); - when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50); - when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55); + when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50); + when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55); when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25}); when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30}); when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210}); diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java new file mode 100644 index 000000000000..24fc34849829 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java @@ -0,0 +1,57 @@ +/* + * 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.server.display; + +import static org.junit.Assert.assertEquals; + + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class HbmEventTest { + private long mStartTimeMillis; + private long mEndTimeMillis; + private HbmEvent mHbmEvent; + + @Before + public void setUp() { + mStartTimeMillis = 10; + mEndTimeMillis = 20; + mHbmEvent = new HbmEvent(mStartTimeMillis, mEndTimeMillis); + } + + @Test + public void getCorrectValues() { + assertEquals(mHbmEvent.getStartTimeMillis(), mStartTimeMillis); + assertEquals(mHbmEvent.getEndTimeMillis(), mEndTimeMillis); + } + + @Test + public void toStringGeneratesExpectedString() { + String actualString = mHbmEvent.toString(); + String expectedString = "HbmEvent: {startTimeMillis:" + mStartTimeMillis + + ", endTimeMillis: " + mEndTimeMillis + "}, total: " + + ((mEndTimeMillis - mStartTimeMillis) / 1000) + "]"; + assertEquals(actualString, expectedString); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java index 53fa3e2db376..da2e1be00769 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -27,9 +27,7 @@ import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIG import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; import static com.android.server.display.AutomaticBrightnessController .AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE; - import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT; - import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID; import static org.junit.Assert.assertEquals; @@ -102,6 +100,7 @@ public class HighBrightnessModeControllerTest { private Binder mDisplayToken; private String mDisplayUniqueId; private Context mContextSpy; + private HighBrightnessModeMetadata mHighBrightnessModeMetadata; @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); @@ -124,6 +123,7 @@ public class HighBrightnessModeControllerTest { mTestLooper = new TestLooper(mClock::now); mDisplayToken = null; mDisplayUniqueId = "unique_id"; + mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy); when(mContextSpy.getContentResolver()).thenReturn(resolver); @@ -140,7 +140,8 @@ public class HighBrightnessModeControllerTest { initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, - mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy); + mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, + null, mContextSpy); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f); } @@ -150,7 +151,8 @@ public class HighBrightnessModeControllerTest { initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, - mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy); + mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, + null, mContextSpy); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); @@ -705,9 +707,12 @@ public class HighBrightnessModeControllerTest { // Creates instance with standard initialization values. private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) { initHandler(clock); + if (mHighBrightnessModeMetadata == null) { + mHighBrightnessModeMetadata = new HighBrightnessModeMetadata(); + } return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, - DEFAULT_HBM_DATA, null, () -> {}, mContextSpy); + DEFAULT_HBM_DATA, null, () -> {}, mHighBrightnessModeMetadata, mContextSpy); } private void initHandler(OffsettableClock clock) { diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java new file mode 100644 index 000000000000..ede54e096ad0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java @@ -0,0 +1,59 @@ +/* + * 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.server.display; + +import static org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + + +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class HighBrightnessModeMetadataTest { + private HighBrightnessModeMetadata mHighBrightnessModeMetadata; + + private long mRunningStartTimeMillis = -1; + + @Before + public void setUp() { + mHighBrightnessModeMetadata = new HighBrightnessModeMetadata(); + } + + @Test + public void checkDefaultValues() { + assertEquals(mHighBrightnessModeMetadata.getRunningStartTimeMillis(), + mRunningStartTimeMillis); + assertEquals(mHighBrightnessModeMetadata.getHbmEventQueue().size(), 0); + } + + @Test + public void checkSetValues() { + mRunningStartTimeMillis = 10; + mHighBrightnessModeMetadata.setRunningStartTimeMillis(mRunningStartTimeMillis); + assertEquals(mHighBrightnessModeMetadata.getRunningStartTimeMillis(), + mRunningStartTimeMillis); + HbmEvent expectedHbmEvent = new HbmEvent(10, 20); + mHighBrightnessModeMetadata.addHbmEvent(expectedHbmEvent); + HbmEvent actualHbmEvent = mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst(); + assertEquals(expectedHbmEvent.toString(), actualHbmEvent.toString()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 638637d544de..6790ad9a5da1 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -18,6 +18,7 @@ package com.android.server.display; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY_GROUP; +import static android.view.Display.TYPE_EXTERNAL; import static android.view.Display.TYPE_INTERNAL; import static android.view.Display.TYPE_VIRTUAL; @@ -173,7 +174,7 @@ public class LogicalDisplayMapperTest { @Test public void testDisplayDeviceAddAndRemove_NonInternalTypes() { - testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL); + testDisplayDeviceAddAndRemove_NonInternal(TYPE_EXTERNAL); testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI); testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY); testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL); @@ -218,7 +219,7 @@ public class LogicalDisplayMapperTest { @Test public void testDisplayDeviceAddAndRemove_OneExternalDefault() { - DisplayDevice device = createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, + DisplayDevice device = createDisplayDevice(TYPE_EXTERNAL, 600, 800, FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); // add @@ -268,7 +269,7 @@ public class LogicalDisplayMapperTest { public void testGetDisplayIdsLocked() { add(createDisplayDevice(TYPE_INTERNAL, 600, 800, FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY)); - add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0)); + add(createDisplayDevice(TYPE_EXTERNAL, 600, 800, 0)); add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0)); int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, @@ -460,7 +461,7 @@ public class LogicalDisplayMapperTest { Layout layout = new Layout(); layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true); - layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false); + layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, true); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); layout = new Layout(); @@ -469,6 +470,8 @@ public class LogicalDisplayMapperTest { when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout); when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout); + when(mDeviceStateToLayoutMapSpy.size()).thenReturn(4); + LogicalDisplay display1 = add(device1); assertEquals(info(display1).address, info(device1).address); assertEquals(DEFAULT_DISPLAY, id(display1)); @@ -481,8 +484,15 @@ public class LogicalDisplayMapperTest { mLogicalDisplayMapper.setDeviceStateLocked(0, false); mLooper.moveTimeForward(1000); mLooper.dispatchAll(); + // The new state is not applied until the boot is completed assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); + + mLogicalDisplayMapper.onBootCompleted(); + mLooper.moveTimeForward(1000); + mLooper.dispatchAll(); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); + assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked()); assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); @@ -623,6 +633,23 @@ public class LogicalDisplayMapperTest { assertEquals(3, threeDisplaysEnabled.length); } + @Test + public void testCreateNewLogicalDisplay() { + DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800, + FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1); + LogicalDisplay display1 = add(device1); + + assertTrue(display1.isEnabledLocked()); + + DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800, + FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2); + LogicalDisplay display2 = add(device2); + + assertFalse(display2.isEnabledLocked()); + } + ///////////////// // Helper Methods ///////////////// diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 66c3f0730404..b12e6ad563d1 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -1678,7 +1678,7 @@ public final class DataManagerTest { mParentNotificationChannel.getImportance(), null, null, mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null, - false, false, false, null, 0, false); + false, false, false, null, 0, false, 0); return true; }).when(mRankingMap).getRanking(eq(GENERIC_KEY), any(NotificationListenerService.Ranking.class)); @@ -1704,7 +1704,7 @@ public final class DataManagerTest { mNotificationChannel.getImportance(), null, null, mNotificationChannel, null, null, true, 0, false, -1, false, null, null, false, - false, false, null, 0, false); + false, false, null, 0, false, 0); return true; }).when(mRankingMap).getRanking(eq(CUSTOM_KEY), any(NotificationListenerService.Ranking.class)); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 12cd834d1d66..8a99c2cdcc6f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -193,7 +193,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { tweak.isConversation(), tweak.getConversationShortcutInfo(), tweak.getRankingAdjustment(), - tweak.isBubble() + tweak.isBubble(), + tweak.getProposedImportance() ); assertNotEquals(nru, nru2); } @@ -274,7 +275,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { isConversation(i), getShortcutInfo(i), getRankingAdjustment(i), - isBubble(i) + isBubble(i), + getProposedImportance(i) ); rankings[i] = ranking; } @@ -402,6 +404,10 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { return index % 3 - 1; } + private int getProposedImportance(int index) { + return index % 5 - 1; + } + private boolean isBubble(int index) { return index % 4 == 0; } @@ -443,6 +449,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { assertEquals(comment, a.getConversationShortcutInfo().getId(), b.getConversationShortcutInfo().getId()); assertActionsEqual(a.getSmartActions(), b.getSmartActions()); + assertEquals(a.getProposedImportance(), b.getProposedImportance()); } private void detailedAssertEquals(RankingMap a, RankingMap b) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java new file mode 100644 index 000000000000..87e86cb00f56 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.PendingIntent; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.UserHandle; +import android.service.notification.Adjustment; +import android.service.notification.SnoozeCriterion; +import android.service.notification.StatusBarNotification; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Objects; + +public class NotificationRecordExtractorDataTest extends UiServiceTestCase { + + @Test + public void testHasDiffs_noDiffs() { + NotificationRecord r = generateRecord(); + + NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData( + 1, + r.getPackageVisibilityOverride(), + r.canShowBadge(), + r.canBubble(), + r.getNotification().isBubbleNotification(), + r.getChannel(), + r.getGroupKey(), + r.getPeopleOverride(), + r.getSnoozeCriteria(), + r.getUserSentiment(), + r.getSuppressedVisualEffects(), + r.getSystemGeneratedSmartActions(), + r.getSmartReplies(), + r.getImportance(), + r.getRankingScore(), + r.isConversation(), + r.getProposedImportance()); + + assertFalse(extractorData.hasDiffForRankingLocked(r, 1)); + assertFalse(extractorData.hasDiffForLoggingLocked(r, 1)); + } + + @Test + public void testHasDiffs_proposedImportanceChange() { + NotificationRecord r = generateRecord(); + + NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData( + 1, + r.getPackageVisibilityOverride(), + r.canShowBadge(), + r.canBubble(), + r.getNotification().isBubbleNotification(), + r.getChannel(), + r.getGroupKey(), + r.getPeopleOverride(), + r.getSnoozeCriteria(), + r.getUserSentiment(), + r.getSuppressedVisualEffects(), + r.getSystemGeneratedSmartActions(), + r.getSmartReplies(), + r.getImportance(), + r.getRankingScore(), + r.isConversation(), + r.getProposedImportance()); + + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_HIGH); + Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0); + r.addAdjustment(adjustment); + r.applyAdjustments(); + + assertTrue(extractorData.hasDiffForRankingLocked(r, 1)); + assertTrue(extractorData.hasDiffForLoggingLocked(r, 1)); + } + + private NotificationRecord generateRecord() { + NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); + final Notification.Builder builder = new Notification.Builder(getContext()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + Notification n = builder.build(); + StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0, + 0, n, UserHandle.ALL, null, System.currentTimeMillis()); + return new NotificationRecord(getContext(), sbn, channel); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 5468220d9564..14b004827ece 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -19,6 +19,7 @@ import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -755,6 +756,24 @@ public class NotificationRecordTest extends UiServiceTestCase { } @Test + public void testProposedImportance() { + StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, + true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, + false /* lights */, false /* defaultLights */, groupId /* group */); + NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel); + + assertEquals(IMPORTANCE_UNSPECIFIED, record.getProposedImportance()); + + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_DEFAULT); + record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId())); + + record.applyAdjustments(); + + assertEquals(IMPORTANCE_DEFAULT, record.getProposedImportance()); + } + + @Test public void testAppImportance_returnsCorrectly() { StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */, true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 598a22bbde39..770feaba9303 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -1703,6 +1703,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); channel.setShowBadge(true); channel.setAllowBubbles(false); + channel.setImportantConversation(true); int lockMask = 0; for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) { lockMask |= NotificationChannel.LOCKABLE_FIELDS[i]; @@ -1718,6 +1719,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights()); assertFalse(savedChannel.canBypassDnd()); assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility()); + assertFalse(channel.isImportantConversation()); assertEquals(channel.canShowBadge(), savedChannel.canShowBadge()); assertEquals(channel.canBubble(), savedChannel.canBubble()); @@ -4396,7 +4398,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT); channel2.setConversationId(calls.getId(), convoId); channel2.setImportantConversation(true); - mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false); + mHelper.createNotificationChannel(PKG_O, UID_O, channel2, false, false); List<ConversationChannelWrapper> convos = mHelper.getConversations(IntArray.wrap(new int[] {0}), false); @@ -4473,7 +4475,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT); channel2.setConversationId(calls.getId(), convoId); channel2.setImportantConversation(true); - mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false); + mHelper.createNotificationChannel(PKG_O, UID_O, channel2, false, false); List<ConversationChannelWrapper> convos = mHelper.getConversations(IntArray.wrap(new int[] {0}), false); @@ -4501,13 +4503,13 @@ public class PreferencesHelperTest extends UiServiceTestCase { new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT); channel.setConversationId(messages.getId(), convoId); channel.setImportantConversation(true); - mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, false, false); NotificationChannel diffConvo = new NotificationChannel("B person msgs", "messages from B", IMPORTANCE_DEFAULT); diffConvo.setConversationId(p.getId(), "different convo"); diffConvo.setImportantConversation(true); - mHelper.createNotificationChannel(PKG_P, UID_P, diffConvo, true, false); + mHelper.createNotificationChannel(PKG_P, UID_P, diffConvo, false, false); NotificationChannel channel2 = new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT); @@ -4534,7 +4536,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT); channel.setConversationId(messages.getId(), convoId); channel.setImportantConversation(true); - mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, false, false); mHelper.permanentlyDeleteNotificationChannel(PKG_O, UID_O, "messages"); @@ -4935,7 +4937,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { "conversation", IMPORTANCE_DEFAULT); friend.setConversationId(parent.getId(), "friend"); friend.setImportantConversation(true); - mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); + mHelper.createNotificationChannel(PKG_O, UID_O, friend, false, false); ArrayList<StatsEvent> events = new ArrayList<>(); mHelper.pullPackageChannelPreferencesStats(events); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index e30e5dbcaf46..74ea7d7687ed 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -40,7 +40,7 @@ import android.hardware.HardwareBuffer; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.view.WindowManager; -import android.window.BackEvent; +import android.window.BackMotionEvent; import android.window.BackNavigationInfo; import android.window.IOnBackInvokedCallback; import android.window.OnBackInvokedCallback; @@ -242,11 +242,11 @@ public class BackNavigationControllerTests extends WindowTestsBase { private IOnBackInvokedCallback createOnBackInvokedCallback() { return new IOnBackInvokedCallback.Stub() { @Override - public void onBackStarted(BackEvent backEvent) { + public void onBackStarted(BackMotionEvent backEvent) { } @Override - public void onBackProgressed(BackEvent backEvent) { + public void onBackProgressed(BackMotionEvent backEvent) { } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index 8bb79e3f7ddc..45b30b204801 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -155,6 +155,18 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { } @Test + public void testTreatmentDisabledPerApp_noForceRotationOrRefresh() + throws Exception { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + when(mActivity.mLetterboxUiController.shouldForceRotateForCameraCompat()) + .thenReturn(false); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + assertNoForceRotationOrRefresh(); + } + + @Test public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mDisplayContent); @@ -327,7 +339,21 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { } @Test - public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception { + public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() + throws Exception { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()) + .thenReturn(false); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); + + assertActivityRefreshRequested(/* refreshRequested */ false); + } + + @Test + public void testOnActivityConfigurationChanging_refreshDisabledPerApp_noRefresh() + throws Exception { when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false); configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -362,6 +388,19 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); } + @Test + public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() + throws Exception { + configureActivity(SCREEN_ORIENTATION_PORTRAIT); + when(mActivity.mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat()) + .thenReturn(true); + + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); + + assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + } + private void configureActivity(@ScreenOrientation int activityOrientation) { configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT); } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 6d778afee88c..5e087f06b36b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -16,8 +16,14 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; +import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; +import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -74,6 +80,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mController = new LetterboxUiController(mWm, mActivity); } + // shouldIgnoreRequestedOrientation + @Test @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() { @@ -134,7 +142,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() { prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); doReturn(false).when(mLetterboxConfiguration) @@ -143,6 +151,163 @@ public class LetterboxUiControllerTest extends WindowTestsBase { assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); } + // shouldRefreshActivityForCameraCompat + + @Test + public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() { + doReturn(false).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + + assertFalse(mController.shouldRefreshActivityForCameraCompat()); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) + public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + + assertFalse(mController.shouldRefreshActivityForCameraCompat()); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) + public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldRefreshActivityForCameraCompat()); + } + + @Test + public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldRefreshActivityForCameraCompat()); + } + + @Test + public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertTrue(mController.shouldRefreshActivityForCameraCompat()); + } + + // shouldRefreshActivityViaPauseForCameraCompat + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE}) + public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() { + doReturn(false).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + + assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat()); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE}) + public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + + assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat()); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE}) + public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsFalseAndOverride_returnFalse() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat()); + } + + @Test + public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat()); + } + + // shouldForceRotateForCameraCompat + + @Test + public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() { + doReturn(false).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + + assertFalse(mController.shouldForceRotateForCameraCompat()); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION}) + public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + + assertFalse(mController.shouldForceRotateForCameraCompat()); + } + + @Test + @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION}) + public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldForceRotateForCameraCompat()); + } + + @Test + public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldForceRotateForCameraCompat()); + } + + @Test + public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true); + mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertTrue(mController.shouldForceRotateForCameraCompat()); + } + private void mockThatProperty(String propertyName, boolean value) throws Exception { Property property = new Property(propertyName, /* value */ value, /* packageName */ "", /* className */ ""); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index adf694c2a88d..db6ac0b432b3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -30,6 +30,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.os.Process.NOBODY_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -1220,20 +1221,35 @@ public class RecentTasksTest extends WindowTestsBase { @Test public void testCreateRecentTaskInfo_detachedTask() { - final Task task = createTaskBuilder(".Task").setCreateActivity(true).build(); + final Task task = createTaskBuilder(".Task").build(); + final ComponentName componentName = getUniqueComponentName(); + new ActivityBuilder(mSupervisor.mService) + .setTask(task) + .setUid(NOBODY_UID) + .setComponent(componentName) + .build(); final TaskDisplayArea tda = task.getDisplayArea(); assertTrue(task.isAttached()); assertTrue(task.supportsMultiWindow()); - RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true); + RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + true /* getTasksAllowed */); assertTrue(info.supportsMultiWindow); + info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + false /* getTasksAllowed */); + + assertFalse(info.topActivity.equals(componentName)); + assertFalse(info.topActivityInfo.packageName.equals(componentName.getPackageName())); + assertFalse(info.baseActivity.equals(componentName)); + // The task can be put in split screen even if it is not attached now. task.removeImmediately(); - info = mRecentTasks.createRecentTaskInfo(task, true); + info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + true /* getTasksAllowed */); assertTrue(info.supportsMultiWindow); @@ -1242,7 +1258,8 @@ public class RecentTasksTest extends WindowTestsBase { doReturn(false).when(tda).supportsNonResizableMultiWindow(); doReturn(false).when(task).isResizeable(); - info = mRecentTasks.createRecentTaskInfo(task, true); + info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + true /* getTasksAllowed */); assertFalse(info.supportsMultiWindow); @@ -1250,7 +1267,8 @@ public class RecentTasksTest extends WindowTestsBase { // the device supports it. doReturn(true).when(tda).supportsNonResizableMultiWindow(); - info = mRecentTasks.createRecentTaskInfo(task, true); + info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */, + true /* getTasksAllowed */); assertTrue(info.supportsMultiWindow); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 2420efc63b80..6b3425cf095c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -796,6 +796,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testApplyTransaction_createTaskFragment_withPairedActivityToken() { + final Task task = createTask(mDisplayContent); + final ActivityRecord activityAtBottom = createActivityRecord(task); + final int uid = Binder.getCallingUid(); + activityAtBottom.info.applicationInfo.uid = uid; + activityAtBottom.getTask().effectiveUid = uid; + mTaskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setFragmentToken(mFragmentToken) + .createActivityCount(1) + .build(); + mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); + final IBinder fragmentToken1 = new Binder(); + final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( + mOrganizerToken, fragmentToken1, activityAtBottom.token) + .setPairedActivityToken(activityAtBottom.token) + .build(); + mTransaction.setTaskFragmentOrganizer(mIOrganizer); + mTransaction.createTaskFragment(params); + assertApplyTransactionAllowed(mTransaction); + + // Successfully created a TaskFragment. + final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment( + fragmentToken1); + assertNotNull(taskFragment); + // The new TaskFragment should be positioned right above the paired activity. + assertEquals(task.mChildren.indexOf(activityAtBottom) + 1, + task.mChildren.indexOf(taskFragment)); + // The top TaskFragment should remain on top. + assertEquals(task.mChildren.indexOf(taskFragment) + 1, + task.mChildren.indexOf(mTaskFragment)); + } + + @Test public void testApplyTransaction_enforceHierarchyChange_reparentChildren() { doReturn(true).when(mTaskFragment).isAttached(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 06a79f47de55..1407cdd8600c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -391,7 +391,7 @@ public class WallpaperControllerTests extends WindowTestsBase { dc.updateOrientation(); dc.sendNewConfiguration(); spyOn(wallpaperWindow); - doReturn(new Rect(0, 0, width, height)).when(wallpaperWindow).getLastReportedBounds(); + doReturn(new Rect(0, 0, width, height)).when(wallpaperWindow).getParentFrame(); } @Test 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 0568f2acf366..3556ded23351 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -42,6 +42,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; @@ -969,6 +970,19 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); } + @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD}) + @Test + public void testNeedsRelativeLayeringToIme_systemDialog() { + WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY, + mDisplayContent, + "SystemDialog", true); + mDisplayContent.setImeLayeringTarget(mAppWindow); + mAppWindow.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + makeWindowVisible(mImeWindow); + systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; + assertTrue(systemDialogWindow.needsRelativeLayeringToIme()); + } + @Test public void testSetFreezeInsetsState() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 77fca451547d..7959d82ae22f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -31,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; @@ -543,4 +545,28 @@ public class ZOrderingTests extends WindowTestsBase { assertZOrderGreaterThan(mTransaction, popupWindow.getSurfaceControl(), mDisplayContent.getImeContainer().getSurfaceControl()); } + + @Test + public void testSystemDialogWindow_expectHigherThanIme_inMultiWindow() { + // Simulate the app window is in multi windowing mode and being IME target + mAppWindow.getConfiguration().windowConfiguration.setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW); + mDisplayContent.setImeLayeringTarget(mAppWindow); + mDisplayContent.setImeInputTarget(mAppWindow); + makeWindowVisible(mImeWindow); + + // Create a popupWindow + final WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY, + mDisplayContent, "SystemDialog", true); + systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; + spyOn(systemDialogWindow); + + mDisplayContent.assignChildLayers(mTransaction); + + // Verify the surface layer of the popupWindow should higher than IME + verify(systemDialogWindow).needsRelativeLayeringToIme(); + assertThat(systemDialogWindow.needsRelativeLayeringToIme()).isTrue(); + assertZOrderGreaterThan(mTransaction, systemDialogWindow.getSurfaceControl(), + mDisplayContent.getImeContainer().getSurfaceControl()); + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 55bf2ab3a0e9..1c57ba572e75 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -55,6 +55,7 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1152,6 +1153,11 @@ final class HotwordDetectionConnection { Slog.w(TAG, "Failed to report onError status: " + e); } } + // Can improve to log exit reason if needed + HotwordMetricsLogger.writeKeyphraseTriggerEvent( + mDetectorType, + HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH, + mVoiceInteractionServiceUid); } @Override diff --git a/telephony/java/android/telephony/AccessNetworkUtils.java b/telephony/java/android/telephony/AccessNetworkUtils.java index b5d97abdd3eb..c2a986465b02 100644 --- a/telephony/java/android/telephony/AccessNetworkUtils.java +++ b/telephony/java/android/telephony/AccessNetworkUtils.java @@ -4,8 +4,8 @@ import static android.telephony.ServiceState.DUPLEX_MODE_FDD; import static android.telephony.ServiceState.DUPLEX_MODE_TDD; import static android.telephony.ServiceState.DUPLEX_MODE_UNKNOWN; -import android.telephony.AccessNetworkConstants.EutranBandArfcnFrequency; import android.telephony.AccessNetworkConstants.EutranBand; +import android.telephony.AccessNetworkConstants.EutranBandArfcnFrequency; import android.telephony.AccessNetworkConstants.GeranBand; import android.telephony.AccessNetworkConstants.GeranBandArfcnFrequency; import android.telephony.AccessNetworkConstants.NgranArfcnFrequency; @@ -13,7 +13,6 @@ import android.telephony.AccessNetworkConstants.NgranBands; import android.telephony.AccessNetworkConstants.UtranBand; import android.telephony.AccessNetworkConstants.UtranBandArfcnFrequency; import android.telephony.ServiceState.DuplexMode; -import android.util.Log; import java.util.Arrays; import java.util.HashSet; @@ -232,6 +231,108 @@ public class AccessNetworkUtils { } /** + * Gets the NR Operating band for a given downlink NRARFCN. + * + * <p>See 3GPP TS 38.104 Table 5.2-1 NR operating bands in FR1 and + * Table 5.2-2 NR operating bands in FR2 + * + * @param nrarfcn The downlink NRARFCN + * @return Operating band number, or {@link #INVALID_BAND} if no corresponding band exists + */ + public static int getOperatingBandForNrarfcn(int nrarfcn) { + if (nrarfcn >= 422000 && nrarfcn <= 434000) { + return NgranBands.BAND_1; + } else if (nrarfcn >= 386000 && nrarfcn <= 398000) { + return NgranBands.BAND_2; + } else if (nrarfcn >= 361000 && nrarfcn <= 376000) { + return NgranBands.BAND_3; + } else if (nrarfcn >= 173800 && nrarfcn <= 178800) { + return NgranBands.BAND_5; + } else if (nrarfcn >= 524000 && nrarfcn <= 538000) { + return NgranBands.BAND_7; + } else if (nrarfcn >= 185000 && nrarfcn <= 192000) { + return NgranBands.BAND_8; + } else if (nrarfcn >= 145800 && nrarfcn <= 149200) { + return NgranBands.BAND_12; + } else if (nrarfcn >= 151600 && nrarfcn <= 153600) { + return NgranBands.BAND_14; + } else if (nrarfcn >= 172000 && nrarfcn <= 175000) { + return NgranBands.BAND_18; + } else if (nrarfcn >= 158200 && nrarfcn <= 164200) { + return NgranBands.BAND_20; + } else if (nrarfcn >= 386000 && nrarfcn <= 399000) { + return NgranBands.BAND_25; + } else if (nrarfcn >= 171800 && nrarfcn <= 178800) { + return NgranBands.BAND_26; + } else if (nrarfcn >= 151600 && nrarfcn <= 160600) { + return NgranBands.BAND_28; + } else if (nrarfcn >= 143400 && nrarfcn <= 145600) { + return NgranBands.BAND_29; + } else if (nrarfcn >= 470000 && nrarfcn <= 472000) { + return NgranBands.BAND_30; + } else if (nrarfcn >= 402000 && nrarfcn <= 405000) { + return NgranBands.BAND_34; + } else if (nrarfcn >= 514000 && nrarfcn <= 524000) { + return NgranBands.BAND_38; + } else if (nrarfcn >= 376000 && nrarfcn <= 384000) { + return NgranBands.BAND_39; + } else if (nrarfcn >= 460000 && nrarfcn <= 480000) { + return NgranBands.BAND_40; + } else if (nrarfcn >= 499200 && nrarfcn <= 537999) { + return NgranBands.BAND_41; + } else if (nrarfcn >= 743334 && nrarfcn <= 795000) { + return NgranBands.BAND_46; + } else if (nrarfcn >= 636667 && nrarfcn <= 646666) { + return NgranBands.BAND_48; + } else if (nrarfcn >= 286400 && nrarfcn <= 303400) { + return NgranBands.BAND_50; + } else if (nrarfcn >= 285400 && nrarfcn <= 286400) { + return NgranBands.BAND_51; + } else if (nrarfcn >= 496700 && nrarfcn <= 499000) { + return NgranBands.BAND_53; + } else if (nrarfcn >= 422000 && nrarfcn <= 440000) { + return NgranBands.BAND_65; // BAND_66 has the same channels + } else if (nrarfcn >= 399000 && nrarfcn <= 404000) { + return NgranBands.BAND_70; + } else if (nrarfcn >= 123400 && nrarfcn <= 130400) { + return NgranBands.BAND_71; + } else if (nrarfcn >= 295000 && nrarfcn <= 303600) { + return NgranBands.BAND_74; + } else if (nrarfcn >= 286400 && nrarfcn <= 303400) { + return NgranBands.BAND_75; + } else if (nrarfcn >= 285400 && nrarfcn <= 286400) { + return NgranBands.BAND_76; + } else if (nrarfcn >= 620000 && nrarfcn <= 680000) { + return NgranBands.BAND_77; + } else if (nrarfcn >= 620000 && nrarfcn <= 653333) { + return NgranBands.BAND_78; + } else if (nrarfcn >= 693334 && nrarfcn <= 733333) { + return NgranBands.BAND_79; + } else if (nrarfcn >= 499200 && nrarfcn <= 538000) { + return NgranBands.BAND_90; + } else if (nrarfcn >= 285400 && nrarfcn <= 286400) { + return NgranBands.BAND_91; + } else if (nrarfcn >= 286400 && nrarfcn <= 303400) { + return NgranBands.BAND_92; + } else if (nrarfcn >= 285400 && nrarfcn <= 286400) { + return NgranBands.BAND_93; + } else if (nrarfcn >= 286400 && nrarfcn <= 303400) { + return NgranBands.BAND_94; + } else if (nrarfcn >= 795000 && nrarfcn <= 875000) { + return NgranBands.BAND_96; + } else if (nrarfcn >= 2054166 && nrarfcn <= 2104165) { + return NgranBands.BAND_257; + } else if (nrarfcn >= 2016667 && nrarfcn <= 2070832) { + return NgranBands.BAND_258; + } else if (nrarfcn >= 2229166 && nrarfcn <= 2279165) { + return NgranBands.BAND_260; + } else if (nrarfcn >= 2070833 && nrarfcn <= 2084999) { + return NgranBands.BAND_261; + } + return INVALID_BAND; + } + + /** * Gets the GERAN Operating band for a given ARFCN. * * <p>See 3GPP TS 45.005 clause 2 for calculation. diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java index 326f4171de3b..65c2146b4819 100644 --- a/telephony/java/android/telephony/NetworkScanRequest.java +++ b/telephony/java/android/telephony/NetworkScanRequest.java @@ -26,7 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; /** - * Defines a request to peform a network scan. + * Defines a request to perform a network scan. * * This class defines whether the network scan will be performed only once or periodically until * cancelled, when the scan is performed periodically, the time interval is not controlled by the diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index 28307d270134..4836c9f8610a 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -1031,6 +1031,15 @@ public class SmsMessage { } /** + * Check if format of the message is 3GPP. + * + * @hide + */ + public boolean is3gpp() { + return (mWrappedSmsMessage instanceof com.android.internal.telephony.gsm.SmsMessage); + } + + /** * Determines whether or not to use CDMA format for MO SMS. * If SMS over IMS is supported, then format is based on IMS SMS format, * otherwise format is based on current phone type. |