diff options
345 files changed, 6699 insertions, 2099 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 8e09939f6805..4a4ba6371939 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1128,6 +1128,9 @@ public class DevicePolicyManager { * <p>Use only for device owner provisioning. This extra can be returned by the admin app when * performing the admin-integrated provisioning flow as a result of the {@link * #ACTION_GET_PROVISIONING_MODE} activity. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE"; @@ -1139,6 +1142,9 @@ public class DevicePolicyManager { * <p>Use only for device owner provisioning. This extra can be returned by the admin app when * performing the admin-integrated provisioning flow as a result of the {@link * #ACTION_GET_PROVISIONING_MODE} activity. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME"; @@ -1150,6 +1156,9 @@ public class DevicePolicyManager { * <p>Use only for device owner provisioning. This extra can be returned by the admin app when * performing the admin-integrated provisioning flow as a result of the {@link * #ACTION_GET_PROVISIONING_MODE} activity. + * + * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE"; @@ -1159,7 +1168,7 @@ public class DevicePolicyManager { * owner provisioning for downloading the mobile device management application. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID"; @@ -1169,7 +1178,7 @@ public class DevicePolicyManager { * is hidden or not. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN"; @@ -1180,7 +1189,7 @@ public class DevicePolicyManager { * {@code WEP} or {@code EAP}. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE"; @@ -1190,7 +1199,7 @@ public class DevicePolicyManager { * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_WIFI_PASSWORD = "android.app.extra.PROVISIONING_WIFI_PASSWORD"; @@ -1281,7 +1290,7 @@ public class DevicePolicyManager { * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_WIFI_PROXY_HOST = "android.app.extra.PROVISIONING_WIFI_PROXY_HOST"; @@ -1291,7 +1300,7 @@ public class DevicePolicyManager { * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_WIFI_PROXY_PORT = "android.app.extra.PROVISIONING_WIFI_PROXY_PORT"; @@ -1301,7 +1310,7 @@ public class DevicePolicyManager { * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_WIFI_PROXY_BYPASS = "android.app.extra.PROVISIONING_WIFI_PROXY_BYPASS"; @@ -1311,7 +1320,7 @@ public class DevicePolicyManager { * {@link #EXTRA_PROVISIONING_WIFI_SSID}. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_WIFI_PAC_URL = "android.app.extra.PROVISIONING_WIFI_PAC_URL"; @@ -1321,7 +1330,7 @@ public class DevicePolicyManager { * package. When not provided it is assumed that the device admin package is already installed. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION"; @@ -1401,7 +1410,7 @@ public class DevicePolicyManager { * installed package is less than this version code. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE = "android.app.extra.PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE"; @@ -1411,7 +1420,7 @@ public class DevicePolicyManager { * url specified in {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER"; @@ -1426,7 +1435,7 @@ public class DevicePolicyManager { * be asked to factory reset the device. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. * * <p><strong>Note:</strong> for devices running {@link android.os.Build.VERSION_CODES#LOLLIPOP} * and {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} only SHA-1 hash is supported. @@ -1472,7 +1481,7 @@ public class DevicePolicyManager { * the user will be asked to factory reset the device. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner - * provisioning via an NFC bump. + * provisioning via an NFC bump. It can also be used for QR code provisioning. */ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM"; 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/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/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/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/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 43d427db2c75..4c49f2c049ee 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -169,6 +169,9 @@ public interface WindowManagerPolicyConstants { case PowerManager.WAKE_REASON_POWER_BUTTON: case PowerManager.WAKE_REASON_PLUGGED_IN: case PowerManager.WAKE_REASON_GESTURE: + case PowerManager.WAKE_REASON_TAP: + case PowerManager.WAKE_REASON_LIFT: + case PowerManager.WAKE_REASON_BIOMETRIC: case PowerManager.WAKE_REASON_CAMERA_LAUNCH: case PowerManager.WAKE_REASON_WAKE_KEY: case PowerManager.WAKE_REASON_WAKE_MOTION: 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/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java index 9152e7837b82..a0bd7f70ca58 100644 --- a/core/java/android/window/ImeOnBackInvokedDispatcher.java +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -220,16 +220,14 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc * @param previous the previously focused {@link ViewRootImpl}. * @param current the currently focused {@link ViewRootImpl}. */ - // TODO(b/232845902): Add CTS to test IME back behavior when there's root view change while - // IME is up. public void switchRootView(ViewRootImpl previous, ViewRootImpl current) { for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) { if (previous != null) { previous.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(imeCallback); } if (current != null) { - current.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( - imeCallback.mPriority, imeCallback); + current.getOnBackInvokedDispatcher().registerOnBackInvokedCallbackUnchecked( + imeCallback, imeCallback.mPriority); } } } 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/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index eece25d707c5..ea61feaab93a 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -327,7 +327,7 @@ <string name="permgrouplab_phone" msgid="570318944091926620">"Telefon"</string> <string name="permgroupdesc_phone" msgid="270048070781478204">"uskutečňování a spravování telefonních hovorů"</string> <string name="permgrouplab_sensors" msgid="9134046949784064495">"Tělesné senzory"</string> - <string name="permgroupdesc_sensors" msgid="2610631290633747752">"přístup k datům ze snímačů vašich životních funkcí"</string> + <string name="permgroupdesc_sensors" msgid="2610631290633747752">"přístup k datům ze senzorů vašich životních funkcí"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Oznámení"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"zobrazovat oznámení"</string> <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Načítat obsah oken"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 64d390e093f1..876d3a17aae3 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1681,13 +1681,13 @@ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Verknüpfung für Bedienungshilfen aktivieren?"</string> <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Wenn du beide Lautstärketasten einige Sekunden lang gedrückt hältst, aktivierst du die Bedienungshilfen. Dadurch kann sich die Funktionsweise deines Geräts ändern.\n\nAktuelle Funktionen:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kannst ausgewählte Funktionen unter \"Einstellungen\" > \"Bedienungshilfen\" ändern."</string> <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string> - <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Verknüpfung für <xliff:g id="SERVICE">%1$s</xliff:g> aktivieren?"</string> + <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Kurzbefehl für <xliff:g id="SERVICE">%1$s</xliff:g> aktivieren?"</string> <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Wenn du beide Lautstärketasten einige Sekunden lang gedrückt hältst, aktivierst du die Bedienungshilfe \"<xliff:g id="SERVICE">%1$s</xliff:g>\". Dadurch kann sich die Funktionsweise deines Geräts ändern.\n\nUnter \"Einstellungen > \"Bedienungshilfen\" kannst du dieser Verknüpfung eine andere Funktion zuweisen."</string> <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivieren"</string> <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nicht aktivieren"</string> <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"AN"</string> <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"AUS"</string> - <string name="accessibility_enable_service_title" msgid="3931558336268541484">"<xliff:g id="SERVICE">%1$s</xliff:g> die vollständige Kontrolle über dein Gerät geben?"</string> + <string name="accessibility_enable_service_title" msgid="3931558336268541484">"„<xliff:g id="SERVICE">%1$s</xliff:g>“ die vollständige Kontrolle über dein Gerät geben?"</string> <string name="accessibility_service_warning_description" msgid="291674995220940133">"Die vollständige Kontrolle sollte nur für Apps aktiviert werden, die dir Zugang zu App-Funktionen erleichtern. Das ist in der Regel nur ein kleiner Teil der Apps."</string> <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Bildschirm aufrufen und steuern"</string> <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Die Funktion kann alle Inhalte auf dem Bildschirm lesen und diese Inhalte über andere Apps anzeigen."</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index c4fe972a0b26..c81bf0034a60 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -299,11 +299,11 @@ <string name="user_owner_label" msgid="8628726904184471211">"Vaihda henkilökohtaiseen profiiliin"</string> <string name="managed_profile_label" msgid="7316778766973512382">"Vaihda työprofiiliin"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"Yhteystiedot"</string> - <string name="permgroupdesc_contacts" msgid="9163927941244182567">"käyttää yhteystietoja"</string> + <string name="permgroupdesc_contacts" msgid="9163927941244182567">"pääsy yhteystietoihin"</string> <string name="permgrouplab_location" msgid="1858277002233964394">"Sijainti"</string> - <string name="permgroupdesc_location" msgid="1995955142118450685">"käyttää laitteen sijaintia"</string> + <string name="permgroupdesc_location" msgid="1995955142118450685">"pääsy laitteen sijaintiin"</string> <string name="permgrouplab_calendar" msgid="6426860926123033230">"Kalenteri"</string> - <string name="permgroupdesc_calendar" msgid="6762751063361489379">"käyttää kalenteria"</string> + <string name="permgroupdesc_calendar" msgid="6762751063361489379">"pääsy kalenteriin"</string> <string name="permgrouplab_sms" msgid="795737735126084874">"Tekstiviestit"</string> <string name="permgroupdesc_sms" msgid="5726462398070064542">"lähettää ja tarkastella tekstiviestejä"</string> <string name="permgrouplab_storage" msgid="17339216290379241">"Tiedostot"</string> @@ -313,7 +313,7 @@ <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Valokuvat ja videot"</string> <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pääsy laitteen kuviin ja videoihin"</string> <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofoni"</string> - <string name="permgroupdesc_microphone" msgid="1047786732792487722">"tallentaa ääntä"</string> + <string name="permgroupdesc_microphone" msgid="1047786732792487722">"tallentaa audiota"</string> <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Liikkuminen"</string> <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"nähdä liikkumistietosi"</string> <string name="permgrouplab_camera" msgid="9090413408963547706">"Kamera"</string> @@ -325,7 +325,7 @@ <string name="permgrouplab_phone" msgid="570318944091926620">"Puhelin"</string> <string name="permgroupdesc_phone" msgid="270048070781478204">"soittaa ja hallinnoida puheluita"</string> <string name="permgrouplab_sensors" msgid="9134046949784064495">"Kehon anturit"</string> - <string name="permgroupdesc_sensors" msgid="2610631290633747752">"käyttää anturitietoja elintoiminnoistasi"</string> + <string name="permgroupdesc_sensors" msgid="2610631290633747752">"pääsy anturidataan elintoiminnoistasi"</string> <string name="permgrouplab_notifications" msgid="5472972361980668884">"Ilmoitukset"</string> <string name="permgroupdesc_notifications" msgid="4608679556801506580">"näyttää ilmoituksia"</string> <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Noutaa ikkunan sisältöä"</string> @@ -448,7 +448,7 @@ <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Tällä sovelluksella on pääsy sijaintitietoihin milloin tahansa, myös silloin kun sovellusta ei käytetä."</string> <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"muuta ääniasetuksia"</string> <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Antaa sovelluksen muokata yleisiä ääniasetuksia, kuten äänenvoimakkuutta ja käytettävää kaiutinta."</string> - <string name="permlab_recordAudio" msgid="1208457423054219147">"tallentaa ääntä"</string> + <string name="permlab_recordAudio" msgid="1208457423054219147">"tallentaa audiota"</string> <string name="permdesc_recordAudio" msgid="5857246765327514062">"Tämä sovellus voi tallentaa mikrofonilla audiota, kun sovellusta käytetään."</string> <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"tallentaa audiota taustalla"</string> <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Tämä sovellus voi tallentaa mikrofonilla audiota koska tahansa."</string> @@ -1173,7 +1173,7 @@ <string name="selected" msgid="6614607926197755875">"valittu"</string> <string name="not_selected" msgid="410652016565864475">"ei valittu"</string> <string name="in_progress" msgid="2149208189184319441">"käynnissä"</string> - <string name="whichApplication" msgid="5432266899591255759">"Tee toiminto käyttäen sovellusta"</string> + <string name="whichApplication" msgid="5432266899591255759">"Tee toiminto käyttäen:"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Suorita sovelluksella %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Suorita toiminto"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Avaa sovelluksessa"</string> @@ -1972,7 +1972,7 @@ <string name="pin_specific_target" msgid="7824671240625957415">"Kiinnitä <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="unpin_target" msgid="3963318576590204447">"Irrota"</string> <string name="unpin_specific_target" msgid="3859828252160908146">"Irrota <xliff:g id="LABEL">%1$s</xliff:g>"</string> - <string name="app_info" msgid="6113278084877079851">"Sovelluksen tiedot"</string> + <string name="app_info" msgid="6113278084877079851">"Sovellustiedot"</string> <string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string> <string name="demo_starting_message" msgid="6577581216125805905">"Aloitetaan esittelyä…"</string> <string name="demo_restarting_message" msgid="1160053183701746766">"Palautetaan asetuksia…"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 12015ec6d85f..fb9a3132debe 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1173,7 +1173,7 @@ <string name="selected" msgid="6614607926197755875">"valt"</string> <string name="not_selected" msgid="410652016565864475">"inte valt"</string> <string name="in_progress" msgid="2149208189184319441">"pågår"</string> - <string name="whichApplication" msgid="5432266899591255759">"Slutför åtgärd genom att använda"</string> + <string name="whichApplication" msgid="5432266899591255759">"Slutför åtgärd med"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Slutför åtgärden med %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Slutför åtgärd"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Öppna med"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index f52b714b9563..7cebe2729adb 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -315,7 +315,7 @@ <string name="permgrouplab_microphone" msgid="2480597427667420076">"మైక్రోఫోన్"</string> <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ఆడియోను రికార్డ్ చేయడానికి"</string> <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"ఫిజికల్ యాక్టివిటీ"</string> - <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"భౌతిక యాక్టివిటీని యాక్సెస్ చేయండి"</string> + <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"ఫిజికల్ యాక్టివిటీని యాక్సెస్ చేయండి"</string> <string name="permgrouplab_camera" msgid="9090413408963547706">"కెమెరా"</string> <string name="permgroupdesc_camera" msgid="7585150538459320326">"చిత్రాలను తీయడానికి మరియు వీడియోను రికార్డ్ చేయడానికి"</string> <string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"సమీపంలోని పరికరాలు"</string> 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..6e4871fdf922 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,7 +552,8 @@ 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); if (calculator == null) { @@ -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, + isDefaultMinSizeSatisfied, 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..367e3b9d9676 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/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/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/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/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-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml index b3987f1aeeda..951d6fed0a17 100644 --- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml +++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml @@ -99,4 +99,9 @@ android:fromId="@id/unlocked" android:toId="@id/locked_aod" android:drawable="@drawable/unlocked_to_aod_lock" /> + + <transition + android:fromId="@id/unlocked" + android:toId="@id/locked" + android:drawable="@drawable/unlocked_to_locked" /> </animated-selector> diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_to_locked.xml new file mode 100644 index 000000000000..b55abd1fdddc --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/unlocked_to_locked.xml @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<animated-vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_2_G_T_1" android:translateX="22.75" android:translateY="22.25" android:scaleX="1.02" android:scaleY="1.02"> + <group android:name="_R_G_L_2_G" android:translateX="-8.75" android:translateY="-8.75"> + <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " /> + </group> + </group> + <group android:name="_R_G_L_1_G_N_4_T_1" android:translateX="22.75" android:translateY="22.25" android:scaleX="1.02" android:scaleY="1.02"> + <group android:name="_R_G_L_1_G_N_4_T_0" android:translateX="-8.75" android:translateY="-8.75"> + <group android:name="_R_G_L_1_G" android:translateX="8.995" android:translateY="18.431" android:scaleX="0.98039" android:scaleY="0.98039"> + <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " /> + </group> + </group> + </group> + <group android:name="_R_G_L_0_G_N_4_T_1" android:translateX="22.75" android:translateY="22.25" android:scaleX="1.02" android:scaleY="1.02"> + <group android:name="_R_G_L_0_G_N_4_T_0" android:translateX="-8.75" android:translateY="-8.75"> + <group android:name="_R_G_L_0_G" android:translateX="8.995000000000001" android:translateY="18.345000000000002" android:pivotX="-0.031" android:pivotY="4.406" android:scaleX="0.98039" android:scaleY="0.98039"> + <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " /> + </group> + </group> + </group> + </group> + <group android:name="time_group" /> + </vector> + </aapt:attr> + <target android:name="_R_G_L_2_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" android:duration="333" android:startOffset="0" android:valueFrom="2" android:valueTo="2" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0.833,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_2_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="83" android:startOffset="0" android:valueFrom="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueTo="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.456,0 0.464,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="133" android:startOffset="83" android:valueFrom="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueTo="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.606,0 0.035,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="117" android:startOffset="217" android:valueFrom="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueTo="M2.52 14.65 C2.52,14.65 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15.02,14.65 15.02,14.65 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.511,0 0.409,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_2_G_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="0" android:valueFrom="22.25" android:valueTo="22.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.535,0 0.426,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="translateY" android:duration="67" android:startOffset="333" android:valueFrom="22.25" android:valueTo="24.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.535,0 0.426,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="translateY" android:duration="100" android:startOffset="400" android:valueFrom="24.25" android:valueTo="22.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.641,0 0.499,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_N_4_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="0" android:valueFrom="22.25" android:valueTo="22.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.535,0 0.426,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="translateY" android:duration="67" android:startOffset="333" android:valueFrom="22.25" android:valueTo="24.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.535,0 0.426,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="translateY" android:duration="100" android:startOffset="400" android:valueFrom="24.25" android:valueTo="22.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.641,0 0.499,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_N_4_T_1"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="0" android:valueFrom="22.25" android:valueTo="22.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.535,0 0.426,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="translateY" android:duration="67" android:startOffset="333" android:valueFrom="22.25" android:valueTo="24.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.535,0 0.426,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="translateY" android:duration="100" android:startOffset="400" android:valueFrom="24.25" android:valueTo="22.25" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.641,0 0.499,1 1.0,1.0" /> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" android:duration="850" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" /> + </set> + </aapt:attr> + </target> +</animated-vector> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 35d0981b96e0..c0509a9f7702 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Beweeg nader aan <xliff:g id="DEVICENAME">%1$s</xliff:g> om hier te speel"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Speel tans op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Iets is fout. Probeer weer."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Onaktief, gaan program na"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nie gekry nie"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrole is nie beskikbaar nie"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index cfb8ad0a8040..d0c5c9040ea8 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"እዚህ ለመጫወት ወደ <xliff:g id="DEVICENAME">%1$s</xliff:g> ቀረብ ይበሉ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ በማጫወት ላይ"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"የሆነ ችግር ተፈጥሯል። እንደገና ይሞክሩ።"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"አልተገኘም"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"መቆጣጠሪያ አይገኝም"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 7568eb7942c5..ea5bce12b3db 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"يُرجى الاقتراب من <xliff:g id="DEVICENAME">%1$s</xliff:g> لتشغيل الوسائط هنا."</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"جارٍ تشغيل الوسائط على <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"حدث خطأ. يُرجى إعادة المحاولة."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string> <string name="controls_error_removed" msgid="6675638069846014366">"لم يتم العثور عليه."</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"عنصر التحكّم غير متوفّر"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• تثبيت تطبيق كاميرا"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• إعداد التطبيق"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• توفُّر جهاز واحد على الأقل"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"انقر مع الاستمرار على الاختصار."</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"إلغاء"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"قلب الجهاز الآن"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"عليك فتح الهاتف لالتقاط صورة ذاتية بشكل أفضل."</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index def260793ad6..cbd2bedd3f20 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ইয়াত খেলিবলৈ <xliff:g id="DEVICENAME">%1$s</xliff:g>ৰ আৰু ওচৰলৈ যাওক"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে কৰি থকা হৈছে"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"কিবা ভুল হ’ল। পুনৰ চেষ্টা কৰক।"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্টো পৰীক্ষা কৰক"</string> <string name="controls_error_removed" msgid="6675638069846014366">"বিচাৰি পোৱা নগ’ল"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"নিয়ন্ত্ৰণটো উপলব্ধ নহয়"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index c3378dde36fb..dd4d5109855d 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Burada oxutmaq üçün <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaxınlaşın"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxudulur"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Xəta oldu. Yenə cəhd edin."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tapılmadı"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Nəzarət əlçatan deyil"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 2b50c3631019..64313896a4ce 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g> da biste na njemu puštali"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Pušta se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Došlo je do greške. Probajte ponovo."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 7394df3bef5b..7e7262a057d3 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Падыдзіце бліжэй да прылады \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", каб прайграць на гэтай"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Прайграецца на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Нешта пайшло не так. Паўтарыце спробу."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не знойдзена"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Кіраванне недаступнае"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Усталявана праграма \"Камера\"."</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• Праграма наладжана."</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Даступная хаця б адна прылада."</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Дакраніцеся і ўтрымлівайце ярлык"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Скасаваць"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Пераключыць"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Каб атрымаць лепшае сэлфі, раскрыйце тэлефон"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 44e4e95dda17..26a154fadc56 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Приближете се до <xliff:g id="DEVICENAME">%1$s</xliff:g> за възпроизвеждане на това устройство"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Възпроизвежда се на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Нещо се обърка. Опитайте отново."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не е намерено"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е налице"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 384b18308468..b6069a4a9361 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"এখান থেকে চালাতে <xliff:g id="DEVICENAME">%1$s</xliff:g>-এর কাছে নিয়ে যান"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ ভিডিও চালানো হচ্ছে"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"কোনও সমস্যা হয়েছে। আবার চেষ্টা করুন।"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"বন্ধ আছে, অ্যাপ চেক করুন"</string> <string name="controls_error_removed" msgid="6675638069846014366">"খুঁজে পাওয়া যায়নি"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"কন্ট্রোল উপলভ্য নেই"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index d6217722287b..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> @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g> da na njemu reproducirate"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Nešto nije uredu. Pokušajte ponovo."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, vidite aplikaciju"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 4b77f089b5e0..2f48676a142a 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Acosta\'t a <xliff:g id="DEVICENAME">%1$s</xliff:g> per reproduir el contingut aquí"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"S\'està reproduint a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"S\'ha produït un error. Torna-ho a provar."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No s\'ha trobat"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"El control no està disponible"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 48f6dfe53d14..feeaa32f68a7 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Pokud zde chcete přehrávat média, přibližte se k zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Přehrávání v zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Došlo k chybě. Zkuste to znovu."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nenalezeno"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládání není k dispozici"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index aa0f20c0b899..f64587e26731 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Ryk tættere på <xliff:g id="DEVICENAME">%1$s</xliff:g> for at afspille her"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Afspilles på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Noget gik galt. Prøv igen."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ikke fundet"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Styringselement ikke tilgængeligt"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index f94c192ec1b0..bd4c5872dfce 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Platziere für die Wiedergabe dein Gerät näher an „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Wird auf „<xliff:g id="DEVICENAME">%1$s</xliff:g>“ abgespielt"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Es gab ein Problem. Versuch es noch einmal."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nicht gefunden"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Steuerelement nicht verfügbar"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index ea2f0c97fffa..05cf06cc4ba0 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Μετακινηθείτε πιο κοντά στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g> για αναπαραγωγή εδώ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Παρουσιάστηκε κάποιο πρόβλημα. Δοκιμάστε ξανά."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Ανενεργό, έλεγχος εφαρμογής"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Δεν βρέθηκε."</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Μη διαθέσιμο στοιχείο ελέγχου"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index faaefe05b7d9..7c902807860b 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -853,6 +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> + <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 7f703df7ec89..f195c640bd41 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -853,6 +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> + <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 faaefe05b7d9..7c902807860b 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -853,6 +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> + <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 faaefe05b7d9..7c902807860b 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -853,6 +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> + <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 e21550f3d140..03659b4f7c69 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -853,6 +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> + <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 def72e897a59..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> @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para reproducir aquí"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Se produjo un error. Vuelve a intentarlo."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No se encontró"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"El control no está disponible"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 5146e74bc1fa..c2443daccceb 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para jugar aquí"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Se ha producido un error. Inténtalo de nuevo."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string> <string name="controls_error_removed" msgid="6675638069846014366">"No se ha encontrado"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Control no disponible"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 7b2bd3e197c4..b31030a42fe3 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Siin esitamiseks liigutage seadmele <xliff:g id="DEVICENAME">%1$s</xliff:g> lähemale"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Esitatakse seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Midagi läks valesti. Proovige uuesti."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ei leitud"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Juhtelement pole saadaval"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 3fb9726be8e4..de9c75f45af8 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Gerturatu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailura bertan erreproduzitzen ari dena hemen erreproduzitzeko"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzen"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Arazoren bat izan da. Saiatu berriro."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ez da aurkitu"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ez dago erabilgarri kontrolatzeko aukera"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 161117a423c6..87aed942d05e 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"برای پخش در اینجا، به <xliff:g id="DEVICENAME">%1$s</xliff:g> نزدیکتر شوید"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"درحال پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"مشکلی پیش آمد. دوباره امتحان کنید."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"غیرفعال، برنامه را بررسی کنید"</string> <string name="controls_error_removed" msgid="6675638069846014366">"پیدا نشد"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"کنترل دردسترس نیست"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 33108eb8445c..f7bb7f701687 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -724,7 +724,7 @@ <string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> on käynnissä"</string> <string name="instant_apps_message" msgid="6112428971833011754">"Sovellus avattiin ilman asennusta."</string> <string name="instant_apps_message_with_help" msgid="1816952263531203932">"Sovellus avattiin ilman asennusta. Katso lisätietoja napauttamalla."</string> - <string name="app_info" msgid="5153758994129963243">"Sovelluksen tiedot"</string> + <string name="app_info" msgid="5153758994129963243">"Sovellustiedot"</string> <string name="go_to_web" msgid="636673528981366511">"Siirry selaimeen"</string> <string name="mobile_data" msgid="4564407557775397216">"Mobiilidata"</string> <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string> @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Siirrä <xliff:g id="DEVICENAME">%1$s</xliff:g> lähemmäs toistaaksesi täällä"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Toistetaan: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Jotain meni pieleen. Yritä uudelleen."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ei löydy"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ohjain ei ole käytettävissä"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 184b907963e8..be81a9275544 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Rapprochez-vous de <xliff:g id="DEVICENAME">%1$s</xliff:g> pour lire le contenu"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Un problème est survenu. Réessayez."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"La commande n\'est pas accessible"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 9823ad016c7c..869166321079 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Rapprochez l\'appareil pour transférer la diffusion à votre <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Un problème est survenu. Réessayez."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Commande indisponible"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index b27bd12cd48f..f9738e3b413a 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Achégate ao dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>) para reproducir o contido neste"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducindo contido noutro dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Produciuse un erro. Téntao de novo."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Non se atopou"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"O control non está dispoñible"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index e00f8d88be38..dac1403f8dd5 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"આમાં ચલાવવા માટે ડિવાઇસને <xliff:g id="DEVICENAME">%1$s</xliff:g>ની નજીક ખસેડો"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવામાં આવી રહ્યું છે"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"કંઈક ખોટું થયું. ફરી પ્રયાસ કરો."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string> <string name="controls_error_removed" msgid="6675638069846014366">"મળ્યું નથી"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"નિયંત્રણ ઉપલબ્ધ નથી"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 49fb626ab6a6..38d2bffa239a 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"अपने डिवाइस पर मीडिया फ़ाइल ट्रांसफ़र करने के लिए, उसे <xliff:g id="DEVICENAME">%1$s</xliff:g> के पास ले जाएं"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर मीडिया चल रहा है"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"कोई गड़बड़ी हुई. फिर से कोशिश करें."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string> <string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल नहीं है"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"कंट्रोल मौजूद नहीं है"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index e5253bc13a1f..7613317734d1 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g> da biste na njemu reproducirali"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Nešto nije u redu. Pokušajte ponovo."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, provjerite aplik."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 6263126eb692..c2b94c0b1102 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Menjen közelebb a(z) <xliff:g id="DEVICENAME">%1$s</xliff:g> eszközhöz, hogy itt játszhassa le a tartalmat"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lejátszás folyamatban a(z) <xliff:g id="DEVICENAME">%1$s</xliff:g> eszközön"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Hiba történt. Próbálkozzon újra."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nem található"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Nem hozzáférhető vezérlő"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index f84530f41579..5623ecfb7f29 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Ավելի մոտեցեք «<xliff:g id="DEVICENAME">%1$s</xliff:g>» սարքին՝ նվագարկումը սկսելու համար"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Նվագարկվում է «<xliff:g id="DEVICENAME">%1$s</xliff:g>» սարքում"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Սխալ առաջացավ։ Նորից փորձեք։"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Չի գտնվել"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Կառավարման տարրը հասանելի չէ"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 2702f9281463..bf5f8b01848c 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Dekatkan ke <xliff:g id="DEVICENAME">%1$s</xliff:g> untuk memutar di sini"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Diputar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Terjadi error. Coba lagi."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol tidak tersedia"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 64c76773fa52..f3efb8624333 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Færðu tækið nær <xliff:g id="DEVICENAME">%1$s</xliff:g> til að spila hér"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Í spilun í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Eitthvað fór úrskeiðis. Reyndu aftur."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Fannst ekki"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Stýring er ekki tiltæk"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 89817d19a8fc..e8c6a5b21308 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Avvicinati a <xliff:g id="DEVICENAME">%1$s</xliff:g> per riprodurre i contenuti qui"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"In riproduzione su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Si è verificato un errore. Riprova."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Controllo non trovato"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Il controllo non è disponibile"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index ad9ccf13f6fd..690b6b22710c 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"צריך להתקרב אל <xliff:g id="DEVICENAME">%1$s</xliff:g> כדי להפעיל כאן"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"פועלת ב-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"משהו השתבש. יש לנסות שוב."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"לא פעיל, יש לבדוק את האפליקציה"</string> <string name="controls_error_removed" msgid="6675638069846014366">"לא נמצא"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"הפקד לא זמין"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 112db0530716..5bd580ccc603 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ここで再生するには<xliff:g id="DEVICENAME">%1$s</xliff:g>に近づいてください"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生しています"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"エラーが発生しました。もう一度お試しください。"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string> <string name="controls_error_removed" msgid="6675638069846014366">"見つかりませんでした"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"コントロールを使用できません"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 22e8a481e6f8..d25daf440ce2 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"მიუახლოვდით <xliff:g id="DEVICENAME">%1$s</xliff:g>-ს მისი მეშვეობით დასაკრავად"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"მიმდინარეობს დაკვრა <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"რაღაც შეცდომა მოხდა. ცადეთ ხელახლა."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"არააქტიურია, გადაამოწმეთ აპი"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ვერ მოიძებნა"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"კონტროლი მიუწვდომელია"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index d9ce21674ea7..bb24d6af3c0a 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Осы жерде ойнау үшін <xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысына жақындаңыз"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында ойнатылуда."</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Бірдеңе дұрыс болмады. Қайталап көріңіз."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Табылмады"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Басқару виджеті қолжетімсіз"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 830f2f24bab2..65953058322d 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"រំកិលឱ្យកាន់តែជិត <xliff:g id="DEVICENAME">%1$s</xliff:g> ដើម្បីចាក់នៅទីនេះ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"កំពុងចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"មានអ្វីមួយខុសប្រក្រតី។ សូមព្យាយាមម្ដងទៀត។"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើលកម្មវិធី"</string> <string name="controls_error_removed" msgid="6675638069846014366">"រកមិនឃើញទេ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"មិនអាចគ្រប់គ្រងបានទេ"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index b142a10c1dfc..81a37f31aa8f 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ಇಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು <xliff:g id="DEVICENAME">%1$s</xliff:g> ಸಮೀಪಕ್ಕೆ ಹೋಗಿ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತಿದೆ"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ಏನೋ ತಪ್ಪಾಗಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ಕಂಡುಬಂದಿಲ್ಲ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ನಿಯಂತ್ರಣ ಲಭ್ಯವಿಲ್ಲ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index f9049f9fa05d..1570a6aa2370 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"현재 기기에서 재생하려면 <xliff:g id="DEVICENAME">%1$s</xliff:g>에 더 가까이 이동합니다."</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생 중"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"문제가 발생했습니다. 다시 시도해 주세요."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string> <string name="controls_error_removed" msgid="6675638069846014366">"찾을 수 없음"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"컨트롤을 사용할 수 없음"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index d579fc2d8c60..b1206840b722 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Бул жерде ойнотуу үчүн <xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүнө жакындатыңыз"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> аркылуу ойнотулууда"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Бир жерден ката кетти. Кайра аракет кылыңыз."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Табылган жок"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Башкара албайсыз"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 628d75e975d3..446deb05cf00 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ກະລຸນາຍ້າຍເຂົ້າໃກ້ <xliff:g id="DEVICENAME">%1$s</xliff:g> ເພື່ອຫຼິ້ນຢູ່ບ່ອນນີ້"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"ກຳລັງຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ມີບາງຢ່າງຜິດພາດເກີດຂຶ້ນ. ກະລຸນາລອງໃໝ່."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ບໍ່ພົບ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ບໍ່ສາມາດໃຊ້ການຄວບຄຸມໄດ້"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 73a645ef4864..db518c100a0d 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Perkelkite arčiau „<xliff:g id="DEVICENAME">%1$s</xliff:g>“, kad būtų galima leisti čia"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Leidžiama įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Kažkas ne taip. Bandykite dar kartą."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nerasta"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Valdiklis nepasiekiamas"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 051779cafadc..5ea8da2889d8 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Pārvietojieties tuvāk ierīcei “<xliff:g id="DEVICENAME">%1$s</xliff:g>”, lai atskaņotu šeit"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Notiek atskaņošana ierīcē <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Radās kļūda. Mēģiniet vēlreiz."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Netika atrasta"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Vadīkla nav pieejama"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Ir instalēta kameras lietotne."</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• Lietotne ir iestatīta."</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Ir pieejama vismaz viena ierīce."</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Pieskarieties saīsnei un turiet."</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Atcelt"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Apvērst tūlīt"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Labākas pašbildes uzņemšana, atlokot tālruni"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 02bfd4f0becb..0575b102e405 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Приближете се до <xliff:g id="DEVICENAME">%1$s</xliff:g> за да пуштите тука"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Пуштено на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Нешто не е во ред. Обидете се повторно."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивна, провери апликација"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не е најдено"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е достапна"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 064927a8bc35..9a2353c6b8ad 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ഇവിടെ പ്ലേ ചെയ്യാൻ <xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിന് അടുത്തേക്ക് നീക്കുക"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"എന്തോ കുഴപ്പമുണ്ടായി. വീണ്ടും ശ്രമിക്കുക."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"നിഷ്ക്രിയം, ആപ്പ് പരിശോധിക്കൂ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"കണ്ടെത്തിയില്ല"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"നിയന്ത്രണം ലഭ്യമല്ല"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 0c36e1df1cf4..08c47b0f4df4 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Энд тоглуулахын тулд <xliff:g id="DEVICENAME">%1$s</xliff:g>-д ойртоно уу"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулж байна"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Алдаа гарлаа. Дахин оролдоно уу."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Олдсонгүй"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Хяналт боломжгүй байна"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 93978c08f412..103e8d204772 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"येथे प्ले करण्यासाठी <xliff:g id="DEVICENAME">%1$s</xliff:g> च्या जवळ जा"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले केला जात आहे"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"काहीतरी चूक झाली. पुन्हा प्रयत्न करा."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string> <string name="controls_error_removed" msgid="6675638069846014366">"आढळले नाही"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"नियंत्रण उपलब्ध नाही"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• कॅमेरा अॅप इंस्टॉल करणे"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• अॅप सेट करणे"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• किमान एक डिव्हाइस उपलब्ध करणे"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"स्पर्श करा आणि धरून ठेवा शॉर्टकट"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"रद्द करा"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"आता फ्लिप करा"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"आणखी चांगल्या सेल्फीसाठी फोनबद्दल अधिक जाणून घ्या"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 46aa1cb85bdb..b0b40962123f 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Dekatkan dengan <xliff:g id="DEVICENAME">%1$s</xliff:g> untuk bermain di sini"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Dimainkan pada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Kesilapan telah berlaku. Cuba lagi."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kawalan tidak tersedia"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index edfe2e9fd9e5..2014f5c566c5 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ဤနေရာတွင်ဖွင့်ရန် <xliff:g id="DEVICENAME">%1$s</xliff:g> အနီးသို့တိုးပါ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင် ဖွင့်နေသည်"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"တစ်ခုခုမှားသွားသည်။ ထပ်စမ်းကြည့်ပါ။"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"မတွေ့ပါ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ထိန်းချုပ်မှု မရနိုင်ပါ"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index eb448aaf2c4d..2b1ebfdb4f27 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Flytt deg nærmere <xliff:g id="DEVICENAME">%1$s</xliff:g> for å spille av her"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Spilles av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Noe gikk galt. Prøv på nytt."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ikke funnet"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrollen er utilgjengelig"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index f3db004b264b..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> @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"तपाईं यहाँ प्ले गर्न चाहनुहुन्छ भने आफ्नो डिभाइसलाई <xliff:g id="DEVICENAME">%1$s</xliff:g> नजिकै लैजानुहोस्"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गरिँदै छ"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"केही चिज गडबड भयो। फेरि प्रयास गर्नुहोस्।"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string> <string name="controls_error_removed" msgid="6675638069846014366">"फेला परेन"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"नियन्त्रण उपलब्ध छैन"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index da60323ab03a..e49b9fac5ccb 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Ga dichter bij <xliff:g id="DEVICENAME">%1$s</xliff:g> staan om hier af te spelen"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Afspelen op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Er is iets misgegaan. Probeer het opnieuw."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Niet gevonden"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Beheeroptie niet beschikbaar"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 8391e249772e..71d0822d078b 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ଏଠାରେ ଚଲାଇବା ପାଇଁ <xliff:g id="DEVICENAME">%1$s</xliff:g>ର ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚାଲୁଛି"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"କିଛି ତ୍ରୁଟି ହୋଇଛି। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ମିଳିଲା ନାହିଁ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ନିୟନ୍ତ୍ରଣ ଉପଲବ୍ଧ ନାହିଁ"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• ଏକ କେମେରା ଆପ ଇନଷ୍ଟଲ କରିବା"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• ଆପ ସେଟ ଅପ କରାଯାଇଛି"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• ଅତିକମରେ ଗୋଟିଏ ଡିଭାଇସ ଉପଲବ୍ଧ ଅଛି"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"ସର୍ଟକଟକୁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"ବାତିଲ କରନ୍ତୁ"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"ବର୍ତ୍ତମାନ ଫ୍ଲିପ କରନ୍ତୁ"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"ଏକ ଉନ୍ନତ ସେଲ୍ଫି ପାଇଁ ଫୋନକୁ ଅନଫୋଲ୍ଡ କରନ୍ତୁ"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 2a35dfcd8708..f6ad3ad01bac 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ਇੱਥੇ ਚਲਾਉਣ ਲਈ <xliff:g id="DEVICENAME">%1$s</xliff:g> ਦੇ ਨੇੜੇ ਜਾਓ"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ਕੋਈ ਗੜਬੜ ਹੋ ਗਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ਨਹੀਂ ਮਿਲਿਆ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 0bc0356e5dbc..7cced9e01ba2 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Zbliż do urządzenia <xliff:g id="DEVICENAME">%1$s</xliff:g>, aby na nim odtwarzać"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Odtwarzam na ekranie <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Coś poszło nie tak. Spróbuj ponownie."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nie znaleziono"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Element jest niedostępny"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index a51ecccd2c21..41925480b3ad 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g> para abrir a mídia aqui"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Mídia aberta no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Algo deu errado. Tente novamente."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"O controle está indisponível"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 27786bcb396f..ad93343fd081 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g> para reproduzir aqui"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"A reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Algo correu mal. Tente novamente."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inativa. Consulte a app."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado."</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"O controlo está indisponível"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index a51ecccd2c21..41925480b3ad 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g> para abrir a mídia aqui"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Mídia aberta no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Algo deu errado. Tente novamente."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"O controle está indisponível"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 318b6cff609f..e4a1b0bcf2e0 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Apropie-te de <xliff:g id="DEVICENAME">%1$s</xliff:g> ca să redai acolo"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Se redă pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"A apărut o eroare. Încearcă din nou."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verifică aplicația"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Comanda este indisponibilă"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 9da9c6d21fae..9b4975e712e6 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Для воспроизведения на этом устройстве подойдите ближе к другому (<xliff:g id="DEVICENAME">%1$s</xliff:g>)."</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Воспроизводится на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Произошла ошибка. Повторите попытку."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не найдено."</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Управление недоступно"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index acbc229ed479..91ed10f66e1d 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"මෙහි ක්රීඩා කිරීමට <xliff:g id="DEVICENAME">%1$s</xliff:g> වෙත වඩා සමීප වන්න"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කරමින්"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"යම් දෙයක් වැරදිණි. නැවත උත්සාහ කරන්න."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"අක්රියයි, යෙදුම පරීක්ෂා කරන්න"</string> <string name="controls_error_removed" msgid="6675638069846014366">"හමු නොවිණි"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"පාලනය ලබා ගත නොහැකිය"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• කැමරා යෙදුමක් ස්ථාපන කරන්න"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• යෙදුම සකසා ඇත"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• අවම වශයෙන් එක උපාංගයක් ලැබේ"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"ස්පර්ශ කර අල්ලා සිටීමේ කෙටිමඟ"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"අවලංගු කරන්න"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"දැන් පෙරළන්න"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"වඩා හොඳ සෙල්ෆියක් සඳහා දුරකථනය දිගහරින්න"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 3718a20109f7..4c3d8b816347 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa k nemu"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Prehráva sa v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Niečo sa pokazilo. Skúste to znova."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nenájdené"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládač nie je k dispozícii"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Nainštalujte si aplikáciu kamery"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• Aplikácia je nastavená"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• K dispozícii je minimálne jedno zariadenie"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Pridržte skratku"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Zrušiť"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Prevráťte"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Ak chcete lepšie selfie, rozložte telefón"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 2aee459a43fe..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> @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Približajte napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> za predvajanje v tej napravi"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Prišlo je do napake. Poskusite znova."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ni mogoče najti"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolnik ni na voljo"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 06d786fec0d5..ad2e596f849c 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Afrohu te <xliff:g id="DEVICENAME">%1$s</xliff:g> për ta luajtur këtu"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Po luhet në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Ndodhi një gabim. Provo përsëri."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Nuk u gjet"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolli është i padisponueshëm"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Të instalosh një aplikacion të kamerës"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• Aplikacioni është konfiguruar"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Ofrohet të paktën një pajisje"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Prek dhe mbaj shtypur shkurtoren"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Anulo"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"U kthye tani"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Shpalos telefonin për një selfi më të mirë"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index bc660e13ef11..ef6bb4597104 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Приближите се уређају <xliff:g id="DEVICENAME">%1$s</xliff:g> да бисте на њему пуштали"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Пушта се на уређају <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Дошло је до грешке. Пробајте поново."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Није пронађено"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Контрола није доступна"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 381f03238d56..c98273926204 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Flytta dig närmare <xliff:g id="DEVICENAME">%1$s</xliff:g> om du vill spela här"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Spelas upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Något gick fel. Försök igen."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv, kolla appen"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hittades inte"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Styrning är inte tillgänglig"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• installera en kameraapp"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• appen har konfigurerats"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• minst en enhet är tillgänglig"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Tryck länge på genvägen"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Avbryt"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Vänd nu"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Vik upp telefonen för att ta en bättre selfie"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 530bfcc2afa1..5e33bf4d82fc 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Sogeza karibu na <xliff:g id="DEVICENAME">%1$s</xliff:g> ili kucheza hapa"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Inacheza kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Hitilafu fulani imetokea. Jaribu tena."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hakipatikani"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kidhibiti hakipatikani"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Sakinisha programu ya kamera"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• Programu hii imewekewa mipangilio"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Angalau kifaa kimoja kinapatikana"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Gusa na ushikilie njia ya mkato"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Ghairi"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Geuza kifaa sasa"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Kunjua simu ili upige selfi iliyo bora"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 87d20d6848e4..6e14c2630a03 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"இங்கு பிளே செய்ய உங்கள் சாதனத்தை <xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்திற்கு அருகில் நகர்த்துங்கள்"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் பிளே ஆகிறது"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ஏதோ தவறாகிவிட்டது. மீண்டும் முயலவும்."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string> <string name="controls_error_removed" msgid="6675638069846014366">"இல்லை"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"கட்டுப்பாடு இல்லை"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 2afff20bb7ae..d4e458f78e28 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ఇక్కడ ప్లే చేయడానికి <xliff:g id="DEVICENAME">%1$s</xliff:g>కి దగ్గరగా వెళ్లండి"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే అవుతోంది"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"ఏదో తప్పు జరిగింది. మళ్లీ ట్రై చేయండి."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"ఇన్యాక్టివ్, యాప్ చెక్ చేయండి"</string> <string name="controls_error_removed" msgid="6675638069846014366">"కనుగొనబడలేదు"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"కంట్రోల్ అందుబాటులో లేదు"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 31a74d13fd72..f8a16bd26483 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ขยับไปใกล้ <xliff:g id="DEVICENAME">%1$s</xliff:g> มากขึ้นเพื่อเล่นที่นี่"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"กำลังเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"เกิดข้อผิดพลาด โปรดลองอีกครั้ง"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"ไม่มีการใช้งาน โปรดตรวจสอบแอป"</string> <string name="controls_error_removed" msgid="6675638069846014366">"ไม่พบ"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"ใช้การควบคุมไม่ได้"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index cc81e719b2f3..3578aa78c96b 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Lumapit sa <xliff:g id="DEVICENAME">%1$s</xliff:g> para mag-play rito"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Nagpe-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Nagkaproblema. Subukan ulit."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Hindi aktibo, tingnan ang app"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Hindi nahanap"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Hindi available ang kontrol"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index a45871d7e488..64431ad25980 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Burada oynatmak için <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaklaşın"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oynatılıyor"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Bir sorun oldu. Tekrar deneyin."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Bulunamadı"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol kullanılamıyor"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Kamera uygulaması yüklenmelidir"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• Uygulama kurulmuş olmalıdır"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• En az bir cihaz mevcut olmalıdır"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Kısayola dokunup basılı tutun"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"İptal"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Şimdi çevirin"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Daha iyi selfie çekmek için telefonu açın"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index f00ac9093eac..9eba61007965 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Наблизьтеся до пристрою <xliff:g id="DEVICENAME">%1$s</xliff:g>, щоб відтворити медіафайли на ньому"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Відтворюється на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Сталася помилка. Повторіть спробу."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Не знайдено"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Елемент керування недоступний"</string> @@ -1004,8 +1006,7 @@ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Встановлено додаток для камери"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• Додаток налаштовано"</string> <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Принаймні один пристрій доступний"</string> - <!-- no translation found for keyguard_affordance_press_too_short (8145437175134998864) --> - <skip /> + <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Натисніть і утримуйте ярлик"</string> <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Скасувати"</string> <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Перевернути"</string> <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Розгорніть телефон, щоб зробити краще селфі"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index d432ff578ab2..cfcdeca3a2c9 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"یہاں چلانے کے ليے <xliff:g id="DEVICENAME">%1$s</xliff:g> کے قریب جائیں"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چل رہا ہے"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"کچھ غلط ہوگیا۔ پھر کوشش کریں۔"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string> <string name="controls_error_removed" msgid="6675638069846014366">"نہیں ملا"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"کنٹرول دستیاب نہیں ہے"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 8c92678c3a4b..83085190c1fa 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Bu yerda ijro qilish uchun <xliff:g id="DEVICENAME">%1$s</xliff:g>qurilmasiga yaqinlashtiring"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> qurilmasida ijro qilinmoqda"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Xatolik yuz berdi. Qayta urining."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Nofaol. Ilovani tekshiring"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Topilmadi"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Boshqarish imkonsiz"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 4e70744fa149..80aa82293e65 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Di chuyển đến gần <xliff:g id="DEVICENAME">%1$s</xliff:g> hơn để phát tại đây"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Đang phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Đã xảy ra lỗi. Hãy thử lại."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Không tìm thấy"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Không có chức năng điều khiển"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index f747730eaaf9..a449e9e332cd 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"若要在此设备上播放,请靠近“<xliff:g id="DEVICENAME">%1$s</xliff:g>”"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"出了点问题,请重试。"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string> <string name="controls_error_removed" msgid="6675638069846014366">"未找到"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"控件不可用"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 01cf0dc557ce..4749e3bc9a97 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"如要在此裝置上播放,請靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"發生錯誤,請再試一次。"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string> <string name="controls_error_removed" msgid="6675638069846014366">"找不到"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"無法使用控制功能"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 0c9bf9952c3a..4972aee02a80 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"如要在這部裝置上播放,請移到更靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」的位置"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"發生錯誤,請再試一次。"</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"無效,請查看應用程式"</string> <string name="controls_error_removed" msgid="6675638069846014366">"找不到控制項"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"無法使用控制項"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 029cbb003eee..d9cf44b3fe3d 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -853,6 +853,8 @@ <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Sondela eduze ne-<xliff:g id="DEVICENAME">%1$s</xliff:g> ukuze udlale lapha"</string> <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Idlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string> <string name="media_transfer_failed" msgid="7955354964610603723">"Kukhona okungahambanga kahle. Zama futhi."</string> + <!-- no translation found for media_transfer_loading (5544017127027152422) --> + <skip /> <string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string> <string name="controls_error_removed" msgid="6675638069846014366">"Ayitholakali"</string> <string name="controls_error_removed_title" msgid="1207794911208047818">"Ukulawula akutholakali"</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..6841bf897e92 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1279,6 +1279,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> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 977adde635aa..87361b9c9f7f 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] --> 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..8de1368091ea 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 @@ -716,6 +717,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Whether keyguard is going away due to screen off or device entry. + */ + public boolean isKeyguardGoingAway() { + return mKeyguardGoingAway; + } + + /** * Updates KeyguardUpdateMonitor's internal state to know if keyguard is showing and if * its occluded. The keyguard is considered visible if its showing and NOT occluded. */ @@ -2048,7 +2056,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 +2320,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 +2715,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() : true; } boolean shouldListen = shouldListenKeyguardState && shouldListenUserState @@ -3845,11 +3834,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 +3910,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/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index dd6a1bd457b8..1322f16a5a59 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -501,6 +501,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } @Override + public void onBiometricsCleared() { + final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric; + mUserUnlockedWithBiometric = + mKeyguardUpdateMonitor.getUserUnlockedWithBiometric( + KeyguardUpdateMonitor.getCurrentUser()); + if (wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric) { + updateVisibility(); + } + } + + @Override public void onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType) { final boolean wasRunningFps = mRunningFPS; 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/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/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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index e40c98892ab5..d015b1580385 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -165,7 +165,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 +180,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") @@ -197,6 +204,11 @@ object Flags { @JvmField val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = false) + // flag for controlling auto pin confirmation and material u shapes in bouncer + @JvmField + val AUTO_PIN_CONFIRMATION = + unreleasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -208,6 +220,10 @@ object Flags { releasedFlag(401, "smartspace_shared_element_transition_enabled") val SMARTSPACE = resourceBooleanFlag(402, R.bool.flag_smartspace, "smartspace") + // TODO(b/258517050): Clean up after the feature is launched. + @JvmField + val SMARTSPACE_DATE_WEATHER_DECOUPLED = unreleasedFlag(403, "smartspace_date_weather_decoupled") + // 500 - quick settings // TODO(b/254512321): Tracking Bug @@ -422,9 +438,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/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/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/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 326acc9eb762..20c6531d580b 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 @@ -30,6 +30,8 @@ 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,6 +53,7 @@ constructor( override fun start() { listenForLockscreenToGone() listenForLockscreenToOccluded() + listenForLockscreenToCamera() listenForLockscreenToAod() listenForLockscreenToBouncer() listenForLockscreenToDreaming() @@ -69,7 +72,7 @@ constructor( name, KeyguardState.LOCKSCREEN, KeyguardState.DREAMING, - getAnimator(), + getAnimator(TO_DREAMING_DURATION), ) ) } @@ -184,17 +187,42 @@ 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), + ) + ) + } + } + } + } + + /** This signal may come in before the occlusion signal, and can provide a custom transition */ + private fun listenForLockscreenToCamera() { + scope.launch { + keyguardInteractor.onCameraLaunchDetected + .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) + .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), ) ) } @@ -223,14 +251,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/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 402c1793f0b2..ac2d230ee605 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 @@ -58,6 +66,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 +128,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/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/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index 1fdbc99333cb..d5558b27ef1a 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 @@ -184,6 +184,7 @@ constructor( 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 +201,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 +724,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 +751,9 @@ constructor( isSsReactivated = isSsReactivated ) } + if (recreateMedia) { + reorderAllPlayers(previousVisibleKey) + } } } 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/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/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/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..91ebf79344b6 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,7 +686,6 @@ public class ScreenshotController { return true; } }); - if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { mScreenshotView.badgeScreenshot( mContext.getPackageManager().getUserBadgeForDensity(owner, 0)); @@ -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..899cdb74274f 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(); }); } 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/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..80d7bbc04440 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; @@ -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/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/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/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/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 198572a51760..8f2704521d95 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); 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/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/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..9a9387b96817 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -116,6 +116,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 +143,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 +235,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private GlobalSettings mGlobalSettings; private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; + @Mock + private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider; private final int mCurrentUserId = 100; @@ -1259,8 +1263,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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(); + when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false); mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true); @@ -1280,8 +1283,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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(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(); @@ -2407,7 +2409,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 ae8f419d4e64..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; @@ -68,6 +69,7 @@ import org.mockito.quality.Strictness; public class LockIconViewControllerBaseTest extends SysuiTestCase { protected static final String UNLOCKED_LABEL = "unlocked"; + protected static final String LOCKED_LABEL = "locked"; protected static final int PADDING = 10; protected MockitoSession mStaticMockSession; @@ -90,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; @@ -130,6 +133,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { Rect windowBounds = new Rect(0, 0, 800, 1200); when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds); when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL); + when(mResources.getString(R.string.accessibility_lock_icon)).thenReturn(LOCKED_LABEL); when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable); when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING); when(mAuthController.getScaleFactor()).thenReturn(1f); @@ -155,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/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index da40595a4f12..b69491ed1096 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -262,6 +262,26 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN the view is updated to NO translation (no burn-in offsets anymore) verify(mLockIconView).setTranslationY(0); verify(mLockIconView).setTranslationX(0); + } + + @Test + public void lockIconShows_afterBiometricsCleared() { + // GIVEN lock icon controller is initialized and view is attached + init(/* useMigrationFlag= */false); + captureKeyguardUpdateMonitorCallback(); + + // GIVEN user has unlocked with a biometric auth (ie: face auth) + // and biometric running state changes + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); + mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, + BiometricSourceType.FACE); + reset(mLockIconView); + + // WHEN biometrics are cleared + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); + mKeyguardUpdateMonitorCallback.onBiometricsCleared(); + // THEN the lock icon is shown + verify(mLockIconView).setContentDescription(LOCKED_LABEL); } } 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/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/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/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/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..754adfdc48b3 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,6 +67,7 @@ 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 @@ -85,7 +87,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository), + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), shadeRepository = shadeRepository, keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), @@ -95,7 +97,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository), + keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) 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/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index 6ca34e0bb7ce..039dd4d92eb4 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,6 +17,7 @@ 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 androidx.test.filters.SmallTest @@ -86,6 +87,9 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Mock lateinit var mediaViewController: MediaViewController @Mock lateinit var smartspaceMediaData: SmartspaceMediaData @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 +115,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { logger, debugLogger ) + verify(configurationController).addCallback(capture(configListener)) verify(mediaDataManager).addListener(capture(listener)) verify(visualStabilityProvider) .addPersistentReorderingAllowedListener(capture(visualStabilityCallback)) @@ -662,4 +667,39 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaCarouselController.updatePageIndicatorAlpha() assertEquals(mediaCarouselController.pageIndicator.alpha, 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/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/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/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/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 09254ad0faf2..5a219451198a 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. @@ -504,7 +506,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/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/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/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/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..148797f21834 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); + } @@ -3741,19 +3743,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 +3850,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 +4255,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 +7999,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 +10174,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/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/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/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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index de0d1f8f76af..5285f63dcc44 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1077,10 +1077,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // a tendency to hit the power button immediately when they pick up their device, and we // don't want to put the device back to sleep in those cases. final PowerManager.WakeData lastWakeUp = mPowerManagerInternal.getLastWakeup(); - if (lastWakeUp != null && lastWakeUp.wakeReason == PowerManager.WAKE_REASON_GESTURE) { - final int gestureDelayMillis = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.POWER_BUTTON_SUPPRESSION_DELAY_AFTER_GESTURE_WAKE, - POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS); + if (lastWakeUp != null && (lastWakeUp.wakeReason == PowerManager.WAKE_REASON_GESTURE + || lastWakeUp.wakeReason == PowerManager.WAKE_REASON_LIFT + || lastWakeUp.wakeReason == PowerManager.WAKE_REASON_BIOMETRIC)) { final long now = SystemClock.uptimeMillis(); if (mPowerButtonSuppressionDelayMillis > 0 && (now < lastWakeUp.wakeTime + mPowerButtonSuppressionDelayMillis)) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9215cabad25a..6b01a7726a43 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7938,6 +7938,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The smallest screen width is the short side of screen bounds. Because the bounds // and density won't be changed, smallestScreenWidthDp is also fixed. overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp; + // TODO(b/264276741): Check whether the runtime orietnation request is fixed rather than + // the manifest orientation which may be obsolete. if (info.isFixedOrientation()) { // lock rotation too. When in size-compat, onConfigurationChanged will watch for and // apply runtime rotation changes. @@ -8051,8 +8053,24 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateResolvedBoundsPosition(newParentConfiguration); } - if (mVisibleRequested) { - updateCompatDisplayInsets(); + boolean isIgnoreOrientationRequest = mDisplayContent != null + && mDisplayContent.getIgnoreOrientationRequest(); + if (mCompatDisplayInsets == null // for size compat mode set in updateCompatDisplayInsets + // Fixed orientation letterboxing is possible on both large screen devices + // with ignoreOrientationRequest enabled and on phones in split screen even with + // ignoreOrientationRequest disabled. + && (mLetterboxBoundsForFixedOrientationAndAspectRatio != null + // Limiting check for aspect ratio letterboxing to devices with enabled + // ignoreOrientationRequest. This avoids affecting phones where apps may + // not expect the change of smallestScreenWidthDp after rotation which is + // possible with this logic. Not having smallestScreenWidthDp completely + // accurate on phones shouldn't make the big difference and is expected + // to be already well-tested by apps. + || (isIgnoreOrientationRequest && mIsAspectRatioApplied))) { + // TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all + // rotations and only re-calculate if parent bounds have non-orientation size change. + resolvedConfig.smallestScreenWidthDp = + Math.min(resolvedConfig.screenWidthDp, resolvedConfig.screenHeightDp); } // Assign configuration sequence number into hierarchy because there is a different way than @@ -8440,7 +8458,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculate app bounds using fixed orientation bounds because they will be needed later // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(), - newParentConfig); + newParentConfig, mCompatDisplayInsets); mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds); } @@ -9119,6 +9137,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLastReportedDisplayId = newDisplayId; } + // Calling from here rather than from onConfigurationChanged because it's possible that + // onConfigurationChanged was called before mVisibleRequested became true and + // mCompatDisplayInsets may not be called again when mVisibleRequested changes. And we + // don't want to save mCompatDisplayInsets in onConfigurationChanged without visibility + // check to avoid remembering obsolete configuration which can lead to unnecessary + // size-compat mode. + if (mVisibleRequested) { + // Calling from here rather than resolveOverrideConfiguration to ensure that this is + // called after full config is updated in ConfigurationContainer#onConfigurationChanged. + updateCompatDisplayInsets(); + } + // Short circuit: if the two full configurations are equal (the common case), then there is // nothing to do. We test the full configuration instead of the global and merged override // configurations because there are cases (like moving a task to the root pinned task) where @@ -9127,12 +9157,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display " + "unchanged in %s", this); - // It's possible that resolveOverrideConfiguration was called before mVisibleRequested - // became true and mCompatDisplayInsets may not have been created so ensure - // that mCompatDisplayInsets is created here. - if (mVisibleRequested) { - updateCompatDisplayInsets(); - } return true; } 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/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 689cae5777cd..22da56ad4d3b 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2138,7 +2138,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final Rect parentBounds = parentConfig.windowConfiguration.getBounds(); final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds(); - if (resolvedBounds == null || resolvedBounds.isEmpty()) { + if (resolvedBounds.isEmpty()) { mTmpFullBounds.set(parentBounds); insideParentBounds = true; } else { @@ -2227,6 +2227,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { : overrideScreenHeightDp; } + // TODO(b/238331848): Consider simplifying logic that computes smallestScreenWidthDp. if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) { // When entering to or exiting from Pip, the PipTaskOrganizer will set the 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/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 ba4ff3762062..63607ad19f5a 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5683,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/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/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/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/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index e9080ab78fbc..a8e91980014e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1617,6 +1617,79 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testComputeConfigResourceOverrides_unresizableApp() { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + final Rect activityBounds = new Rect(mActivity.getBounds()); + + int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp; + int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp; + + // App should launch in fixed orientation letterbox. + // Activity bounds should be 700x1400 with the ratio as the display. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertFitted(); + assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp); + assertTrue(originalScreenWidthDp < originalScreenHeighthDp); + + // Rotate display to portrait. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + // After we rotate, the activity should go in the size-compat mode and report the same + // configuration values. + assertScaled(); + assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp); + assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp); + assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp); + + // Restart activity + mActivity.restartProcessIfVisible(); + + // Now configuration should be updated + assertFitted(); + assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp); + assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp); + assertEquals(mActivity.getConfiguration().screenWidthDp, + mActivity.getConfiguration().smallestScreenWidthDp); + } + + @Test + public void testComputeConfigResourceOverrides_resizableFixedOrientationActivity() { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + // Portrait fixed app without max aspect. + prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */); + + final Rect activityBounds = new Rect(mActivity.getBounds()); + + int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp; + int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp; + + // App should launch in fixed orientation letterbox. + // Activity bounds should be 700x1400 with the ratio as the display. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertFitted(); + assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp); + assertTrue(originalScreenWidthDp < originalScreenHeighthDp); + + // Rotate display to portrait. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + // Now configuration should be updated + assertFitted(); + assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp); + assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp); + assertEquals(mActivity.getConfiguration().screenWidthDp, + mActivity.getConfiguration().smallestScreenWidthDp); + } + + @Test public void testSplitAspectRatioForUnresizablePortraitApps() { // Set up a display in landscape and ignoring orientation request. int screenWidth = 1600; 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/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. |