diff options
268 files changed, 6316 insertions, 2029 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index dabb8b610c6e..e7194df0f97d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3087,6 +3087,7 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); + method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); method public boolean clearCache(); method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public final void disableSelf(); @@ -49980,6 +49981,7 @@ package android.view { public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable { ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage); method public int describeContents(); + method @NonNull public android.view.SurfaceControl getSurfaceControl(); method public void notifyConfigurationChanged(@NonNull android.content.res.Configuration); method public void notifyDetachedFromWindow(); method public void release(); @@ -54043,23 +54045,22 @@ package android.view.inputmethod { } public final class TextAppearanceInfo implements android.os.Parcelable { - ctor public TextAppearanceInfo(@NonNull android.widget.TextView); method public int describeContents(); - method @Nullable public String getFontFamilyName(); method @Nullable public String getFontFeatureSettings(); method @Nullable public String getFontVariationSettings(); + method @ColorInt public int getHighlightTextColor(); + method @ColorInt public int getHintTextColor(); method public float getLetterSpacing(); method public int getLineBreakStyle(); method public int getLineBreakWordStyle(); - method public int getMaxLength(); + method @ColorInt public int getLinkTextColor(); + method @ColorInt public int getShadowColor(); method @Px public float getShadowDx(); method @Px public float getShadowDy(); method @Px public float getShadowRadius(); + method @Nullable public String getSystemFontFamilyName(); method @ColorInt public int getTextColor(); - method @ColorInt public int getTextColorHighlight(); - method @ColorInt public int getTextColorHint(); - method @Nullable public android.content.res.ColorStateList getTextColorLink(); - method @IntRange(from=0xffffffff, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight(); + method @IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight(); method @NonNull public android.os.LocaleList getTextLocales(); method public float getTextScaleX(); method @Px public float getTextSize(); @@ -54071,6 +54072,33 @@ package android.view.inputmethod { field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAppearanceInfo> CREATOR; } + public static final class TextAppearanceInfo.Builder { + ctor public TextAppearanceInfo.Builder(); + method @NonNull public android.view.inputmethod.TextAppearanceInfo build(); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setAllCaps(boolean); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setElegantTextHeight(boolean); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFallbackLineSpacing(boolean); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontFeatureSettings(@Nullable String); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontVariationSettings(@Nullable String); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHighlightTextColor(@ColorInt int); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHintTextColor(@ColorInt int); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLetterSpacing(float); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakStyle(int); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakWordStyle(int); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLinkTextColor(@ColorInt int); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowColor(@ColorInt int); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDx(@Px float); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDy(@Px float); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowRadius(@Px float); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setSystemFontFamilyName(@Nullable String); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextColor(@ColorInt int); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextFontWeight(@IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) int); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextLocales(@NonNull android.os.LocaleList); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextScaleX(float); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextSize(@Px float); + method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextStyle(int); + } + public final class TextAttribute implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.os.PersistableBundle getExtras(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 51694133024c..baf1f3107e2c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -512,6 +512,10 @@ package android.app { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean startProfile(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public boolean stopProfile(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean switchUser(@NonNull android.os.UserHandle); + field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 + field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 + field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4 + field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 } public static interface ActivityManager.OnUidImportanceListener { @@ -1109,6 +1113,7 @@ package android.app.admin { method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set<java.lang.Integer> getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); @@ -1134,6 +1139,7 @@ package android.app.admin { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; + method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); @@ -1155,6 +1161,7 @@ package android.app.admin { field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER"; field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"; field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER"; + field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0 field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER"; field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION"; field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME"; @@ -3622,6 +3629,7 @@ package android.content.pm { field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000 field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000 field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000 + field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000 field @Deprecated public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000 field @Nullable public final String backgroundPermission; field @NonNull public java.util.Set<java.lang.String> knownCerts; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3fee610943ba..85d73ec7d65a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -143,11 +143,7 @@ package android.app { field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1 field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6 - field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 - field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 - field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4 field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8 - field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4 field public static final int PROCESS_STATE_TOP = 2; // 0x2 field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff @@ -863,10 +859,6 @@ package android.content.pm { field public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared"; } - public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { - field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000 - } - public final class ProviderInfoList implements android.os.Parcelable { method public int describeContents(); method @NonNull public static android.content.pm.ProviderInfoList fromList(@NonNull java.util.List<android.content.pm.ProviderInfo>); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 2fe5d5140371..02a81acf791a 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -56,7 +56,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.SurfaceView; +import android.view.SurfaceControl; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityCache; @@ -2467,8 +2467,8 @@ public abstract class AccessibilityService extends Service { * </p> * <p> * <strong>Note:</strong> If the view with {@link AccessibilityNodeInfo#FOCUS_INPUT} - * is on an embedded view hierarchy which is embedded in a {@link SurfaceView} via - * {@link SurfaceView#setChildSurfacePackage}, there is a limitation that this API + * is on an embedded view hierarchy which is embedded in a {@link android.view.SurfaceView} via + * {@link android.view.SurfaceView#setChildSurfacePackage}, there is a limitation that this API * won't be able to find the node for the view. It's because views don't know about * the embedded hierarchies. Instead, you could traverse all the nodes to find the * focus. @@ -3379,4 +3379,28 @@ public abstract class AccessibilityService extends Service { controller.onStateChanged(state); } } + + /** + * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the + * specified display. This type of overlay should be used for content that does not need to + * track the location and size of Views in the currently active app e.g. service configuration + * or general service UI. To remove this overlay and free the associated resources, use + * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>. + * + * @param displayId the display to which the SurfaceControl should be attached. + * @param sc the SurfaceControl containing the overlay content + */ + public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) { + Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getConnection(mConnectionId); + if (connection == null) { + return; + } + try { + connection.attachAccessibilityOverlayToDisplay(displayId, sc); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index da14b50e6481..da13e7353056 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -25,6 +25,7 @@ import android.os.Bundle; import android.os.RemoteCallback; import android.view.MagnificationSpec; import android.view.MotionEvent; +import android.view.SurfaceControl; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; @@ -155,4 +156,5 @@ interface IAccessibilityServiceConnection { void setInstalledAndEnabledServices(in List<AccessibilityServiceInfo> infos); List<AccessibilityServiceInfo> getInstalledAndEnabledServices(); + void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl sc); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d6c10ae26406..83963fd2141d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -628,19 +628,19 @@ public class ActivityManager { public @interface ProcessCapability {} /** @hide Process does not have any capability */ - @TestApi + @SystemApi public static final int PROCESS_CAPABILITY_NONE = 0; /** @hide Process can access location while in foreground */ - @TestApi + @SystemApi public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 0; /** @hide Process can access camera while in foreground */ - @TestApi + @SystemApi public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 1; /** @hide Process can access microphone while in foreground */ - @TestApi + @SystemApi public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2; /** @hide Process can access network despite any power saving resrictions */ @@ -4398,8 +4398,6 @@ public class ActivityManager { * * @throws UnsupportedOperationException if the device does not support background users on * secondary displays. - * @throws IllegalArgumentException if the display doesn't exist or is not a valid display to - * start secondary users on. * * @hide */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1f6334381bc9..e001c7df506e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -40,7 +40,6 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK import android.annotation.NonNull; import android.annotation.Nullable; import android.app.RemoteServiceException.BadForegroundServiceNotificationException; -import android.app.RemoteServiceException.CannotDeliverBroadcastException; import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException; import android.app.RemoteServiceException.CrashedByAdbException; import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; @@ -1994,9 +1993,6 @@ public final class ActivityThread extends ClientTransactionHandler case ForegroundServiceDidNotStartInTimeException.TYPE_ID: throw generateForegroundServiceDidNotStartInTimeException(message, extras); - case CannotDeliverBroadcastException.TYPE_ID: - throw new CannotDeliverBroadcastException(message); - case CannotPostForegroundServiceNotificationException.TYPE_ID: throw new CannotPostForegroundServiceNotificationException(message); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1b3282e752f4..d5879fb523ce 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1407,9 +1407,41 @@ public class AppOpsManager { public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE = AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE; + /** + * Exempt from start foreground service from background restriction. + * + * Only to be used by the system. + * + * @hide + */ + public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION = + AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION; + + /** + * Exempt from start foreground service from background with while in user permission + * restriction. + * + * Only to be used by the system. + * + * @hide + */ + public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION = + AppProtoEnums + .APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION; + + /** + * Hide foreground service stop button in quick settings. + * + * Only to be used by the system. + * + * @hide + */ + public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON = + AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 128; + public static final int _NUM_OP = 131; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1947,6 +1979,38 @@ public class AppOpsManager { public static final String OPSTR_FOREGROUND_SERVICE_SPECIAL_USE = "android:foreground_service_special_use"; + /** + * Exempt from start foreground service from background restriction. + * + * Only to be used by the system. + * + * @hide + */ + public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION = + "android:system_exempt_from_fgs_bg_start_restriction"; + + /** + * Exempt from start foreground service from background with while in user permission + * restriction. + * + * Only to be used by the system. + * + * @hide + */ + public static final String + OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION = + "android:system_exempt_from_fgs_bg_start_while_in_use_permission_restriction"; + + /** + * Hide foreground service stop button in quick settings. + * + * Only to be used by the system. + * + * @hide + */ + public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON = + "android:system_exempt_from_fgs_stop_button"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2441,6 +2505,17 @@ public class AppOpsManager { new AppOpInfo.Builder(OP_FOREGROUND_SERVICE_SPECIAL_USE, OPSTR_FOREGROUND_SERVICE_SPECIAL_USE, "FOREGROUND_SERVICE_SPECIAL_USE") .setPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE).build(), + new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION, + OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION, + "SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION").build(), + new AppOpInfo.Builder( + OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION, + OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION, + "SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION") + .build(), + new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON, + OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON, + "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build() }; // The number of longs needed to form a full bitmask of app ops @@ -2498,12 +2573,6 @@ public class AppOpsManager { sPermToOp.put(sAppOpInfos[op].permission, op); } } - - if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) { - // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is - // two longs - throw new IllegalStateException("notedAppOps collection code assumes < 128 appops"); - } } /** Config used to control app ops access messages sampling */ @@ -2603,8 +2672,8 @@ public class AppOpsManager { if (boxedOpCode != null) { return boxedOpCode; } - if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(), - permission)) { + if (permission != null && HealthConnectManager.isHealthPermission( + ActivityThread.currentApplication(), permission)) { return OP_READ_WRITE_HEALTH_DATA; } return OP_NONE; diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 45d44589b2d8..1777f37202c2 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -63,7 +63,6 @@ public class BroadcastOptions extends ComponentOptions { private long mRequireCompatChangeId = CHANGE_INVALID; private boolean mRequireCompatChangeEnabled = true; private boolean mIsAlarmBroadcast = false; - private boolean mIsInteractiveBroadcast = false; private long mIdForResponseEvent; private @Nullable IntentFilter mRemoveMatchingFilter; private @DeliveryGroupPolicy int mDeliveryGroupPolicy; @@ -171,13 +170,6 @@ public class BroadcastOptions extends ComponentOptions { "android:broadcast.is_alarm"; /** - * Corresponds to {@link #setInteractiveBroadcast(boolean)} - * @hide - */ - public static final String KEY_INTERACTIVE_BROADCAST = - "android:broadcast.is_interactive"; - - /** * @hide * @deprecated Use {@link android.os.PowerExemptionManager# * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead. @@ -308,7 +300,6 @@ public class BroadcastOptions extends ComponentOptions { mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true); mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT); mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false); - mIsInteractiveBroadcast = opts.getBoolean(KEY_INTERACTIVE_BROADCAST, false); mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER, IntentFilter.class); mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY, @@ -629,28 +620,6 @@ public class BroadcastOptions extends ComponentOptions { } /** - * When set, this broadcast will be understood as having originated from - * some direct interaction by the user such as a notification tap or button - * press. Only the OS itself may use this option. - * @hide - * @param broadcastIsInteractive - * @see #isInteractiveBroadcast() - */ - @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE) - public void setInteractiveBroadcast(boolean broadcastIsInteractive) { - mIsInteractiveBroadcast = broadcastIsInteractive; - } - - /** - * Did this broadcast originate with a direct user interaction? - * @return true if this broadcast is the result of an interaction, false otherwise - * @hide - */ - public boolean isInteractiveBroadcast() { - return mIsInteractiveBroadcast; - } - - /** * Did this broadcast originate from a push message from the server? * * @return true if this broadcast is a push message, false otherwise. @@ -837,9 +806,6 @@ public class BroadcastOptions extends ComponentOptions { if (mIsAlarmBroadcast) { b.putBoolean(KEY_ALARM_BROADCAST, true); } - if (mIsInteractiveBroadcast) { - b.putBoolean(KEY_INTERACTIVE_BROADCAST, true); - } if (mMinManifestReceiverApiLevel != 0) { b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel); } diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java index 4e5e384a2798..74db39f63830 100644 --- a/core/java/android/app/ComponentOptions.java +++ b/core/java/android/app/ComponentOptions.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.RequiresPermission; import android.os.Bundle; /** @@ -45,8 +46,15 @@ public class ComponentOptions { public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION = "android.pendingIntent.backgroundActivityAllowedByPermission"; + /** + * Corresponds to {@link #setInteractive(boolean)} + * @hide + */ + public static final String KEY_INTERACTIVE = "android:component.isInteractive"; + private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT; private boolean mPendingIntentBalAllowedByPermission = false; + private boolean mIsInteractive = false; ComponentOptions() { } @@ -61,6 +69,29 @@ public class ComponentOptions { setPendingIntentBackgroundActivityLaunchAllowedByPermission( opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false)); + mIsInteractive = opts.getBoolean(KEY_INTERACTIVE, false); + } + + /** + * When set, a broadcast will be understood as having originated from + * some direct interaction by the user such as a notification tap or button + * press. Only the OS itself may use this option. + * @hide + * @param interactive + * @see #isInteractive() + */ + @RequiresPermission(android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE) + public void setInteractive(boolean interactive) { + mIsInteractive = interactive; + } + + /** + * Did this PendingIntent send originate with a direct user interaction? + * @return true if this is the result of an interaction, false otherwise + * @hide + */ + public boolean isInteractive() { + return mIsInteractive; } /** @@ -103,6 +134,9 @@ public class ComponentOptions { b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, mPendingIntentBalAllowedByPermission); } + if (mIsInteractive) { + b.putBoolean(KEY_INTERACTIVE, true); + } return b; } } diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index 37c5cabc2376..811118479ef8 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -16,9 +16,12 @@ package android.app; +import android.app.backup.BackupRestoreEventLogger; import android.app.backup.IBackupCallback; import android.app.backup.IBackupManager; import android.os.ParcelFileDescriptor; + +import com.android.internal.infra.AndroidFuture; /** * Interface presented by applications being asked to participate in the @@ -193,4 +196,14 @@ oneway interface IBackupAgent { * @param message The message to be passed to the agent's application in an exception. */ void fail(String message); + + /** + * Provides the logging results that were accumulated in the BackupAgent during a backup or + * restore operation. This method should be called after the agent completes its backup or + * restore. + * + * @param resultsFuture a future that is completed with the logging results. + */ + void getLoggerResults( + in AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture); } diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java index e220627706f9..620adbedc903 100644 --- a/core/java/android/app/RemoteServiceException.java +++ b/core/java/android/app/RemoteServiceException.java @@ -72,21 +72,6 @@ public class RemoteServiceException extends AndroidRuntimeException { /** * Exception used to crash an app process when the system received a RemoteException - * while delivering a broadcast to an app process. - * - * @hide - */ - public static class CannotDeliverBroadcastException extends RemoteServiceException { - /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ - public static final int TYPE_ID = 2; - - public CannotDeliverBroadcastException(String msg) { - super(msg); - } - } - - /** - * Exception used to crash an app process when the system received a RemoteException * while posting a notification of a foreground service. * * @hide @@ -94,7 +79,7 @@ public class RemoteServiceException extends AndroidRuntimeException { public static class CannotPostForegroundServiceNotificationException extends RemoteServiceException { /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ - public static final int TYPE_ID = 3; + public static final int TYPE_ID = 2; public CannotPostForegroundServiceNotificationException(String msg) { super(msg); @@ -109,7 +94,7 @@ public class RemoteServiceException extends AndroidRuntimeException { */ public static class BadForegroundServiceNotificationException extends RemoteServiceException { /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ - public static final int TYPE_ID = 4; + public static final int TYPE_ID = 3; public BadForegroundServiceNotificationException(String msg) { super(msg); @@ -125,7 +110,7 @@ public class RemoteServiceException extends AndroidRuntimeException { public static class MissingRequestPasswordComplexityPermissionException extends RemoteServiceException { /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ - public static final int TYPE_ID = 5; + public static final int TYPE_ID = 4; public MissingRequestPasswordComplexityPermissionException(String msg) { super(msg); @@ -139,7 +124,7 @@ public class RemoteServiceException extends AndroidRuntimeException { */ public static class CrashedByAdbException extends RemoteServiceException { /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ - public static final int TYPE_ID = 6; + public static final int TYPE_ID = 5; public CrashedByAdbException(String msg) { super(msg); diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 7167d4f17a6d..754e3b628f79 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -1106,4 +1106,16 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac return sStartForegroundServiceStackTraces.get(className); } } + + /** + * Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}. + * + * TODO Implement it + * TODO Javadoc + * + * @param startId + * @hide + */ + public void onTimeout(int startId) { + } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6fedb41884ec..be4df9d24c25 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -103,6 +103,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.net.NetworkUtilsInternal; import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.org.conscrypt.TrustedCertificateStore; @@ -3823,6 +3824,27 @@ public class DevicePolicyManager { public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; /** + * Prevent an app from being placed into app standby buckets, such that it will not be subject + * to device resources restrictions as a result of app standby buckets. + * + * @hide + */ + @SystemApi + public static final int EXEMPT_FROM_APP_STANDBY = 0; + + /** + * Exemptions to platform restrictions, given to an application through + * {@link #setApplicationExemptions(String, Set)}. + * + * @hide + */ + @IntDef(prefix = { "EXEMPT_FROM_"}, value = { + EXEMPT_FROM_APP_STANDBY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ApplicationExemptionConstants {} + + /** * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management * resources with IDs {@link #EXTRA_RESOURCE_IDS} has been updated, the updated resources can be * retrieved using {@link DevicePolicyResourcesManager#getDrawable} and @@ -14727,6 +14749,95 @@ public class DevicePolicyManager { } /** + * Service-specific error code used in {@link #setApplicationExemptions(String, Set)} and + * {@link #getApplicationExemptions(String)}. + * @hide + */ + public static final int ERROR_PACKAGE_NAME_NOT_FOUND = 1; + + /** + * Called by an application with the + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission, to + * grant platform restriction exemptions to a given application. + * + * @param packageName The package name of the application to be exempt. + * @param exemptions The set of exemptions to be applied. + * @throws SecurityException If the caller does not have + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} + * @throws NameNotFoundException If either the package is not installed or the package is not + * visible to the caller. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) + public void setApplicationExemptions(@NonNull String packageName, + @NonNull @ApplicationExemptionConstants Set<Integer> exemptions) + throws NameNotFoundException { + throwIfParentInstance("setApplicationExemptions"); + if (mService != null) { + try { + mService.setApplicationExemptions(packageName, + ArrayUtils.convertToIntArray(new ArraySet<>(exemptions))); + } catch (ServiceSpecificException e) { + switch (e.errorCode) { + case ERROR_PACKAGE_NAME_NOT_FOUND: + throw new NameNotFoundException(e.getMessage()); + default: + throw new RuntimeException( + "Unknown error setting application exemptions: " + e.errorCode, e); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns all the platform restriction exemptions currently applied to an application. Called + * by an application with the + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission. + * + * @param packageName The package name to check. + * @return A set of platform restrictions an application is exempt from. + * @throws SecurityException If the caller does not have + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} + * @throws NameNotFoundException If either the package is not installed or the package is not + * visible to the caller. + * @hide + */ + @NonNull + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) + public Set<Integer> getApplicationExemptions(@NonNull String packageName) + throws NameNotFoundException { + throwIfParentInstance("getApplicationExemptions"); + if (mService == null) { + return Collections.emptySet(); + } + try { + return intArrayToSet(mService.getApplicationExemptions(packageName)); + } catch (ServiceSpecificException e) { + switch (e.errorCode) { + case ERROR_PACKAGE_NAME_NOT_FOUND: + throw new NameNotFoundException(e.getMessage()); + default: + throw new RuntimeException( + "Unknown error getting application exemptions: " + e.errorCode, e); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private Set<Integer> intArrayToSet(int[] array) { + Set<Integer> set = new ArraySet<>(); + for (int item : array) { + set.add(item); + } + return set; + } + + /** * Called by a device owner or a profile owner to disable user control over apps. User will not * be able to clear app data or force-stop packages. When called by a device owner, applies to * all users on the device. Starting from Android 13, packages with user control disabled are diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 6c27dd7b771b..8a4026539267 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -568,4 +568,7 @@ interface IDevicePolicyManager { boolean shouldAllowBypassingDevicePolicyManagementRoleQualification(); List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle); + + void setApplicationExemptions(String packageName, in int[]exemptions); + int[] getApplicationExemptions(String packageName); } diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index b1b59b0e39b1..a4f612d7faee 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -41,6 +41,7 @@ import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; import libcore.io.IoUtils; @@ -202,6 +203,7 @@ public abstract class BackupAgent extends ContextWrapper { Handler mHandler = null; + @Nullable private volatile BackupRestoreEventLogger mLogger = null; @Nullable private UserHandle mUser; // This field is written from the main thread (in onCreate), and read in a Binder thread (in // onFullBackup that is called from system_server via Binder). @@ -234,6 +236,20 @@ public abstract class BackupAgent extends ContextWrapper { } catch (InterruptedException e) { /* ignored */ } } + /** + * Get a logger to record app-specific backup and restore events that are happening during a + * backup or restore operation. + * + * <p>The logger instance had been created by the system with the correct {@link + * BackupRestoreEventLogger.OperationType} that corresponds to the operation the {@code + * BackupAgent} is currently handling. + * + * @hide + */ + @Nullable + public BackupRestoreEventLogger getBackupRestoreEventLogger() { + return mLogger; + } public BackupAgent() { super(null); @@ -264,6 +280,9 @@ public abstract class BackupAgent extends ContextWrapper { * @hide */ public void onCreate(UserHandle user, @OperationType int operationType) { + // TODO: Instantiate with the correct type using a parameter. + mLogger = new BackupRestoreEventLogger(BackupRestoreEventLogger.OperationType.BACKUP); + onCreate(); mUser = user; @@ -1305,6 +1324,16 @@ public abstract class BackupAgent extends ContextWrapper { } } } + + @Override + public void getLoggerResults( + AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> in) { + if (mLogger != null) { + in.complete(mLogger.getLoggingResults()); + } else { + in.complete(Collections.emptyList()); + } + } } static class FailRunnable implements Runnable { diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.aidl b/core/java/android/app/backup/BackupRestoreEventLogger.aidl new file mode 100644 index 000000000000..d6ef4e64258d --- /dev/null +++ b/core/java/android/app/backup/BackupRestoreEventLogger.aidl @@ -0,0 +1,19 @@ +/* + * 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.app.backup; + +parcelable BackupRestoreEventLogger.DataTypeResult;
\ No newline at end of file diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java index 6f62c8a03078..68740cb3c086 100644 --- a/core/java/android/app/backup/BackupRestoreEventLogger.java +++ b/core/java/android/app/backup/BackupRestoreEventLogger.java @@ -19,6 +19,10 @@ package android.app.backup; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; import android.util.Slog; import java.lang.annotation.Retention; @@ -312,7 +316,7 @@ public class BackupRestoreEventLogger { /** * Encapsulate logging results for a single data type. */ - public static class DataTypeResult { + public static class DataTypeResult implements Parcelable { @BackupRestoreDataType private final String mDataType; private int mSuccessCount; @@ -362,5 +366,57 @@ public class BackupRestoreEventLogger { public byte[] getMetadataHash() { return mMetadataHash; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mDataType); + + dest.writeInt(mSuccessCount); + + dest.writeInt(mFailCount); + + Bundle errorsBundle = new Bundle(); + for (Map.Entry<String, Integer> e : mErrors.entrySet()) { + errorsBundle.putInt(e.getKey(), e.getValue()); + } + dest.writeBundle(errorsBundle); + + dest.writeByteArray(mMetadataHash); + } + + public static final Parcelable.Creator<DataTypeResult> CREATOR = + new Parcelable.Creator<>() { + public DataTypeResult createFromParcel(Parcel in) { + String dataType = in.readString(); + + int successCount = in.readInt(); + + int failCount = in.readInt(); + + Map<String, Integer> errors = new ArrayMap<>(); + Bundle errorsBundle = in.readBundle(getClass().getClassLoader()); + for (String key : errorsBundle.keySet()) { + errors.put(key, errorsBundle.getInt(key)); + } + + byte[] metadataHash = in.createByteArray(); + + DataTypeResult result = new DataTypeResult(dataType); + result.mSuccessCount = successCount; + result.mFailCount = failCount; + result.mErrors.putAll(errors); + result.mMetadataHash = metadataHash; + return result; + } + + public DataTypeResult[] newArray(int size) { + return new DataTypeResult[size]; + } + }; } } diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java index 2581daa2f68b..d628b7f92c6c 100644 --- a/core/java/android/app/prediction/AppPredictor.java +++ b/core/java/android/app/prediction/AppPredictor.java @@ -30,6 +30,8 @@ import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + import dalvik.system.CloseGuard; import java.util.List; @@ -79,6 +81,7 @@ public final class AppPredictor { private final AtomicBoolean mIsClosed = new AtomicBoolean(false); private final AppPredictionSessionId mSessionId; + @GuardedBy("itself") private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>(); /** @@ -94,7 +97,7 @@ public final class AppPredictor { IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE); mPredictionManager = IPredictionManager.Stub.asInterface(b); mSessionId = new AppPredictionSessionId( - context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId()); + context.getPackageName() + ":" + UUID.randomUUID(), context.getUserId()); try { mPredictionManager.createPredictionSession(predictionContext, mSessionId, getToken()); } catch (RemoteException e) { @@ -155,6 +158,15 @@ public final class AppPredictor { */ public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor, @NonNull AppPredictor.Callback callback) { + synchronized (mRegisteredCallbacks) { + registerPredictionUpdatesLocked(callbackExecutor, callback); + } + } + + @GuardedBy("mRegisteredCallbacks") + private void registerPredictionUpdatesLocked( + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull AppPredictor.Callback callback) { if (mIsClosed.get()) { throw new IllegalStateException("This client has already been destroyed."); } @@ -183,6 +195,13 @@ public final class AppPredictor { * @param callback The callback to be unregistered. */ public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) { + synchronized (mRegisteredCallbacks) { + unregisterPredictionUpdatesLocked(callback); + } + } + + @GuardedBy("mRegisteredCallbacks") + private void unregisterPredictionUpdatesLocked(@NonNull AppPredictor.Callback callback) { if (mIsClosed.get()) { throw new IllegalStateException("This client has already been destroyed."); } @@ -235,7 +254,7 @@ public final class AppPredictor { } try { - mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets), + mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice<>(targets), new CallbackWrapper(callbackExecutor, callback)); } catch (RemoteException e) { Log.e(TAG, "Failed to sort targets", e); @@ -251,19 +270,25 @@ public final class AppPredictor { if (!mIsClosed.getAndSet(true)) { mCloseGuard.close(); - // Do destroy; - try { - mPredictionManager.onDestroyPredictionSession(mSessionId); - } catch (RemoteException e) { - Log.e(TAG, "Failed to notify app target event", e); - e.rethrowAsRuntimeException(); + synchronized (mRegisteredCallbacks) { + destroySessionLocked(); } - mRegisteredCallbacks.clear(); } else { throw new IllegalStateException("This client has already been destroyed."); } } + @GuardedBy("mRegisteredCallbacks") + private void destroySessionLocked() { + try { + mPredictionManager.onDestroyPredictionSession(mSessionId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify app target event", e); + e.rethrowAsRuntimeException(); + } + mRegisteredCallbacks.clear(); + } + @Override protected void finalize() throws Throwable { try { diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS index b95f8bb72218..0a7d079b8939 100644 --- a/core/java/android/content/pm/OWNERS +++ b/core/java/android/content/pm/OWNERS @@ -1,13 +1,11 @@ # Bug component: 36137 -patb@google.com +file:/PACKAGE_MANAGER_OWNERS -per-file Package* = file:/PACKAGE_MANAGER_OWNERS per-file PackageParser.java = set noparent per-file PackageParser.java = chiuwinson@google.com,patb@google.com per-file *Capability* = file:/core/java/android/content/pm/SHORTCUT_OWNERS per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS -per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS per-file UserInfo* = file:/MULTIUSER_OWNERS per-file *UserProperties* = file:/MULTIUSER_OWNERS diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 7c22c08860b1..61fc6f6775c1 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -22,7 +22,6 @@ import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.SuppressLint; import android.annotation.SystemApi; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -184,7 +183,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { * * @hide */ - @TestApi + @SystemApi public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 0x8000; /** diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index ab20b6f7f9cc..14f03eae8bf1 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -364,6 +364,28 @@ public class ServiceInfo extends ComponentInfo public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10; /** + * Foreground service type corresponding to {@code shortService} in + * the {@link android.R.attr#foregroundServiceType} attribute. + * + * TODO Implement it + * + * TODO Expand the javadoc + * + * This type is not associated with specific use cases unlike other types, but this has + * unique restrictions. + * <ul> + * <li>Has a timeout + * <li>Cannot start other foreground services from this + * <li> + * </ul> + * + * @see Service#onTimeout + * + * @hide + */ + public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11; + + /** * Constant corresponding to {@code specialUse} in * the {@link android.R.attr#foregroundServiceType} attribute. * Use cases that can't be categorized into any other foreground service types, but also @@ -446,6 +468,7 @@ public class ServiceInfo extends ComponentInfo FOREGROUND_SERVICE_TYPE_HEALTH, FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING, FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED, + FOREGROUND_SERVICE_TYPE_SHORT_SERVICE, FOREGROUND_SERVICE_TYPE_SPECIAL_USE }) @Retention(RetentionPolicy.SOURCE) @@ -528,6 +551,8 @@ public class ServiceInfo extends ComponentInfo return "remoteMessaging"; case FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED: return "systemExempted"; + case FOREGROUND_SERVICE_TYPE_SHORT_SERVICE: + return "shortService"; case FOREGROUND_SERVICE_TYPE_SPECIAL_USE: return "specialUse"; default: diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index c9a0626341b7..04d57ad8993f 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -22,7 +22,9 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; +import android.app.PendingIntent; import android.content.Context; +import android.content.IntentSender; import android.os.CancellationSignal; import android.os.ICancellationSignal; import android.os.OutcomeReceiver; @@ -84,8 +86,11 @@ public final class CredentialManager { ICancellationSignal cancelRemote = null; try { - cancelRemote = mService.executeGetCredential(request, - new GetCredentialTransport(executor, callback), mContext.getOpPackageName()); + cancelRemote = mService.executeGetCredential( + request, + // TODO: use a real activity instead of context. + new GetCredentialTransport(mContext, executor, callback), + mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -124,7 +129,8 @@ public final class CredentialManager { ICancellationSignal cancelRemote = null; try { cancelRemote = mService.executeCreateCredential(request, - new CreateCredentialTransport(executor, callback), + // TODO: use a real activity instead of context. + new CreateCredentialTransport(mContext, executor, callback), mContext.getOpPackageName()); } catch (RemoteException e) { e.rethrowFromSystemServer(); @@ -176,17 +182,30 @@ public final class CredentialManager { private static class GetCredentialTransport extends IGetCredentialCallback.Stub { // TODO: listen for cancellation to release callback. + private final Context mActivityContext; private final Executor mExecutor; private final OutcomeReceiver< GetCredentialResponse, CredentialManagerException> mCallback; - private GetCredentialTransport(Executor executor, + private GetCredentialTransport(Context activityContext, Executor executor, OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) { + mActivityContext = activityContext; mExecutor = executor; mCallback = callback; } @Override + public void onPendingIntent(PendingIntent pendingIntent) { + try { + mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0); + } catch (IntentSender.SendIntentException e) { + Log.e(TAG, "startIntentSender() failed for intent:" + + pendingIntent.getIntentSender(), e); + // TODO: propagate the error. + } + } + + @Override public void onResponse(GetCredentialResponse response) { mExecutor.execute(() -> mCallback.onResult(response)); } @@ -201,17 +220,30 @@ public final class CredentialManager { private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub { // TODO: listen for cancellation to release callback. + private final Context mActivityContext; private final Executor mExecutor; private final OutcomeReceiver< CreateCredentialResponse, CredentialManagerException> mCallback; - private CreateCredentialTransport(Executor executor, + private CreateCredentialTransport(Context activityContext, Executor executor, OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) { + mActivityContext = activityContext; mExecutor = executor; mCallback = callback; } @Override + public void onPendingIntent(PendingIntent pendingIntent) { + try { + mActivityContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0); + } catch (IntentSender.SendIntentException e) { + Log.e(TAG, "startIntentSender() failed for intent:" + + pendingIntent.getIntentSender(), e); + // TODO: propagate the error. + } + } + + @Override public void onResponse(CreateCredentialResponse response) { mExecutor.execute(() -> mCallback.onResult(response)); } diff --git a/core/java/android/credentials/ICreateCredentialCallback.aidl b/core/java/android/credentials/ICreateCredentialCallback.aidl index 75620faf04fd..87fd36fe78f4 100644 --- a/core/java/android/credentials/ICreateCredentialCallback.aidl +++ b/core/java/android/credentials/ICreateCredentialCallback.aidl @@ -16,6 +16,7 @@ package android.credentials; +import android.app.PendingIntent; import android.credentials.CreateCredentialResponse; /** @@ -24,6 +25,7 @@ import android.credentials.CreateCredentialResponse; * @hide */ interface ICreateCredentialCallback { + oneway void onPendingIntent(in PendingIntent pendingIntent); oneway void onResponse(in CreateCredentialResponse response); oneway void onError(int errorCode, String message); }
\ No newline at end of file diff --git a/core/java/android/credentials/IGetCredentialCallback.aidl b/core/java/android/credentials/IGetCredentialCallback.aidl index 92e585142a46..da152bad2da9 100644 --- a/core/java/android/credentials/IGetCredentialCallback.aidl +++ b/core/java/android/credentials/IGetCredentialCallback.aidl @@ -16,6 +16,7 @@ package android.credentials; +import android.app.PendingIntent; import android.credentials.GetCredentialResponse; /** @@ -24,6 +25,7 @@ import android.credentials.GetCredentialResponse; * @hide */ interface IGetCredentialCallback { + oneway void onPendingIntent(in PendingIntent pendingIntent); oneway void onResponse(in GetCredentialResponse response); oneway void onError(int errorCode, String message); }
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 8bb6fa5a1a14..ea3e4a807baf 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -2498,12 +2498,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p><b>Available values for this device:</b><br> * {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> - * <p><b>Limited capability</b> - - * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the - * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * * @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES - * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see #CONTROL_SETTINGS_OVERRIDE_OFF * @see #CONTROL_SETTINGS_OVERRIDE_ZOOM */ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index c5246b52a6cf..285c9331fd4a 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2702,12 +2702,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p><b>Available values for this device:</b><br> * {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> - * <p><b>Limited capability</b> - - * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the - * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> * * @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES - * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see #CONTROL_SETTINGS_OVERRIDE_OFF * @see #CONTROL_SETTINGS_OVERRIDE_ZOOM */ diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index bdd45e6df448..dba1a5e8dfc6 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -52,6 +52,22 @@ public final class DeviceStateManager { /** The maximum allowed device state identifier. */ public static final int MAXIMUM_DEVICE_STATE = 255; + /** + * Intent needed to launch the rear display overlay activity from SysUI + * + * @hide + */ + public static final String ACTION_SHOW_REAR_DISPLAY_OVERLAY = + "com.android.intent.action.SHOW_REAR_DISPLAY_OVERLAY"; + + /** + * Intent extra sent to the rear display overlay activity of the current base state + * + * @hide + */ + public static final String EXTRA_ORIGINAL_DEVICE_BASE_STATE = + "original_device_base_state"; + private final DeviceStateManagerGlobal mGlobal; /** @hide */ diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index 738045dafdf1..7756b9ca7e5a 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -51,7 +51,7 @@ public final class DeviceStateManagerGlobal { * connection with the device state service couldn't be established. */ @Nullable - static DeviceStateManagerGlobal getInstance() { + public static DeviceStateManagerGlobal getInstance() { synchronized (DeviceStateManagerGlobal.class) { if (sInstance == null) { IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE); @@ -259,6 +259,22 @@ public final class DeviceStateManagerGlobal { } } + /** + * Provides notification to the system server that a device state feature overlay + * was dismissed. This should only be called from the {@link android.app.Activity} that + * was showing the overlay corresponding to the feature. + * + * Validation of there being an overlay visible and pending state request is handled on the + * system server. + */ + public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) { + try { + mDeviceStateManager.onStateRequestOverlayDismissed(shouldCancelRequest); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private void registerCallbackIfNeededLocked() { if (mCallback == null) { mCallback = new DeviceStateManagerCallback(); diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl index 7175eae58a26..099316099738 100644 --- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl +++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl @@ -103,4 +103,15 @@ interface IDeviceStateManager { @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)") void cancelBaseStateOverride(); + + /** + * Notifies the system service that the educational overlay that was launched + * before entering a requested state was dismissed or closed, and provides + * the system information on if the pairing mode should be canceled or not. + * + * This should only be called from the overlay itself. + */ + @JavaPassthrough(annotation= + "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)") + void onStateRequestOverlayDismissed(boolean shouldCancelRequest); } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 6b3e67309f22..1c2c895a1912 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -412,7 +412,7 @@ public abstract class DisplayManagerInternal { * the PowerManagerService to focus on the global power state and not * have to micro-manage screen off animations, auto-brightness and other effects. */ - public static final class DisplayPowerRequest { + public static class DisplayPowerRequest { // Policy: Turn screen off as if the user pressed the power button // including playing a screen off animation if applicable. public static final int POLICY_OFF = 0; diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java index c7450d8fa65d..ad51eb894e22 100644 --- a/core/java/android/hardware/input/VirtualTouchEvent.java +++ b/core/java/android/hardware/input/VirtualTouchEvent.java @@ -262,6 +262,16 @@ public final class VirtualTouchEvent implements Parcelable { /** * Sets the pressure of the event. This field is optional and can be omitted. * + * @param pressure The pressure of the touch. + * Note: The VirtualTouchscreen, consuming VirtualTouchEvents, is + * configured with a pressure axis range from 0.0 to 255.0. Only the + * lower end of the range is enforced. You can pass values larger than + * 255.0. With physical input devices this could happen if the + * calibration is off. Values larger than 255.0 will not be trimmed and + * passed on as is. + * + * @throws IllegalArgumentException if the pressure is smaller than 0. + * * @return this builder, to allow for chaining of calls */ public @NonNull Builder setPressure(@FloatRange(from = 0f) float pressure) { diff --git a/core/java/android/hardware/radio/OWNERS b/core/java/android/hardware/radio/OWNERS index d2bdd643b0a2..302fdd73ccf3 100644 --- a/core/java/android/hardware/radio/OWNERS +++ b/core/java/android/hardware/radio/OWNERS @@ -1,3 +1,4 @@ xuweilin@google.com oscarazu@google.com +ericjeong@google.com keunyoung@google.com diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6eeb5e7f4b0f..8a09cd77efdb 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -25,9 +25,6 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.app.ActivityThread; -import android.content.ContentResolver; -import android.content.Context; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.net.Uri; @@ -882,9 +879,8 @@ public final class DeviceConfig { @NonNull @RequiresPermission(READ_DEVICE_CONFIG) public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) { - ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); return new Properties(namespace, - Settings.Config.getStrings(contentResolver, namespace, Arrays.asList(names))); + Settings.Config.getStrings(namespace, Arrays.asList(names))); } /** @@ -1023,8 +1019,7 @@ public final class DeviceConfig { @RequiresPermission(WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault) { - ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); - return Settings.Config.putString(contentResolver, namespace, name, value, makeDefault); + return Settings.Config.putString(namespace, name, value, makeDefault); } /** @@ -1045,8 +1040,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull Properties properties) throws BadConfigException { - ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); - return Settings.Config.setStrings(contentResolver, properties.getNamespace(), + return Settings.Config.setStrings(properties.getNamespace(), properties.mMap); } @@ -1062,8 +1056,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) { - ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); - return Settings.Config.deleteString(contentResolver, namespace, name); + return Settings.Config.deleteString(namespace, name); } /** @@ -1094,8 +1087,7 @@ public final class DeviceConfig { @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) { - ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); - Settings.Config.resetToDefaults(contentResolver, resetMode, namespace); + Settings.Config.resetToDefaults(resetMode, namespace); } /** @@ -1112,8 +1104,7 @@ public final class DeviceConfig { */ @RequiresPermission(WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) { - ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); - Settings.Config.setSyncDisabledMode(contentResolver, syncDisabledMode); + Settings.Config.setSyncDisabledMode(syncDisabledMode); } /** @@ -1124,8 +1115,7 @@ public final class DeviceConfig { */ @RequiresPermission(WRITE_DEVICE_CONFIG) public static @SyncDisabledMode int getSyncDisabledMode() { - ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); - return Settings.Config.getSyncDisabledMode(contentResolver); + return Settings.Config.getSyncDisabledMode(); } /** @@ -1148,8 +1138,7 @@ public final class DeviceConfig { @NonNull String namespace, @NonNull @CallbackExecutor Executor executor, @NonNull OnPropertiesChangedListener onPropertiesChangedListener) { - enforceReadPermission(ActivityThread.currentApplication().getApplicationContext(), - namespace); + enforceReadPermission(namespace); synchronized (sLock) { Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener); if (oldNamespace == null) { @@ -1216,7 +1205,7 @@ public final class DeviceConfig { } } }; - ActivityThread.currentApplication().getContentResolver() + Settings.Config .registerContentObserver(createNamespaceUri(namespace), true, contentObserver); sNamespaces.put(namespace, new Pair<>(contentObserver, 1)); } @@ -1240,8 +1229,7 @@ public final class DeviceConfig { sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1)); } else { // Decrementing a namespace to zero means we no longer need its ContentObserver. - ActivityThread.currentApplication().getContentResolver() - .unregisterContentObserver(namespaceCount.first); + Settings.Config.unregisterContentObserver(namespaceCount.first); sNamespaces.remove(namespace); } } @@ -1281,8 +1269,8 @@ public final class DeviceConfig { * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces. * @hide */ - public static void enforceReadPermission(Context context, String namespace) { - if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) + public static void enforceReadPermission(String namespace) { + if (Settings.Config.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PackageManager.PERMISSION_GRANTED) { if (!PUBLIC_NAMESPACES.contains(namespace)) { throw new SecurityException("Permission denial: reading from settings requires:" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 52b1adb876fc..ef448f5dbd1b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -47,9 +47,11 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PermissionName; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.ContentObserver; import android.database.Cursor; import android.database.SQLException; import android.location.ILocationManager; @@ -3344,7 +3346,7 @@ public final class Settings { public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix, List<String> names) { String namespace = prefix.substring(0, prefix.length() - 1); - DeviceConfig.enforceReadPermission(ActivityThread.currentApplication(), namespace); + DeviceConfig.enforceReadPermission(namespace); ArrayMap<String, String> keyValues = new ArrayMap<>(); int currentGeneration = -1; @@ -18002,20 +18004,36 @@ public final class Settings { /** * Look up a name in the database. - * @param resolver to access the database with * @param name to look up in the table * @return the corresponding value, or null if not present * * @hide */ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) - static String getString(ContentResolver resolver, String name) { + static String getString(String name) { + ContentResolver resolver = getContentResolver(); return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId()); } /** * Look up a list of names in the database, within the specified namespace. * + * @param namespace to which the names belong + * @param names to look up in the table + * @return a non null, but possibly empty, map from name to value for any of the names that + * were found during lookup. + * + * @hide + */ + @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) + public static Map<String, String> getStrings(@NonNull String namespace, + @NonNull List<String> names) { + return getStrings(getContentResolver(), namespace, names); + } + + /** + * Look up a list of names in the database, within the specified namespace. + * * @param resolver to access the database with * @param namespace to which the names belong * @param names to look up in the table @@ -18053,7 +18071,6 @@ public final class Settings { * <strong>not</strong> be set as the default. * </p> * - * @param resolver to access the database with. * @param namespace to store the name/value pair in. * @param name to store. * @param value to associate with the name. @@ -18065,8 +18082,9 @@ public final class Settings { * @hide */ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) - static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace, + public static boolean putString(@NonNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault) { + ContentResolver resolver = getContentResolver(); return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name), value, null, makeDefault, resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE); @@ -18076,6 +18094,23 @@ public final class Settings { * Clear all name/value pairs for the provided namespace and save new name/value pairs in * their place. * + * @param namespace to which the names should be set. + * @param keyValues map of key names (without the prefix) to values. + * @return true if the name/value pairs were set, false if setting was blocked + * + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) + public static boolean setStrings(@NonNull String namespace, + @NonNull Map<String, String> keyValues) + throws DeviceConfig.BadConfigException { + return setStrings(getContentResolver(), namespace, keyValues); + } + + /** + * Clear all name/value pairs for the provided namespace and save new name/value pairs in + * their place. + * * @param resolver to access the database with. * @param namespace to which the names should be set. * @param keyValues map of key names (without the prefix) to values. @@ -18106,7 +18141,6 @@ public final class Settings { /** * Delete a name/value pair from the database for the specified namespace. * - * @param resolver to access the database with. * @param namespace to delete the name/value pair from. * @param name to delete. * @return true if the value was deleted, false on database errors. If the name/value pair @@ -18117,8 +18151,9 @@ public final class Settings { * @hide */ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) - static boolean deleteString(@NonNull ContentResolver resolver, @NonNull String namespace, + static boolean deleteString(@NonNull String namespace, @NonNull String name) { + ContentResolver resolver = getContentResolver(); return sNameValueCache.deleteStringForUser(resolver, createCompositeName(namespace, name), resolver.getUserId()); } @@ -18129,7 +18164,6 @@ public final class Settings { * The method accepts an optional prefix parameter. If provided, only pairs with a name that * starts with the exact prefix will be reset. Otherwise all will be reset. * - * @param resolver Handle to the content resolver. * @param resetMode The reset mode to use. * @param namespace Optionally, to limit which which namespace is reset. * @@ -18138,9 +18172,10 @@ public final class Settings { * @hide */ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) - static void resetToDefaults(@NonNull ContentResolver resolver, @ResetMode int resetMode, + static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) { try { + ContentResolver resolver = getContentResolver(); Bundle arg = new Bundle(); arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId()); arg.putInt(CALL_METHOD_RESET_MODE_KEY, resetMode); @@ -18163,9 +18198,9 @@ public final class Settings { */ @SuppressLint("AndroidFrameworkRequiresPermission") @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) - static void setSyncDisabledMode( - @NonNull ContentResolver resolver, @SyncDisabledMode int disableSyncMode) { + static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) { try { + ContentResolver resolver = getContentResolver(); Bundle args = new Bundle(); args.putInt(CALL_METHOD_SYNC_DISABLED_MODE_KEY, disableSyncMode); IContentProvider cp = sProviderHolder.getProvider(resolver); @@ -18184,8 +18219,9 @@ public final class Settings { */ @SuppressLint("AndroidFrameworkRequiresPermission") @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) - static int getSyncDisabledMode(@NonNull ContentResolver resolver) { + static int getSyncDisabledMode() { try { + ContentResolver resolver = getContentResolver(); Bundle args = Bundle.EMPTY; IContentProvider cp = sProviderHolder.getProvider(resolver); Bundle bundle = cp.call(resolver.getAttributionSource(), @@ -18202,7 +18238,6 @@ public final class Settings { /** * Register callback for monitoring Config table. * - * @param resolver Handle to the content resolver. * @param callback callback to register * * @hide @@ -18213,6 +18248,50 @@ public final class Settings { registerMonitorCallbackAsUser(resolver, resolver.getUserId(), callback); } + + /** + * Register a content observer + * + * @hide + */ + public static void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants, + @NonNull ContentObserver observer) { + ActivityThread.currentApplication().getContentResolver() + .registerContentObserver(uri, notifyForDescendants, observer); + } + + /** + * Unregister a content observer + * + * @hide + */ + public static void unregisterContentObserver(@NonNull ContentObserver observer) { + ActivityThread.currentApplication().getContentResolver() + .unregisterContentObserver(observer); + } + + /** + * Determine whether the calling process of an IPC <em>or you</em> have been + * granted a particular permission. This is the same as + * {@link #checkCallingPermission}, except it grants your own permissions + * if you are not currently processing an IPC. Use with care! + * + * @param permission The name of the permission being checked. + * + * @return {@link PackageManager#PERMISSION_GRANTED} if the calling + * pid/uid is allowed that permission, or + * {@link PackageManager#PERMISSION_DENIED} if it is not. + * + * @see PackageManager#checkPermission(String, String) + * @see #checkPermission + * @see #checkCallingPermission + * @hide + */ + public static int checkCallingOrSelfPermission(@NonNull @PermissionName String permission) { + return ActivityThread.currentApplication() + .getApplicationContext().checkCallingOrSelfPermission(permission); + } + private static void registerMonitorCallbackAsUser( @NonNull ContentResolver resolver, @UserIdInt int userHandle, @NonNull RemoteCallback callback) { @@ -18245,6 +18324,10 @@ public final class Settings { Preconditions.checkNotNull(namespace); return namespace + "/"; } + + private static ContentResolver getContentResolver() { + return ActivityThread.currentApplication().getContentResolver(); + } } /** diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java index cd38e8a01d62..dd5373ff8c38 100644 --- a/core/java/android/service/dreams/DreamManagerInternal.java +++ b/core/java/android/service/dreams/DreamManagerInternal.java @@ -58,4 +58,32 @@ public abstract class DreamManagerInternal { * @param isScreenOn True if the screen is currently on. */ public abstract boolean canStartDreaming(boolean isScreenOn); + + /** + * Register a {@link DreamManagerStateListener}, which will be called when there are changes to + * dream state. + * + * @param listener The listener to register. + */ + public abstract void registerDreamManagerStateListener(DreamManagerStateListener listener); + + /** + * Unregister a {@link DreamManagerStateListener}, which will be called when there are changes + * to dream state. + * + * @param listener The listener to unregister. + */ + public abstract void unregisterDreamManagerStateListener(DreamManagerStateListener listener); + + /** + * Called when there are changes to dream state. + */ + public interface DreamManagerStateListener { + /** + * Called when keep dreaming when undocked has changed. + * + * @param keepDreaming True if the current dream should continue when undocking. + */ + void onKeepDreamingWhenUndockedChanged(boolean keepDreaming); + } } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 3acb0534e388..18897725e98f 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -159,9 +159,11 @@ public class SurfaceControlViewHost { } /** - * Use {@link SurfaceView#setChildSurfacePackage} or manually fix - * accessibility (see SurfaceView implementation). - * @hide + * Returns the {@link android.view.SurfaceControl} associated with this SurfacePackage for + * cases where more control is required. + * + * @return the SurfaceControl associated with this SurfacePackage and its containing + * SurfaceControlViewHost */ public @NonNull SurfaceControl getSurfaceControl() { return mSurfaceControl; diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index d77e8825e462..164a49464051 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -587,6 +587,13 @@ public final class ThreadedRenderer extends HardwareRenderer { updateWebViewOverlayCallbacks(); } + @Override + public void notifyCallbackPending() { + if (isEnabled()) { + super.notifyCallbackPending(); + } + } + /** * Updates the light position based on the position of the window. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2b071dbb7d87..d709840d1b10 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8457,6 +8457,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * accurately supplying the semantics of their UI. * They should not need to specify what exactly is announced to users. * + * <p> + * In general, only announce transitions and don’t generate a confirmation message for simple + * actions like a button press. Label your controls concisely and precisely instead, and for + * significant UI changes like window changes, use + * {@link android.app.Activity#setTitle(CharSequence)} and + * {@link View#setAccessibilityPaneTitle(CharSequence)}. + * + * <p> + * Use {@link View#setAccessibilityLiveRegion(int)} to inform the user of changes to critical + * views within the user interface. These should still be used sparingly as they may generate + * announcements every time a View is updated. + * * @param text The announcement text. */ public void announceForAccessibility(CharSequence text) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5e1dc340a7a7..a441f3f05fda 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2309,6 +2309,7 @@ public final class ViewRootImpl implements ViewParent, */ void notifyRendererOfFramePending() { if (mAttachInfo.mThreadedRenderer != null) { + mAttachInfo.mThreadedRenderer.notifyCallbackPending(); mAttachInfo.mThreadedRenderer.notifyFramePending(); } } @@ -9127,6 +9128,9 @@ public final class ViewRootImpl implements ViewParent, mConsumeBatchedInputScheduled = true; mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mConsumedBatchedInputRunnable, null); + if (mAttachInfo.mThreadedRenderer != null) { + mAttachInfo.mThreadedRenderer.notifyCallbackPending(); + } } } @@ -9320,6 +9324,9 @@ public final class ViewRootImpl implements ViewParent, mViews.add(view); postIfNeededLocked(); } + if (mAttachInfo.mThreadedRenderer != null) { + mAttachInfo.mThreadedRenderer.notifyCallbackPending(); + } } public void addViewRect(AttachInfo.InvalidateInfo info) { @@ -9327,6 +9334,9 @@ public final class ViewRootImpl implements ViewParent, mViewRects.add(info); postIfNeededLocked(); } + if (mAttachInfo.mThreadedRenderer != null) { + mAttachInfo.mThreadedRenderer.notifyCallbackPending(); + } } public void removeView(View view) { diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index b0cf504ec568..a52a99bec500 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -24,6 +24,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; +import android.view.View; import com.android.internal.util.BitUtils; @@ -519,6 +520,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Represents the event of an application making an announcement. + * <p> + * In general, follow the practices described in + * {@link View#announceForAccessibility(CharSequence)}. */ public static final int TYPE_ANNOUNCEMENT = 0x00004000; diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java index 1df4fc54089e..500c41cd1fed 100644 --- a/core/java/android/view/inputmethod/TextAppearanceInfo.java +++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java @@ -16,12 +16,13 @@ package android.view.inputmethod; +import static android.graphics.Typeface.NORMAL; + import android.annotation.ColorInt; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; -import android.content.res.ColorStateList; import android.graphics.Paint; import android.graphics.Typeface; import android.graphics.fonts.FontStyle; @@ -30,7 +31,7 @@ import android.inputmethodservice.InputMethodService; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; -import android.text.InputFilter; +import android.text.method.TransformationMethod; import android.widget.TextView; import java.util.Objects; @@ -38,7 +39,6 @@ import java.util.Objects; /** * Information about text appearance in an editor, passed through * {@link CursorAnchorInfo} for use by {@link InputMethodService}. - * * @see TextView * @see Paint * @see CursorAnchorInfo.Builder#setTextAppearanceInfo(TextAppearanceInfo) @@ -46,12 +46,12 @@ import java.util.Objects; */ public final class TextAppearanceInfo implements Parcelable { /** - * The text size (in pixels) for current {@link TextView}. + * The text size (in pixels) for current editor. */ private final @Px float mTextSize; /** - * The LocaleList of the text. + * The {@link LocaleList} of the text. */ @NonNull private final LocaleList mTextLocales; @@ -64,7 +64,8 @@ public final class TextAppearanceInfo implements Parcelable { /** * The weight of the text. */ - private final @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int mTextFontWeight; + @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) + private final int mTextFontWeight; /** * The style (normal, bold, italic, bold|italic) of the text, see {@link Typeface}. @@ -72,8 +73,7 @@ public final class TextAppearanceInfo implements Parcelable { private final @Typeface.Style int mTextStyle; /** - * Whether the transformation method applied to the current {@link TextView} is set to - * ALL CAPS. + * Whether the transformation method applied to the current editor is set to all caps. */ private final boolean mAllCaps; @@ -93,6 +93,11 @@ public final class TextAppearanceInfo implements Parcelable { private final @Px float mShadowRadius; /** + * The shadow color of the text shadow. + */ + private final @ColorInt int mShadowColor; + + /** * The elegant text height, especially for less compacted complex script text. */ private final boolean mElegantTextHeight; @@ -135,67 +140,46 @@ public final class TextAppearanceInfo implements Parcelable { /** * The color of the text selection highlight. */ - private final @ColorInt int mTextColorHighlight; + private final @ColorInt int mHighlightTextColor; /** - * The current text color. + * The current text color of the editor. */ private final @ColorInt int mTextColor; /** - * The current color of the hint text. - */ - private final @ColorInt int mTextColorHint; - - /** - * The text color for links. - */ - @Nullable private final ColorStateList mTextColorLink; - - /** - * The max length of text. - */ - private final int mMaxLength; - - - public TextAppearanceInfo(@NonNull TextView textView) { - mTextSize = textView.getTextSize(); - mTextLocales = textView.getTextLocales(); - Typeface typeface = textView.getPaint().getTypeface(); - String systemFontFamilyName = null; - int textFontWeight = -1; - if (typeface != null) { - systemFontFamilyName = typeface.getSystemFontFamilyName(); - textFontWeight = typeface.getWeight(); - } - mSystemFontFamilyName = systemFontFamilyName; - mTextFontWeight = textFontWeight; - mTextStyle = textView.getTypefaceStyle(); - mAllCaps = textView.isAllCaps(); - mShadowRadius = textView.getShadowRadius(); - mShadowDx = textView.getShadowDx(); - mShadowDy = textView.getShadowDy(); - mElegantTextHeight = textView.isElegantTextHeight(); - mFallbackLineSpacing = textView.isFallbackLineSpacing(); - mLetterSpacing = textView.getLetterSpacing(); - mFontFeatureSettings = textView.getFontFeatureSettings(); - mFontVariationSettings = textView.getFontVariationSettings(); - mLineBreakStyle = textView.getLineBreakStyle(); - mLineBreakWordStyle = textView.getLineBreakWordStyle(); - mTextScaleX = textView.getTextScaleX(); - mTextColorHighlight = textView.getHighlightColor(); - mTextColor = textView.getCurrentTextColor(); - mTextColorHint = textView.getCurrentHintTextColor(); - mTextColorLink = textView.getLinkTextColors(); - int maxLength = -1; - for (InputFilter filter: textView.getFilters()) { - if (filter instanceof InputFilter.LengthFilter) { - maxLength = ((InputFilter.LengthFilter) filter).getMax(); - // There is at most one LengthFilter. - break; - } - } - mMaxLength = maxLength; + * The current color of the hint text. + */ + private final @ColorInt int mHintTextColor; + + /** + * The text color used to paint the links in the editor. + */ + private final @ColorInt int mLinkTextColor; + + private TextAppearanceInfo(@NonNull final TextAppearanceInfo.Builder builder) { + mTextSize = builder.mTextSize; + mTextLocales = builder.mTextLocales; + mSystemFontFamilyName = builder.mSystemFontFamilyName; + mTextFontWeight = builder.mTextFontWeight; + mTextStyle = builder.mTextStyle; + mAllCaps = builder.mAllCaps; + mShadowDx = builder.mShadowDx; + mShadowDy = builder.mShadowDy; + mShadowRadius = builder.mShadowRadius; + mShadowColor = builder.mShadowColor; + mElegantTextHeight = builder.mElegantTextHeight; + mFallbackLineSpacing = builder.mFallbackLineSpacing; + mLetterSpacing = builder.mLetterSpacing; + mFontFeatureSettings = builder.mFontFeatureSettings; + mFontVariationSettings = builder.mFontVariationSettings; + mLineBreakStyle = builder.mLineBreakStyle; + mLineBreakWordStyle = builder.mLineBreakWordStyle; + mTextScaleX = builder.mTextScaleX; + mHighlightTextColor = builder.mHighlightTextColor; + mTextColor = builder.mTextColor; + mHintTextColor = builder.mHintTextColor; + mLinkTextColor = builder.mLinkTextColor; } @Override @@ -214,6 +198,7 @@ public final class TextAppearanceInfo implements Parcelable { dest.writeFloat(mShadowDx); dest.writeFloat(mShadowDy); dest.writeFloat(mShadowRadius); + dest.writeInt(mShadowColor); dest.writeBoolean(mElegantTextHeight); dest.writeBoolean(mFallbackLineSpacing); dest.writeFloat(mLetterSpacing); @@ -222,14 +207,13 @@ public final class TextAppearanceInfo implements Parcelable { dest.writeInt(mLineBreakStyle); dest.writeInt(mLineBreakWordStyle); dest.writeFloat(mTextScaleX); - dest.writeInt(mTextColorHighlight); + dest.writeInt(mHighlightTextColor); dest.writeInt(mTextColor); - dest.writeInt(mTextColorHint); - dest.writeTypedObject(mTextColorLink, flags); - dest.writeInt(mMaxLength); + dest.writeInt(mHintTextColor); + dest.writeInt(mLinkTextColor); } - private TextAppearanceInfo(@NonNull Parcel in) { + TextAppearanceInfo(@NonNull Parcel in) { mTextSize = in.readFloat(); mTextLocales = LocaleList.CREATOR.createFromParcel(in); mAllCaps = in.readBoolean(); @@ -239,6 +223,7 @@ public final class TextAppearanceInfo implements Parcelable { mShadowDx = in.readFloat(); mShadowDy = in.readFloat(); mShadowRadius = in.readFloat(); + mShadowColor = in.readInt(); mElegantTextHeight = in.readBoolean(); mFallbackLineSpacing = in.readBoolean(); mLetterSpacing = in.readFloat(); @@ -247,11 +232,10 @@ public final class TextAppearanceInfo implements Parcelable { mLineBreakStyle = in.readInt(); mLineBreakWordStyle = in.readInt(); mTextScaleX = in.readFloat(); - mTextColorHighlight = in.readInt(); + mHighlightTextColor = in.readInt(); mTextColor = in.readInt(); - mTextColorHint = in.readInt(); - mTextColorLink = in.readTypedObject(ColorStateList.CREATOR); - mMaxLength = in.readInt(); + mHintTextColor = in.readInt(); + mLinkTextColor = in.readInt(); } @NonNull @@ -268,14 +252,14 @@ public final class TextAppearanceInfo implements Parcelable { }; /** - * Returns the text size (in pixels) for current {@link TextView}. + * Returns the text size (in pixels) for current editor. */ public @Px float getTextSize() { return mTextSize; } /** - * Returns the LocaleList of the text. + * Returns the {@link LocaleList} of the text. */ @NonNull public LocaleList getTextLocales() { @@ -286,31 +270,38 @@ public final class TextAppearanceInfo implements Parcelable { * Returns the font family name if the {@link Typeface} of the text is created from a * system font family. Returns null if no {@link Typeface} is specified, or it is not created * from a system font family. + * + * @see Typeface#getSystemFontFamilyName() */ @Nullable - public String getFontFamilyName() { + public String getSystemFontFamilyName() { return mSystemFontFamilyName; } /** - * Returns the weight of the text. Returns -1 when no {@link Typeface} is specified. + * Returns the weight of the text, or {@code FontStyle#FONT_WEIGHT_UNSPECIFIED} + * when no {@link Typeface} is specified. */ - public @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int getTextFontWeight() { + @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) + public int getTextFontWeight() { return mTextFontWeight; } /** * Returns the style (normal, bold, italic, bold|italic) of the text. Returns - * {@link Typeface#NORMAL} when no {@link Typeface} is specified. See {@link Typeface} for - * more information. + * {@link Typeface#NORMAL} when no {@link Typeface} is specified. + * + * @see Typeface */ public @Typeface.Style int getTextStyle() { return mTextStyle; } /** - * Returns whether the transformation method applied to the current {@link TextView} is set to - * ALL CAPS. + * Returns whether the transformation method applied to the current editor is set to all caps. + * + * @see TextView#setAllCaps(boolean) + * @see TextView#setTransformationMethod(TransformationMethod) */ public boolean isAllCaps() { return mAllCaps; @@ -318,6 +309,8 @@ public final class TextAppearanceInfo implements Parcelable { /** * Returns the horizontal offset (in pixels) of the text shadow. + * + * @see Paint#setShadowLayer(float, float, float, int) */ public @Px float getShadowDx() { return mShadowDx; @@ -325,6 +318,8 @@ public final class TextAppearanceInfo implements Parcelable { /** * Returns the vertical offset (in pixels) of the text shadow. + * + * @see Paint#setShadowLayer(float, float, float, int) */ public @Px float getShadowDy() { return mShadowDy; @@ -332,15 +327,28 @@ public final class TextAppearanceInfo implements Parcelable { /** * Returns the blur radius (in pixels) of the text shadow. + * + * @see Paint#setShadowLayer(float, float, float, int) */ public @Px float getShadowRadius() { return mShadowRadius; } /** + * Returns the color of the text shadow. + * + * @see Paint#setShadowLayer(float, float, float, int) + */ + public @ColorInt int getShadowColor() { + return mShadowColor; + } + + /** * Returns {@code true} if the elegant height metrics flag is set. This setting selects font * variants that have not been compacted to fit Latin-based vertical metrics, and also increases * top and bottom bounds to provide more space. + * + * @see Paint#isElegantTextHeight() */ public boolean isElegantTextHeight() { return mElegantTextHeight; @@ -411,13 +419,17 @@ public final class TextAppearanceInfo implements Parcelable { /** * Returns the color of the text selection highlight. + * + * @see TextView#getHighlightColor() */ - public @ColorInt int getTextColorHighlight() { - return mTextColorHighlight; + public @ColorInt int getHighlightTextColor() { + return mHighlightTextColor; } /** - * Returns the current text color. + * Returns the current text color of the editor. + * + * @see TextView#getCurrentTextColor() */ public @ColorInt int getTextColor() { return mTextColor; @@ -425,27 +437,22 @@ public final class TextAppearanceInfo implements Parcelable { /** * Returns the current color of the hint text. + * + * @see TextView#getCurrentHintTextColor() */ - public @ColorInt int getTextColorHint() { - return mTextColorHint; + public @ColorInt int getHintTextColor() { + return mHintTextColor; } /** - * Returns the text color for links. + * Returns the text color used to paint the links in the editor. + * + * @see TextView#getLinkTextColors() */ - @Nullable - public ColorStateList getTextColorLink() { - return mTextColorLink; + public @ColorInt int getLinkTextColor() { + return mLinkTextColor; } - /** - * Returns the max length of text, which is used to set an input filter to constrain the text - * length to the specified number. Returns -1 when there is no {@link InputFilter.LengthFilter} - * in the Editor. - */ - public int getMaxLength() { - return mMaxLength; - } @Override public boolean equals(Object o) { @@ -456,27 +463,29 @@ public final class TextAppearanceInfo implements Parcelable { && mTextFontWeight == that.mTextFontWeight && mTextStyle == that.mTextStyle && mAllCaps == that.mAllCaps && Float.compare(that.mShadowDx, mShadowDx) == 0 && Float.compare(that.mShadowDy, mShadowDy) == 0 && Float.compare( - that.mShadowRadius, mShadowRadius) == 0 && mMaxLength == that.mMaxLength + that.mShadowRadius, mShadowRadius) == 0 && that.mShadowColor == mShadowColor && mElegantTextHeight == that.mElegantTextHeight && mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare( that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle && mLineBreakWordStyle == that.mLineBreakWordStyle - && mTextColorHighlight == that.mTextColorHighlight && mTextColor == that.mTextColor - && mTextColorLink.getDefaultColor() == that.mTextColorLink.getDefaultColor() - && mTextColorHint == that.mTextColorHint && Objects.equals( - mTextLocales, that.mTextLocales) && Objects.equals(mSystemFontFamilyName, - that.mSystemFontFamilyName) && Objects.equals(mFontFeatureSettings, - that.mFontFeatureSettings) && Objects.equals(mFontVariationSettings, - that.mFontVariationSettings) && Float.compare(that.mTextScaleX, mTextScaleX) == 0; + && mHighlightTextColor == that.mHighlightTextColor + && mTextColor == that.mTextColor + && mLinkTextColor == that.mLinkTextColor + && mHintTextColor == that.mHintTextColor + && Objects.equals(mTextLocales, that.mTextLocales) + && Objects.equals(mSystemFontFamilyName, that.mSystemFontFamilyName) + && Objects.equals(mFontFeatureSettings, that.mFontFeatureSettings) + && Objects.equals(mFontVariationSettings, that.mFontVariationSettings) + && Float.compare(that.mTextScaleX, mTextScaleX) == 0; } @Override public int hashCode() { return Objects.hash(mTextSize, mTextLocales, mSystemFontFamilyName, mTextFontWeight, - mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mElegantTextHeight, - mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, mFontVariationSettings, - mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, mTextColorHighlight, mTextColor, - mTextColorHint, mTextColorLink, mMaxLength); + mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mShadowColor, + mElegantTextHeight, mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, + mFontVariationSettings, mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, + mHighlightTextColor, mTextColor, mHintTextColor, mLinkTextColor); } @Override @@ -491,6 +500,7 @@ public final class TextAppearanceInfo implements Parcelable { + ", mShadowDx=" + mShadowDx + ", mShadowDy=" + mShadowDy + ", mShadowRadius=" + mShadowRadius + + ", mShadowColor=" + mShadowColor + ", mElegantTextHeight=" + mElegantTextHeight + ", mFallbackLineSpacing=" + mFallbackLineSpacing + ", mLetterSpacing=" + mLetterSpacing @@ -499,11 +509,290 @@ public final class TextAppearanceInfo implements Parcelable { + ", mLineBreakStyle=" + mLineBreakStyle + ", mLineBreakWordStyle=" + mLineBreakWordStyle + ", mTextScaleX=" + mTextScaleX - + ", mTextColorHighlight=" + mTextColorHighlight + + ", mHighlightTextColor=" + mHighlightTextColor + ", mTextColor=" + mTextColor - + ", mTextColorHint=" + mTextColorHint - + ", mTextColorLink=" + mTextColorLink - + ", mMaxLength=" + mMaxLength + + ", mHintTextColor=" + mHintTextColor + + ", mLinkTextColor=" + mLinkTextColor + '}'; } + + /** + * Builder for {@link TextAppearanceInfo}. + */ + public static final class Builder { + private @Px float mTextSize = -1; + private @NonNull LocaleList mTextLocales = LocaleList.getAdjustedDefault(); + @Nullable private String mSystemFontFamilyName = null; + @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX) + private int mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; + private @Typeface.Style int mTextStyle = NORMAL; + private boolean mAllCaps = false; + private @Px float mShadowDx = 0; + private @Px float mShadowDy = 0; + private @Px float mShadowRadius = 0; + private @ColorInt int mShadowColor = 0; + private boolean mElegantTextHeight = false; + private boolean mFallbackLineSpacing = false; + private float mLetterSpacing = 0; + @Nullable private String mFontFeatureSettings = null; + @Nullable private String mFontVariationSettings = null; + @LineBreakConfig.LineBreakStyle + private int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE; + @LineBreakConfig.LineBreakWordStyle + private int mLineBreakWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + private float mTextScaleX = 1; + private @ColorInt int mHighlightTextColor = 0; + private @ColorInt int mTextColor = 0; + private @ColorInt int mHintTextColor = 0; + private @ColorInt int mLinkTextColor = 0; + + /** + * Set the text size (in pixels) obtained from the current editor. + */ + @NonNull + public Builder setTextSize(@Px float textSize) { + mTextSize = textSize; + return this; + } + + /** + * Set the {@link LocaleList} of the text. + */ + @NonNull + public Builder setTextLocales(@NonNull LocaleList textLocales) { + mTextLocales = textLocales; + return this; + } + + /** + * Set the system font family name if the {@link Typeface} of the text is created from a + * system font family. + * + * @see Typeface#getSystemFontFamilyName() + */ + @NonNull + public Builder setSystemFontFamilyName(@Nullable String systemFontFamilyName) { + mSystemFontFamilyName = systemFontFamilyName; + return this; + } + + /** + * Set the weight of the text. + */ + @NonNull + public Builder setTextFontWeight( + @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, + to = FontStyle.FONT_WEIGHT_MAX) int textFontWeight) { + mTextFontWeight = textFontWeight; + return this; + } + + /** + * Set the style (normal, bold, italic, bold|italic) of the text. + * + * @see Typeface + */ + @NonNull + public Builder setTextStyle(@Typeface.Style int textStyle) { + mTextStyle = textStyle; + return this; + } + + /** + * Set whether the transformation method applied to the current editor is set to all caps. + * + * @see TextView#setAllCaps(boolean) + * @see TextView#setTransformationMethod(TransformationMethod) + */ + @NonNull + public Builder setAllCaps(boolean allCaps) { + mAllCaps = allCaps; + return this; + } + + /** + * Set the horizontal offset (in pixels) of the text shadow. + * + * @see Paint#setShadowLayer(float, float, float, int) + */ + @NonNull + public Builder setShadowDx(@Px float shadowDx) { + mShadowDx = shadowDx; + return this; + } + + /** + * Set the vertical offset (in pixels) of the text shadow. + * + * @see Paint#setShadowLayer(float, float, float, int) + */ + @NonNull + public Builder setShadowDy(@Px float shadowDy) { + mShadowDy = shadowDy; + return this; + } + + /** + * Set the blur radius (in pixels) of the text shadow. + * + * @see Paint#setShadowLayer(float, float, float, int) + */ + @NonNull + public Builder setShadowRadius(@Px float shadowRadius) { + mShadowRadius = shadowRadius; + return this; + } + + /** + * Set the color of the text shadow. + * + * @see Paint#setShadowLayer(float, float, float, int) + */ + @NonNull + public Builder setShadowColor(@ColorInt int shadowColor) { + mShadowColor = shadowColor; + return this; + } + + /** + * Set the elegant height metrics flag. This setting selects font variants that + * have not been compacted to fit Latin-based vertical metrics, and also increases + * top and bottom bounds to provide more space. + * + * @see Paint#isElegantTextHeight() + */ + @NonNull + public Builder setElegantTextHeight(boolean elegantTextHeight) { + mElegantTextHeight = elegantTextHeight; + return this; + } + + /** + * Set whether to expand linespacing based on fallback fonts. + * + * @see TextView#setFallbackLineSpacing(boolean) + */ + @NonNull + public Builder setFallbackLineSpacing(boolean fallbackLineSpacing) { + mFallbackLineSpacing = fallbackLineSpacing; + return this; + } + + /** + * Set the text letter-spacing, which determines the spacing between characters. + * The value is in 'EM' units. Normally, this value is 0.0. + */ + @NonNull + public Builder setLetterSpacing(float letterSpacing) { + mLetterSpacing = letterSpacing; + return this; + } + + /** + * Set the font feature settings. + * + * @see Paint#getFontFeatureSettings() + */ + @NonNull + public Builder setFontFeatureSettings(@Nullable String fontFeatureSettings) { + mFontFeatureSettings = fontFeatureSettings; + return this; + } + + /** + * Set the font variation settings. Returns null if no variation is specified. + * + * @see Paint#getFontVariationSettings() + */ + @NonNull + public Builder setFontVariationSettings(@Nullable String fontVariationSettings) { + mFontVariationSettings = fontVariationSettings; + return this; + } + + /** + * Set the line-break strategies for text wrapping. + * + * @see TextView#setLineBreakStyle(int) + */ + @NonNull + public Builder setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) { + mLineBreakStyle = lineBreakStyle; + return this; + } + + /** + * Set the line-break word strategies for text wrapping. + * + * @see TextView#setLineBreakWordStyle(int) + */ + @NonNull + public Builder setLineBreakWordStyle( + @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { + mLineBreakWordStyle = lineBreakWordStyle; + return this; + } + + /** + * Set the extent by which text should be stretched horizontally. + */ + @NonNull + public Builder setTextScaleX(float textScaleX) { + mTextScaleX = textScaleX; + return this; + } + + /** + * Set the color of the text selection highlight. + * + * @see TextView#getHighlightColor() + */ + @NonNull + public Builder setHighlightTextColor(@ColorInt int highlightTextColor) { + mHighlightTextColor = highlightTextColor; + return this; + } + + /** + * Set the current text color of the editor. + * + * @see TextView#getCurrentTextColor() + */ + @NonNull + public Builder setTextColor(@ColorInt int textColor) { + mTextColor = textColor; + return this; + } + + /** + * Set the current color of the hint text. + * + * @see TextView#getCurrentHintTextColor() + */ + @NonNull + public Builder setHintTextColor(@ColorInt int hintTextColor) { + mHintTextColor = hintTextColor; + return this; + } + + /** + * Set the text color used to paint the links in the editor. + * + * @see TextView#getLinkTextColors() + */ + @NonNull + public Builder setLinkTextColor(@ColorInt int linkTextColor) { + mLinkTextColor = linkTextColor; + return this; + } + + /** + * Returns {@link TextAppearanceInfo} using parameters in this + * {@link TextAppearanceInfo.Builder}. + */ + @NonNull + public TextAppearanceInfo build() { + return new TextAppearanceInfo(this); + } + } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 56524a2c01ef..5740f86b3486 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -21,6 +21,7 @@ import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID; import android.R; import android.animation.ValueAnimator; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -37,6 +38,7 @@ import android.content.UndoOperation; import android.content.UndoOwner; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; @@ -49,8 +51,10 @@ import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.RenderNode; +import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.fonts.FontStyle; import android.os.Build; import android.os.Bundle; import android.os.LocaleList; @@ -4762,8 +4766,41 @@ public class Editor { } if (includeTextAppearance) { - TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo(mTextView); - builder.setTextAppearanceInfo(textAppearanceInfo); + Typeface typeface = mTextView.getPaint().getTypeface(); + String systemFontFamilyName = null; + int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED; + if (typeface != null) { + systemFontFamilyName = typeface.getSystemFontFamilyName(); + textFontWeight = typeface.getWeight(); + } + ColorStateList linkTextColors = mTextView.getLinkTextColors(); + @ColorInt int linkTextColor = linkTextColors != null + ? linkTextColors.getDefaultColor() : 0; + + TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder(); + appearanceBuilder.setTextSize(mTextView.getTextSize()) + .setTextLocales(mTextView.getTextLocales()) + .setSystemFontFamilyName(systemFontFamilyName) + .setTextFontWeight(textFontWeight) + .setTextStyle(mTextView.getTypefaceStyle()) + .setAllCaps(mTextView.isAllCaps()) + .setShadowDx(mTextView.getShadowDx()) + .setShadowDy(mTextView.getShadowDy()) + .setShadowRadius(mTextView.getShadowRadius()) + .setShadowColor(mTextView.getShadowColor()) + .setElegantTextHeight(mTextView.isElegantTextHeight()) + .setFallbackLineSpacing(mTextView.isFallbackLineSpacing()) + .setLetterSpacing(mTextView.getLetterSpacing()) + .setFontFeatureSettings(mTextView.getFontFeatureSettings()) + .setFontVariationSettings(mTextView.getFontVariationSettings()) + .setLineBreakStyle(mTextView.getLineBreakStyle()) + .setLineBreakWordStyle(mTextView.getLineBreakWordStyle()) + .setTextScaleX(mTextView.getTextScaleX()) + .setHighlightTextColor(mTextView.getHighlightColor()) + .setTextColor(mTextView.getCurrentTextColor()) + .setHintTextColor(mTextView.getCurrentHintTextColor()) + .setLinkTextColor(linkTextColor); + builder.setTextAppearanceInfo(appearanceBuilder.build()); } imm.updateCursorAnchorInfo(mTextView, builder.build()); diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 0f64f6da63f7..292a50b77ecc 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -584,6 +584,13 @@ public final class SystemUiDeviceConfigFlags { public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions"; /** + * (boolean) Whether to ignore the source package for determining whether to use remote copy + * behavior in the clipboard UI. + */ + public static final String CLIPBOARD_IGNORE_REMOTE_COPY_SOURCE = + "clipboard_ignore_remote_copy_source"; + + /** * (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE */ public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled"; diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 09f099120812..614f96255acb 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -91,7 +91,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.UiThread; import android.annotation.WorkerThread; -import android.app.ActivityThread; import android.content.Context; import android.os.Build; import android.os.Handler; @@ -417,7 +416,6 @@ public class InteractionJankMonitor { public InteractionJankMonitor(@NonNull HandlerThread worker) { // Check permission early. DeviceConfig.enforceReadPermission( - ActivityThread.currentApplication().getApplicationContext(), DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR); mRunningTrackers = new SparseArray<>(); diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 571a8e2e64be..b6bf617b40ae 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -126,6 +126,7 @@ static void init() { addHyphenator("nn", 2, 2); // Norwegian Nynorsk addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi + addHyphenator("pl", 2, 2); // Polish addHyphenator("pt", 2, 3); // Portuguese addHyphenator("ru", 2, 2); // Russian addHyphenator("sk", 2, 2); // Slovak @@ -141,7 +142,6 @@ static void init() { // Following two hyphenators do not have pattern files but there is some special logic based on // language. addHyphenatorWithoutPatternFile("ca", 2, 2); // Catalan - addHyphenatorWithoutPatternFile("pl", 2, 2); // Polish // English locales that fall back to en-US. The data is from CLDR. It's all English locales, // minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>). diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 196ea59fe0f4..ec43ae3b43cb 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1153,8 +1153,16 @@ <!-- Allows an application to read image or video files from external storage that a user has selected via the permission prompt photo picker. Apps can check this permission to verify that a user has decided to use the photo picker, instead of granting access to - {@link #READ_MEDIA_IMAGES or #READ_MEDIA_VIDEO}. It does not prevent apps from accessing the - standard photo picker manually. + {@link #READ_MEDIA_IMAGES} or {@link #READ_MEDIA_VIDEO}. It does not prevent apps from + accessing the standard photo picker manually. This permission should be requested alongside + {@link #READ_MEDIA_IMAGES} and/or {@link #READ_MEDIA_VIDEO}, depending on which type of media + is desired. + <p> This permission will be automatically added to an app's manifest if the app requests + {@link #READ_MEDIA_IMAGES}, {@link #READ_MEDIA_VIDEO}, or {@link #ACCESS_MEDIA_LOCATION} + regardless of target SDK. If an app does not request this permission, then the grant dialog + will return `PERMISSION_GRANTED` for {@link #READ_MEDIA_IMAGES} and/or + {@link #READ_MEDIA_VIDEO}, but the app will only have access to the media selected by the + user. This false grant state will persist until the app goes into the background. <p>Protection level: dangerous --> <permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" android:permissionGroup="android.permission-group.UNDEFINED" @@ -3177,10 +3185,10 @@ <permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/> - <!-- Allows an application to hint that a broadcast is associated with an - "interactive" usage scenario + <!-- Allows an application to hint that a component lifecycle operation such as sending + a broadcast is associated with an "interactive" usage scenario. @hide --> - <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE" + <permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Must be required by activities that handle the intent action diff --git a/core/res/OWNERS b/core/res/OWNERS index 22f40a1461c0..6d05e0785ec1 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -1,5 +1,4 @@ adamp@google.com -alanv@google.com asc@google.com cinek@google.com dsandler@android.com @@ -20,9 +19,6 @@ narayan@google.com ogunwale@google.com patb@google.com shanh@google.com -svetoslavganov@android.com -svetoslavganov@google.com -toddke@google.com tsuji@google.com yamasani@google.com diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 607467abe24f..a5c082723046 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1704,6 +1704,12 @@ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}. --> <flag name="systemExempted" value="0x400" /> + <!-- "Short service" foreground service type. See + TODO: Change it to a real link + {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}. + for more details. + --> + <flag name="shortService" value="0x800" /> <!-- Use cases that can't be categorized into any other foreground service types, but also can't use @link android.app.job.JobInfo.Builder} APIs. See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java index 7e875ada7267..10452fd64430 100644 --- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java +++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java @@ -541,35 +541,35 @@ public class BroadcastTest extends ActivityTestsBase { public void testBroadcastOption_interactive() throws Exception { final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setInteractiveBroadcast(true); + options.setInteractive(true); final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED); try { getContext().sendBroadcast(intent, null, options.toBundle()); - fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)"); + fail("No exception thrown with BroadcastOptions.setInteractive(true)"); } catch (SecurityException se) { // Expected, correct behavior - this case intentionally empty } catch (Exception e) { fail("Unexpected exception " + e.getMessage() - + " thrown with BroadcastOptions.setInteractiveBroadcast(true)"); + + " thrown with BroadcastOptions.setInteractive(true)"); } } public void testBroadcastOption_interactive_PendingIntent() throws Exception { final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setInteractiveBroadcast(true); + options.setInteractive(true); final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED); PendingIntent brPending = PendingIntent.getBroadcast(getContext(), 1, intent, PendingIntent.FLAG_IMMUTABLE); try { brPending.send(getContext(), 1, null, null, null, null, options.toBundle()); - fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)"); + fail("No exception thrown with BroadcastOptions.setInteractive(true)"); } catch (SecurityException se) { // Expected, correct behavior - this case intentionally empty } catch (Exception e) { fail("Unexpected exception " + e.getMessage() - + " thrown with BroadcastOptions.setInteractiveBroadcast(true)"); + + " thrown with BroadcastOptions.setInteractive(true)"); } finally { brPending.cancel(); } diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java index 37cf4700c1d0..4d5b0d2d4ae7 100644 --- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java +++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java @@ -46,6 +46,7 @@ import java.util.Set; public class BackupAgentTest { // An arbitrary user. private static final UserHandle USER_HANDLE = new UserHandle(15); + private static final String DATA_TYPE_BACKED_UP = "test data type"; @Mock FullBackup.BackupScheme mBackupScheme; @@ -73,6 +74,42 @@ public class BackupAgentTest { assertThat(rules).isEqualTo(expectedRules); } + @Test + public void getBackupRestoreEventLogger_beforeOnCreate_isNull() { + BackupAgent agent = new TestFullBackupAgent(); + + assertThat(agent.getBackupRestoreEventLogger()).isNull(); + } + + @Test + public void getBackupRestoreEventLogger_afterOnCreateForBackup_initializedForBackup() { + BackupAgent agent = new TestFullBackupAgent(); + agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type + + assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1); + } + + @Test + public void getBackupRestoreEventLogger_afterOnCreateForRestore_initializedForRestore() { + BackupAgent agent = new TestFullBackupAgent(); + agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type + + assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1); + } + + @Test + public void getBackupRestoreEventLogger_afterBackup_containsLogsLoggedByAgent() + throws Exception { + BackupAgent agent = new TestFullBackupAgent(); + agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type + + // TestFullBackupAgent logs DATA_TYPE_BACKED_UP when onFullBackup is called. + agent.onFullBackup(new FullBackupDataOutput(/* quota = */ 0)); + + assertThat(agent.getBackupRestoreEventLogger().getLoggingResults().get(0).getDataType()) + .isEqualTo(DATA_TYPE_BACKED_UP); + } + private BackupAgent getAgentForOperationType(@OperationType int operationType) { BackupAgent agent = new TestFullBackupAgent(); agent.onCreate(USER_HANDLE, operationType); @@ -88,6 +125,11 @@ public class BackupAgentTest { } @Override + public void onFullBackup(FullBackupDataOutput data) { + getBackupRestoreEventLogger().logItemsBackedUp(DATA_TYPE_BACKED_UP, 1); + } + + @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { // Left empty as this is a full backup agent. diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java index bbd2ef38d786..b9fdc6d2aa23 100644 --- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java +++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java @@ -25,6 +25,7 @@ import static junit.framework.Assert.fail; import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType; import android.app.backup.BackupRestoreEventLogger.DataTypeResult; +import android.os.Parcel; import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; @@ -35,6 +36,7 @@ import org.junit.runner.RunWith; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -236,10 +238,10 @@ public class BackupRestoreEventLoggerTest { mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1); mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2); - int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1) - .getErrors().get(ERROR_1); - int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1) - .getErrors().get(ERROR_2); + int firstErrorTypeCount = + getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1); + int secondErrorTypeCount = + getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2); assertThat(firstErrorTypeCount).isEqualTo(firstCount); assertThat(secondErrorTypeCount).isEqualTo(secondCount); } @@ -253,16 +255,54 @@ public class BackupRestoreEventLoggerTest { mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1); mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2); - int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1) - .getErrors().get(ERROR_1); - int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1) - .getErrors().get(ERROR_2); + int firstErrorTypeCount = + getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1); + int secondErrorTypeCount = + getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2); assertThat(firstErrorTypeCount).isEqualTo(firstCount); assertThat(secondErrorTypeCount).isEqualTo(secondCount); } - private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger, - @BackupRestoreDataType String dataType) { + @Test + public void testGetLoggingResults_resultsParceledAndUnparceled_recreatedCorrectly() { + mLogger = new BackupRestoreEventLogger(RESTORE); + int firstTypeSuccessCount = 1; + int firstTypeErrorOneCount = 2; + int firstTypeErrorTwoCount = 3; + mLogger.logItemsRestored(DATA_TYPE_1, firstTypeSuccessCount); + mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorOneCount, ERROR_1); + mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorTwoCount, ERROR_2); + mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1); + int secondTypeSuccessCount = 4; + int secondTypeErrorOneCount = 5; + mLogger.logItemsRestored(DATA_TYPE_2, secondTypeSuccessCount); + mLogger.logItemsRestoreFailed(DATA_TYPE_2, secondTypeErrorOneCount, ERROR_1); + + List<DataTypeResult> resultsList = mLogger.getLoggingResults(); + Parcel parcel = Parcel.obtain(); + + parcel.writeParcelableList(resultsList, /* flags= */ 0); + + parcel.setDataPosition(0); + List<DataTypeResult> recreatedList = new ArrayList<>(); + parcel.readParcelableList( + recreatedList, DataTypeResult.class.getClassLoader(), DataTypeResult.class); + + assertThat(recreatedList.get(0).getDataType()).isEqualTo(DATA_TYPE_1); + assertThat(recreatedList.get(0).getSuccessCount()).isEqualTo(firstTypeSuccessCount); + assertThat(recreatedList.get(0).getFailCount()) + .isEqualTo(firstTypeErrorOneCount + firstTypeErrorTwoCount); + assertThat(recreatedList.get(0).getErrors().get(ERROR_1)).isEqualTo(firstTypeErrorOneCount); + assertThat(recreatedList.get(0).getErrors().get(ERROR_2)).isEqualTo(firstTypeErrorTwoCount); + assertThat(recreatedList.get(1).getDataType()).isEqualTo(DATA_TYPE_2); + assertThat(recreatedList.get(1).getSuccessCount()).isEqualTo(secondTypeSuccessCount); + assertThat(recreatedList.get(1).getFailCount()).isEqualTo(secondTypeErrorOneCount); + assertThat(recreatedList.get(1).getErrors().get(ERROR_1)) + .isEqualTo(secondTypeErrorOneCount); + } + + private static DataTypeResult getResultForDataType( + BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) { Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType); if (result.isEmpty()) { fail("Failed to find result for data type: " + dataType); @@ -273,8 +313,9 @@ public class BackupRestoreEventLoggerTest { private static Optional<DataTypeResult> getResultForDataTypeIfPresent( BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) { List<DataTypeResult> resultList = logger.getLoggingResults(); - return resultList.stream().filter( - dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny(); + return resultList.stream() + .filter(dataTypeResult -> dataTypeResult.getDataType().equals(dataType)) + .findAny(); } private byte[] getMetaDataHash(String metaData) { diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 7a5ab0458406..6443ac18e7dc 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; +import android.view.SurfaceControl; import android.window.ScreenCapture; import java.util.Collections; @@ -222,4 +223,7 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() throws RemoteException { return null; } + + @Override + public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {} } diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index 9e39e13265bd..3e3c77b7b21c 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -377,6 +377,11 @@ public final class DeviceStateManagerGlobalTest { notifyDeviceStateInfoChanged(); } + // No-op in the test since DeviceStateManagerGlobal just calls into the system server with + // no business logic around it. + @Override + public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {} + public void setSupportedStates(int[] states) { mSupportedStates = states; notifyDeviceStateInfoChanged(); diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 9a1b8a90dbfd..6328b02cf56a 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -288,6 +288,15 @@ targetSdk="33"> <new-permission name="android.permission.READ_MEDIA_IMAGES" /> </split-permission> + <split-permission name="android.permission.READ_MEDIA_IMAGES"> + <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" /> + </split-permission> + <split-permission name="android.permission.READ_MEDIA_VIDEO"> + <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" /> + </split-permission> + <split-permission name="android.permission.ACCESS_MEDIA_LOCATION"> + <new-permission name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" /> + </split-permission> <!-- This is a list of all the libraries available for application code to link against. --> diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 48dd3e6c451b..a7fb7428bc98 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -982,6 +982,15 @@ public class HardwareRenderer { } /** + * Notifies the hardware renderer about pending choreographer callbacks. + * + * @hide + */ + public void notifyCallbackPending() { + nNotifyCallbackPending(mNativeProxy); + } + + /** * b/68769804, b/66945974: For low FPS experiments. * * @hide @@ -1536,4 +1545,6 @@ public class HardwareRenderer { private static native boolean nIsDrawingEnabled(); private static native void nSetRtAnimationsEnabled(boolean rtAnimationsEnabled); + + private static native void nNotifyCallbackPending(long nativeProxy); } diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp index aba3ab3a06a2..ed30904ec179 100644 --- a/libs/androidfw/tests/TypeWrappers_test.cpp +++ b/libs/androidfw/tests/TypeWrappers_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <algorithm> #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> #include <utils/String8.h> @@ -22,88 +23,123 @@ namespace android { -void* createTypeData() { - ResTable_type t; - memset(&t, 0, sizeof(t)); +// create a ResTable_type in memory with a vector of Res_value* +static ResTable_type* createTypeTable(std::vector<Res_value*>& values, + bool compact_entry = false, + bool short_offsets = false) +{ + ResTable_type t{}; t.header.type = RES_TABLE_TYPE_TYPE; t.header.headerSize = sizeof(t); + t.header.size = sizeof(t); t.id = 1; - t.entryCount = 3; - - uint32_t offsets[3]; - t.entriesStart = t.header.headerSize + sizeof(offsets); - t.header.size = t.entriesStart; - - offsets[0] = 0; - ResTable_entry e1; - memset(&e1, 0, sizeof(e1)); - e1.full.size = sizeof(e1); - e1.full.key.index = 0; - t.header.size += sizeof(e1); - - Res_value v1; - memset(&v1, 0, sizeof(v1)); - t.header.size += sizeof(v1); - - offsets[1] = ResTable_type::NO_ENTRY; - - offsets[2] = sizeof(e1) + sizeof(v1); - ResTable_entry e2; - memset(&e2, 0, sizeof(e2)); - e2.full.size = sizeof(e2); - e2.full.key.index = 1; - t.header.size += sizeof(e2); - - Res_value v2; - memset(&v2, 0, sizeof(v2)); - t.header.size += sizeof(v2); - - uint8_t* data = (uint8_t*)malloc(t.header.size); - uint8_t* p = data; - memcpy(p, &t, sizeof(t)); - p += sizeof(t); - memcpy(p, offsets, sizeof(offsets)); - p += sizeof(offsets); - memcpy(p, &e1, sizeof(e1)); - p += sizeof(e1); - memcpy(p, &v1, sizeof(v1)); - p += sizeof(v1); - memcpy(p, &e2, sizeof(e2)); - p += sizeof(e2); - memcpy(p, &v2, sizeof(v2)); - p += sizeof(v2); - return data; + t.flags = short_offsets ? ResTable_type::FLAG_OFFSET16 : 0; + + t.header.size += values.size() * (short_offsets ? sizeof(uint16_t) : sizeof(uint32_t)); + t.entriesStart = t.header.size; + t.entryCount = values.size(); + + size_t entry_size = compact_entry ? sizeof(ResTable_entry) + : sizeof(ResTable_entry) + sizeof(Res_value); + for (auto const v : values) { + t.header.size += v ? entry_size : 0; + } + + uint8_t* data = (uint8_t *)malloc(t.header.size); + uint8_t* p_header = data; + uint8_t* p_offsets = data + t.header.headerSize; + uint8_t* p_entries = data + t.entriesStart; + + memcpy(p_header, &t, sizeof(t)); + + size_t i = 0, entry_offset = 0; + uint32_t k = 0; + for (auto const& v : values) { + if (short_offsets) { + uint16_t *p = reinterpret_cast<uint16_t *>(p_offsets) + i; + *p = v ? (entry_offset >> 2) & 0xffffu : 0xffffu; + } else { + uint32_t *p = reinterpret_cast<uint32_t *>(p_offsets) + i; + *p = v ? entry_offset : ResTable_type::NO_ENTRY; + } + + if (v) { + ResTable_entry entry{}; + if (compact_entry) { + entry.compact.key = i; + entry.compact.flags = ResTable_entry::FLAG_COMPACT | (v->dataType << 8); + entry.compact.data = v->data; + memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry); + entry_offset += sizeof(entry); + } else { + Res_value value{}; + entry.full.size = sizeof(entry); + entry.full.key.index = i; + value = *v; + memcpy(p_entries, &entry, sizeof(entry)); p_entries += sizeof(entry); + memcpy(p_entries, &value, sizeof(value)); p_entries += sizeof(value); + entry_offset += sizeof(entry) + sizeof(value); + } + } + i++; + } + return reinterpret_cast<ResTable_type*>(data); } TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) { - ResTable_type* data = (ResTable_type*) createTypeData(); + std::vector<Res_value *> values; - TypeVariant v(data); + Res_value *v1 = new Res_value{}; + values.push_back(v1); - TypeVariant::iterator iter = v.beginEntries(); - ASSERT_EQ(uint32_t(0), iter.index()); - ASSERT_TRUE(NULL != *iter); - ASSERT_EQ(uint32_t(0), iter->full.key.index); - ASSERT_NE(v.endEntries(), iter); + values.push_back(nullptr); - iter++; + Res_value *v2 = new Res_value{}; + values.push_back(v2); - ASSERT_EQ(uint32_t(1), iter.index()); - ASSERT_TRUE(NULL == *iter); - ASSERT_NE(v.endEntries(), iter); + Res_value *v3 = new Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x12345678}; + values.push_back(v3); - iter++; + // test for combinations of compact_entry and short_offsets + for (size_t i = 0; i < 4; i++) { + bool compact_entry = i & 0x1, short_offsets = i & 0x2; + ResTable_type* data = createTypeTable(values, compact_entry, short_offsets); + TypeVariant v(data); - ASSERT_EQ(uint32_t(2), iter.index()); - ASSERT_TRUE(NULL != *iter); - ASSERT_EQ(uint32_t(1), iter->full.key.index); - ASSERT_NE(v.endEntries(), iter); + TypeVariant::iterator iter = v.beginEntries(); + ASSERT_EQ(uint32_t(0), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(uint32_t(0), iter->key()); + ASSERT_NE(v.endEntries(), iter); - iter++; + iter++; - ASSERT_EQ(v.endEntries(), iter); + ASSERT_EQ(uint32_t(1), iter.index()); + ASSERT_TRUE(NULL == *iter); + ASSERT_NE(v.endEntries(), iter); - free(data); + iter++; + + ASSERT_EQ(uint32_t(2), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(uint32_t(2), iter->key()); + ASSERT_NE(v.endEntries(), iter); + + iter++; + + ASSERT_EQ(uint32_t(3), iter.index()); + ASSERT_TRUE(NULL != *iter); + ASSERT_EQ(iter->is_compact(), compact_entry); + ASSERT_EQ(uint32_t(3), iter->key()); + ASSERT_EQ(uint32_t(0x12345678), iter->value().data); + ASSERT_EQ(Res_value::TYPE_STRING, iter->value().dataType); + + iter++; + + ASSERT_EQ(v.endEntries(), iter); + + free(data); + } } } // namespace android diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index f603e231e49b..0663121a4027 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -860,6 +860,11 @@ static void android_view_ThreadedRenderer_setRtAnimationsEnabled(JNIEnv* env, jo RenderProxy::setRtAnimationsEnabled(enabled); } +static void android_view_ThreadedRenderer_notifyCallbackPending(JNIEnv*, jclass, jlong proxyPtr) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + proxy->notifyCallbackPending(); +} + // Plumbs the display density down to DeviceInfo. static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, jint densityDpi) { // Convert from dpi to density-independent pixels. @@ -1037,6 +1042,8 @@ static const JNINativeMethod gMethods[] = { {"nIsDrawingEnabled", "()Z", (void*)android_view_ThreadedRenderer_isDrawingEnabled}, {"nSetRtAnimationsEnabled", "(Z)V", (void*)android_view_ThreadedRenderer_setRtAnimationsEnabled}, + {"nNotifyCallbackPending", "(J)V", + (void*)android_view_ThreadedRenderer_notifyCallbackPending}, }; static JavaVM* mJvm = nullptr; diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index dc7676c08bd7..cb306144ffd6 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -42,6 +42,7 @@ typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, size_t, int64_t); typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t); typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t); +typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t); typedef void (*APH_closeSession)(APerformanceHintSession* session); bool gAPerformanceHintBindingInitialized = false; @@ -49,6 +50,7 @@ APH_getManager gAPH_getManagerFn = nullptr; APH_createSession gAPH_createSessionFn = nullptr; APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr; APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr; +APH_sendHint gAPH_sendHintFn = nullptr; APH_closeSession gAPH_closeSessionFn = nullptr; void ensureAPerformanceHintBindingInitialized() { @@ -77,6 +79,10 @@ void ensureAPerformanceHintBindingInitialized() { gAPH_reportActualWorkDurationFn == nullptr, "Failed to find required symbol APerformanceHint_reportActualWorkDuration!"); + gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint"); + LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr, + "Failed to find required symbol APerformanceHint_sendHint!"); + gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession"); LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr, "Failed to find required symbol APerformanceHint_closeSession!"); @@ -239,6 +245,16 @@ void DrawFrameTask::run() { mLastDequeueBufferDuration = dequeueBufferDuration; } +void DrawFrameTask::sendLoadResetHint() { + if (!(Properties::useHintManager && Properties::isDrawingEnabled())) return; + if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId); + nsecs_t now = systemTime(); + if (now - mLastFrameNotification > kResetHintTimeout) { + mHintSessionWrapper->sendHint(SessionHint::CPU_LOAD_RESET); + } + mLastFrameNotification = now; +} + bool DrawFrameTask::syncFrameState(TreeInfo& info) { ATRACE_CALL(); int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)]; @@ -327,6 +343,12 @@ void DrawFrameTask::HintSessionWrapper::reportActualWorkDuration(long actualDura } } +void DrawFrameTask::HintSessionWrapper::sendHint(SessionHint hint) { + if (mHintSession && Properties::isDrawingEnabled()) { + gAPH_sendHintFn(mHintSession, static_cast<int>(hint)); + } +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index d6fc292d5900..7eae41c07e64 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -28,6 +28,7 @@ #include "../Rect.h" #include "../TreeInfo.h" #include "RenderTask.h" +#include "utils/TimeUtils.h" namespace android { namespace uirenderer { @@ -90,6 +91,8 @@ public: void forceDrawNextFrame() { mForceDrawFrame = true; } + void sendLoadResetHint(); + private: class HintSessionWrapper { public: @@ -98,6 +101,7 @@ private: void updateTargetWorkDuration(long targetDurationNanos); void reportActualWorkDuration(long actualDurationNanos); + void sendHint(SessionHint hint); private: APerformanceHintSession* mHintSession = nullptr; @@ -135,6 +139,9 @@ private: nsecs_t mLastTargetWorkDuration = 0; std::optional<HintSessionWrapper> mHintSessionWrapper; + nsecs_t mLastFrameNotification = 0; + nsecs_t kResetHintTimeout = 100_ms; + bool mForceDrawFrame = false; }; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 332471545f82..03a2bc9f7e51 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -236,6 +236,10 @@ void RenderProxy::notifyFramePending() { mRenderThread.queue().post([this]() { mContext->notifyFramePending(); }); } +void RenderProxy::notifyCallbackPending() { + mDrawFrameTask.sendLoadResetHint(); +} + void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) { mRenderThread.queue().runSync([&]() { std::lock_guard lock(mRenderThread.getJankDataMutex()); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 2a99a736180f..a21faa86ed0f 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -109,6 +109,7 @@ public: static int maxTextureSize(); void stopDrawing(); void notifyFramePending(); + void notifyCallbackPending(); void dumpProfileInfo(int fd, int dumpFlags); // Not exported, only used for testing diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml index 586ef86f26f6..bd27dab28da3 100644 --- a/packages/CredentialManager/AndroidManifest.xml +++ b/packages/CredentialManager/AndroidManifest.xml @@ -36,8 +36,6 @@ android:name=".CredentialSelectorActivity" android:exported="true" android:label="@string/app_name" - android:launchMode="singleInstance" - android:noHistory="true" android:excludeFromRecents="true" android:theme="@style/Theme.CredentialSelector"> </activity> diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 2f6d1b4bea02..114de89ce82a 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -62,4 +62,8 @@ <string name="locked_credential_entry_label_subtext">Tap to unlock</string> <!-- Column heading for displaying action chips for managing sign-ins from each credential provider. [CHAR LIMIT=80] --> <string name="get_dialog_heading_manage_sign_ins">Manage sign-ins</string> + <!-- Column heading for displaying option to use sign-ins saved on a different device. [CHAR LIMIT=80] --> + <string name="get_dialog_heading_from_another_device">From another device</string> + <!-- Headline text for an option to use sign-ins saved on a different device. [CHAR LIMIT=120] --> + <string name="get_dialog_option_headline_use_a_different_device">Use a different device</string> </resources>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index b848a47f37de..7e69987f1bf8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -194,7 +194,7 @@ class CredentialManagerRepo( ) ) .setRemoteEntry( - newRemoteEntry("key1", "subkey-1") + newRemoteEntry("key2", "subkey-1") ) .setIsDefaultProvider(true) .build(), @@ -252,6 +252,8 @@ class CredentialManagerRepo( "Open Google Password Manager", "beckett-family@gmail.com" ), ) + ).setRemoteEntry( + newRemoteEntry("key4", "subkey-1") ).build(), GetCredentialProviderData.Builder("com.dashlane") .setCredentialEntries( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index df797436baf6..fad9364e3c23 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -30,6 +30,7 @@ import com.android.credentialmanager.getflow.ActionEntryInfo import com.android.credentialmanager.getflow.AuthenticationEntryInfo import com.android.credentialmanager.getflow.CredentialEntryInfo import com.android.credentialmanager.getflow.ProviderInfo +import com.android.credentialmanager.getflow.RemoteEntryInfo import com.android.credentialmanager.jetpack.provider.ActionUi import com.android.credentialmanager.jetpack.provider.CredentialEntryUi import com.android.credentialmanager.jetpack.provider.SaveEntryUi @@ -59,10 +60,11 @@ class GetFlowUtils { credentialEntryList = getCredentialOptionInfoList( it.providerFlattenedComponentName, it.credentialEntries, context), authenticationEntry = getAuthenticationEntry( - it.providerFlattenedComponentName, - providerDisplayName, - providerIcon, - it.authenticationEntry), + it.providerFlattenedComponentName, + providerDisplayName, + providerIcon, + it.authenticationEntry), + remoteEntry = getRemoteEntry(it.providerFlattenedComponentName, it.remoteEntry), actionEntryList = getActionEntryList( it.providerFlattenedComponentName, it.actionChips, context), ) @@ -116,6 +118,18 @@ class GetFlowUtils { ) } + private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? { + // TODO: should also call fromSlice after getting the official jetpack code. + if (remoteEntry == null) { + return null + } + return RemoteEntryInfo( + providerId = providerId, + entryKey = remoteEntry.key, + entrySubkey = remoteEntry.subkey, + ) + } + private fun getActionEntryList( providerId: String, actionEntries: List<Entry>, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 6fd51dd3b69a..19a032fcf4b8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -220,16 +221,25 @@ fun AllSignInOptionCard( ) } // Locked password manager - item { - if (!authenticationEntryList.isEmpty()) { + if (!authenticationEntryList.isEmpty()) { + item { LockedCredentials( authenticationEntryList = authenticationEntryList, onEntrySelected = onEntrySelected, ) } } - // TODO: Remote action - // Manage sign-ins + // From another device + val remoteEntry = providerDisplayInfo.remoteEntry + if (remoteEntry != null) { + item { + RemoteEntryCard( + remoteEntry = remoteEntry, + onEntrySelected = onEntrySelected, + ) + } + } + // Manage sign-ins (action chips) item { ActionChips(providerInfoList = providerInfoList, onEntrySelected = onEntrySelected) } @@ -271,6 +281,47 @@ fun ActionChips( } @Composable +fun RemoteEntryCard( + remoteEntry: RemoteEntryInfo, + onEntrySelected: (EntryInfo) -> Unit, +) { + Text( + text = stringResource(R.string.get_dialog_heading_from_another_device), + style = MaterialTheme.typography.labelLarge, + modifier = Modifier.padding(vertical = 8.dp) + ) + Card( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + shape = MaterialTheme.shapes.medium, + ) { + Column( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Entry( + onClick = {onEntrySelected(remoteEntry)}, + icon = { + Icon( + painter = painterResource(R.drawable.ic_other_devices), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.padding(start = 18.dp) + ) + }, + label = { + Text( + text = stringResource(R.string.get_dialog_option_headline_use_a_different_device), + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(start = 16.dp, top = 18.dp, bottom = 18.dp) + .align(alignment = Alignment.CenterHorizontally) + ) + } + ) + } + } +} + +@Composable fun LockedCredentials( authenticationEntryList: List<AuthenticationEntryInfo>, onEntrySelected: (EntryInfo) -> Unit, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt index f78456aab332..22370a91cde4 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt @@ -27,6 +27,7 @@ import com.android.credentialmanager.CredentialManagerRepo import com.android.credentialmanager.common.DialogResult import com.android.credentialmanager.common.ResultState import com.android.credentialmanager.jetpack.developer.PublicKeyCredential +import com.android.internal.util.Preconditions data class GetCredentialUiState( val providerInfoList: List<ProviderInfo>, @@ -86,10 +87,14 @@ private fun toProviderDisplayInfo( val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>() val authenticationEntryList = mutableListOf<AuthenticationEntryInfo>() + val remoteEntryList = mutableListOf<RemoteEntryInfo>() providerInfoList.forEach { providerInfo -> if (providerInfo.authenticationEntry != null) { authenticationEntryList.add(providerInfo.authenticationEntry) } + if (providerInfo.remoteEntry != null) { + remoteEntryList.add(providerInfo.remoteEntry) + } providerInfo.credentialEntryList.forEach { userNameToCredentialEntryMap.compute( @@ -105,6 +110,9 @@ private fun toProviderDisplayInfo( } } } + // There can only be at most one remote entry + // TODO: fail elegantly + Preconditions.checkState(remoteEntryList.size <= 1) // Compose sortedUserNameToCredentialEntryList val comparator = CredentialEntryInfoComparator() @@ -122,6 +130,7 @@ private fun toProviderDisplayInfo( return ProviderDisplayInfo( sortedUserNameToCredentialEntryList = sortedUserNameToCredentialEntryList, authenticationEntryList = authenticationEntryList, + remoteEntry = remoteEntryList.getOrNull(0), ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index c541e08ffbb4..76d9847f5356 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -28,8 +28,8 @@ data class ProviderInfo( val displayName: String, val credentialEntryList: List<CredentialEntryInfo>, val authenticationEntry: AuthenticationEntryInfo?, + val remoteEntry: RemoteEntryInfo?, val actionEntryList: List<ActionEntryInfo>, - // TODO: add remote entry ) /** Display-centric data structure derived from the [ProviderInfo]. This abstraction is not grouping @@ -41,6 +41,7 @@ data class ProviderDisplayInfo( */ val sortedUserNameToCredentialEntryList: List<PerUserNameCredentialEntryList>, val authenticationEntryList: List<AuthenticationEntryInfo>, + val remoteEntry: RemoteEntryInfo? ) abstract class EntryInfo ( @@ -72,6 +73,12 @@ class AuthenticationEntryInfo( val icon: Drawable, ) : EntryInfo(providerId, entryKey, entrySubkey) +class RemoteEntryInfo( + providerId: String, + entryKey: String, + entrySubkey: String, +) : EntryInfo(providerId, entryKey, entrySubkey) + class ActionEntryInfo( providerId: String, entryKey: String, diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index bfab9bec1cbc..e4bdab89532b 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -28,6 +28,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ProviderInfo; @@ -35,6 +36,8 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.RemoteException; +import android.text.TextUtils; +import android.util.EventLog; import android.util.Log; import java.util.Arrays; @@ -96,6 +99,23 @@ public class InstallStart extends Activity { mAbortInstall = true; } } + + final String installerPackageNameFromIntent = getIntent().getStringExtra( + Intent.EXTRA_INSTALLER_PACKAGE_NAME); + if (installerPackageNameFromIntent != null) { + final String callingPkgName = getLaunchedFromPackage(); + if (installerPackageNameFromIntent.length() >= SessionParams.MAX_PACKAGE_NAME_LENGTH + || (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName) + && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES, + callingPkgName) != PackageManager.PERMISSION_GRANTED)) { + Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent + + " is invalid. Remove it."); + EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(), + "Invalid EXTRA_INSTALLER_PACKAGE_NAME"); + getIntent().removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); + } + } + if (mAbortInstall) { setResult(RESULT_CANCELED); finish(); diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt index 764973f2a8f5..32b283e9a8a9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt @@ -17,20 +17,13 @@ package com.android.settingslib.spa.widget.scaffold import androidx.appcompat.R -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.FindInPage -import androidx.compose.material.icons.outlined.MoreVert -import androidx.compose.material3.DropdownMenu import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.android.settingslib.spa.framework.compose.LocalNavController @@ -82,27 +75,3 @@ internal fun ClearAction(onClick: () -> Unit) { ) } } - -@Composable -fun MoreOptionsAction( - content: @Composable ColumnScope.(onDismissRequest: () -> Unit) -> Unit, -) { - var expanded by rememberSaveable { mutableStateOf(false) } - MoreOptionsActionButton { expanded = true } - val onDismissRequest = { expanded = false } - DropdownMenu( - expanded = expanded, - onDismissRequest = onDismissRequest, - content = { content(onDismissRequest) }, - ) -} - -@Composable -private fun MoreOptionsActionButton(onClick: () -> Unit) { - IconButton(onClick) { - Icon( - imageVector = Icons.Outlined.MoreVert, - contentDescription = stringResource(R.string.abc_action_menu_overflow_description), - ) - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt new file mode 100644 index 000000000000..5e201dfccbd5 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt @@ -0,0 +1,82 @@ +/* + * 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.settingslib.spa.widget.scaffold + +import androidx.appcompat.R +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.MoreVert +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource + +/** + * Scope for the children of [MoreOptionsAction]. + */ +interface MoreOptionsScope : ColumnScope { + fun dismiss() + + @Composable + fun MenuItem(text: String, enabled: Boolean = true, onClick: () -> Unit) { + DropdownMenuItem( + text = { Text(text) }, + onClick = { + dismiss() + onClick() + }, + enabled = enabled, + ) + } +} + +@Composable +fun MoreOptionsAction( + content: @Composable MoreOptionsScope.() -> Unit, +) { + var expanded by rememberSaveable { mutableStateOf(false) } + MoreOptionsActionButton { expanded = true } + val onDismiss = { expanded = false } + DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) { + val moreOptionsScope = remember(this) { + object : MoreOptionsScope, ColumnScope by this { + override fun dismiss() { + onDismiss() + } + } + } + moreOptionsScope.content() + } +} + +@Composable +private fun MoreOptionsActionButton(onClick: () -> Unit) { + IconButton(onClick) { + Icon( + imageVector = Icons.Outlined.MoreVert, + contentDescription = stringResource(R.string.abc_action_menu_overflow_description), + ) + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/MoreOptionsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/MoreOptionsTest.kt new file mode 100644 index 000000000000..019a22e38f93 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/MoreOptionsTest.kt @@ -0,0 +1,87 @@ +/* + * 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.settingslib.spa.widget.scaffold + +import android.content.Context +import androidx.appcompat.R +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MoreOptionsTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun moreOptionsAction_collapseAtBegin() { + composeTestRule.setContent { + MoreOptionsAction { + MenuItem(text = ITEM_TEXT) {} + } + } + + composeTestRule.onNodeWithText(ITEM_TEXT).assertDoesNotExist() + } + + @Test + fun moreOptionsAction_canExpand() { + composeTestRule.setContent { + MoreOptionsAction { + MenuItem(text = ITEM_TEXT) {} + } + } + composeTestRule.onNodeWithContentDescription( + context.getString(R.string.abc_action_menu_overflow_description) + ).performClick() + + composeTestRule.onNodeWithText(ITEM_TEXT).assertIsDisplayed() + } + + @Test + fun moreOptionsAction_itemClicked() { + var menuItemClicked = false + + composeTestRule.setContent { + MoreOptionsAction { + MenuItem(text = ITEM_TEXT) { + menuItemClicked = true + } + } + } + composeTestRule.onNodeWithContentDescription( + context.getString(R.string.abc_action_menu_overflow_description) + ).performClick() + composeTestRule.onNodeWithText(ITEM_TEXT).performClick() + + assertThat(menuItemClicked).isTrue() + } + + private companion object { + const val ITEM_TEXT = "item text" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml new file mode 100644 index 000000000000..bc885286674a --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rCA/strings.xml @@ -0,0 +1,26 @@ +<?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. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="no_applications" msgid="5800789569715871963">"No apps."</string> + <string name="menu_show_system" msgid="906304605807554788">"Show system"</string> + <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string> + <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string> + <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string> + <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string> +</resources> diff --git a/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml new file mode 100644 index 000000000000..c395286a4d7d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/res/values-en-rXC/strings.xml @@ -0,0 +1,26 @@ +<?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. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="no_applications" msgid="5800789569715871963">"No apps."</string> + <string name="menu_show_system" msgid="906304605807554788">"Show system"</string> + <string name="menu_hide_system" msgid="374571689914923020">"Hide system"</string> + <string name="app_permission_summary_allowed" msgid="6115213465364138103">"Allowed"</string> + <string name="app_permission_summary_not_allowed" msgid="58396132188553920">"Not allowed"</string> + <string name="version_text" msgid="4001669804596458577">"version <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string> +</resources> diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt index 388a7d87a1bd..f371ce97b16e 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt @@ -18,8 +18,6 @@ package com.android.settingslib.spaprivileged.template.app import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -27,6 +25,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction +import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope import com.android.settingslib.spa.widget.scaffold.SearchScaffold import com.android.settingslib.spa.widget.ui.Spinner import com.android.settingslib.spaprivileged.R @@ -46,6 +45,7 @@ fun <T : AppRecord> AppListPage( listModel: AppListModel<T>, showInstantApps: Boolean = false, primaryUserOnly: Boolean = false, + moreOptions: @Composable MoreOptionsScope.() -> Unit = {}, header: @Composable () -> Unit = {}, appItem: @Composable (itemState: AppListItemModel<T>) -> Unit, ) { @@ -53,7 +53,10 @@ fun <T : AppRecord> AppListPage( SearchScaffold( title = title, actions = { - ShowSystemAction(showSystem.value) { showSystem.value = it } + MoreOptionsAction { + ShowSystemAction(showSystem.value) { showSystem.value = it } + moreOptions() + } }, ) { bottomPadding, searchQuery -> WorkProfilePager(primaryUserOnly) { userInfo -> @@ -82,15 +85,12 @@ fun <T : AppRecord> AppListPage( } @Composable -private fun ShowSystemAction(showSystem: Boolean, setShowSystem: (showSystem: Boolean) -> Unit) { - MoreOptionsAction { onDismissRequest -> - val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system - DropdownMenuItem( - text = { Text(stringResource(menuText)) }, - onClick = { - onDismissRequest() - setShowSystem(!showSystem) - }, - ) +private fun MoreOptionsScope.ShowSystemAction( + showSystem: Boolean, + setShowSystem: (showSystem: Boolean) -> Unit, +) { + val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system + MenuItem(text = stringResource(menuText)) { + setShowSystem(!showSystem) } } diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml index 327e4e9990d1..8a5723231526 100644 --- a/packages/SettingsLib/res/values-en-rCA/arrays.xml +++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml @@ -86,7 +86,7 @@ <item msgid="8147982633566548515">"map14"</item> </string-array> <string-array name="bluetooth_a2dp_codec_titles"> - <item msgid="2494959071796102843">"Use system selection (default)"</item> + <item msgid="2494959071796102843">"Use System Selection (Default)"</item> <item msgid="4055460186095649420">"SBC"</item> <item msgid="720249083677397051">"AAC"</item> <item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> @@ -96,7 +96,7 @@ <item msgid="506175145534048710">"Opus"</item> </string-array> <string-array name="bluetooth_a2dp_codec_summaries"> - <item msgid="8868109554557331312">"Use system selection (default)"</item> + <item msgid="8868109554557331312">"Use System Selection (Default)"</item> <item msgid="9024885861221697796">"SBC"</item> <item msgid="4688890470703790013">"AAC"</item> <item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item> @@ -106,52 +106,52 @@ <item msgid="7940970833006181407">"Opus"</item> </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_titles"> - <item msgid="926809261293414607">"Use system selection (default)"</item> + <item msgid="926809261293414607">"Use System Selection (Default)"</item> <item msgid="8003118270854840095">"44.1 kHz"</item> <item msgid="3208896645474529394">"48.0 kHz"</item> <item msgid="8420261949134022577">"88.2 kHz"</item> <item msgid="8887519571067543785">"96.0 kHz"</item> </string-array> <string-array name="bluetooth_a2dp_codec_sample_rate_summaries"> - <item msgid="2284090879080331090">"Use system selection (default)"</item> + <item msgid="2284090879080331090">"Use System Selection (Default)"</item> <item msgid="1872276250541651186">"44.1 kHz"</item> <item msgid="8736780630001704004">"48.0 kHz"</item> <item msgid="7698585706868856888">"88.2 kHz"</item> <item msgid="8946330945963372966">"96.0 kHz"</item> </string-array> <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles"> - <item msgid="2574107108483219051">"Use system selection (default)"</item> + <item msgid="2574107108483219051">"Use System Selection (Default)"</item> <item msgid="4671992321419011165">"16 bits/sample"</item> <item msgid="1933898806184763940">"24 bits/sample"</item> <item msgid="1212577207279552119">"32 bits/sample"</item> </string-array> <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries"> - <item msgid="9196208128729063711">"Use system selection (default)"</item> + <item msgid="9196208128729063711">"Use System Selection (Default)"</item> <item msgid="1084497364516370912">"16 bits/sample"</item> <item msgid="2077889391457961734">"24 bits/sample"</item> <item msgid="3836844909491316925">"32 bits/sample"</item> </string-array> <string-array name="bluetooth_a2dp_codec_channel_mode_titles"> - <item msgid="3014194562841654656">"Use system selection (default)"</item> + <item msgid="3014194562841654656">"Use System Selection (Default)"</item> <item msgid="5982952342181788248">"Mono"</item> <item msgid="927546067692441494">"Stereo"</item> </string-array> <string-array name="bluetooth_a2dp_codec_channel_mode_summaries"> - <item msgid="1997302811102880485">"Use system selection (default)"</item> + <item msgid="1997302811102880485">"Use System Selection (Default)"</item> <item msgid="8005696114958453588">"Mono"</item> <item msgid="1333279807604675720">"Stereo"</item> </string-array> <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles"> - <item msgid="1241278021345116816">"Optimised for Audio Quality (990kbps/909kbps)"</item> - <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660 kbps/606 kbps)"</item> - <item msgid="886408010459747589">"Optimised for Connection Quality (330kbps/303kbps)"</item> + <item msgid="1241278021345116816">"Optimized for Audio Quality (990kbps/909kbps)"</item> + <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660kbps/606kbps)"</item> + <item msgid="886408010459747589">"Optimized for Connection Quality (330kbps/303kbps)"</item> <item msgid="3808414041654351577">"Best Effort (Adaptive Bit Rate)"</item> </string-array> <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries"> - <item msgid="804499336721569838">"Optimised for Audio Quality"</item> - <item msgid="7451422070435297462">"Balanced Audio and Connection Quality"</item> - <item msgid="6173114545795428901">"Optimised for Connection Quality"</item> - <item msgid="4349908264188040530">"Best effort (adaptive bit rate)"</item> + <item msgid="804499336721569838">"Optimized for Audio Quality"</item> + <item msgid="7451422070435297462">"Balanced Audio And Connection Quality"</item> + <item msgid="6173114545795428901">"Optimized for Connection Quality"</item> + <item msgid="4349908264188040530">"Best Effort (Adaptive Bit Rate)"</item> </string-array> <string-array name="bluetooth_audio_active_device_summaries"> <item msgid="8019740759207729126"></item> @@ -161,25 +161,25 @@ </string-array> <string-array name="select_logd_size_titles"> <item msgid="1191094707770726722">"Off"</item> - <item msgid="7839165897132179888">"64 K"</item> - <item msgid="2715700596495505626">"256 K"</item> - <item msgid="7099386891713159947">"1 M"</item> - <item msgid="6069075827077845520">"4 M"</item> - <item msgid="6078203297886482480">"8 M"</item> + <item msgid="7839165897132179888">"64K"</item> + <item msgid="2715700596495505626">"256K"</item> + <item msgid="7099386891713159947">"1M"</item> + <item msgid="6069075827077845520">"4M"</item> + <item msgid="6078203297886482480">"8M"</item> </string-array> <string-array name="select_logd_size_lowram_titles"> <item msgid="1145807928339101085">"Off"</item> - <item msgid="4064786181089783077">"64 K"</item> - <item msgid="3052710745383602630">"256 K"</item> - <item msgid="3691785423374588514">"1 M"</item> + <item msgid="4064786181089783077">"64K"</item> + <item msgid="3052710745383602630">"256K"</item> + <item msgid="3691785423374588514">"1M"</item> </string-array> <string-array name="select_logd_size_summaries"> <item msgid="409235464399258501">"Off"</item> - <item msgid="4195153527464162486">"64 K per log buffer"</item> - <item msgid="7464037639415220106">"256 K per log buffer"</item> - <item msgid="8539423820514360724">"1 M per log buffer"</item> - <item msgid="1984761927103140651">"4 M per log buffer"</item> - <item msgid="2983219471251787208">"8 M per log buffer"</item> + <item msgid="4195153527464162486">"64K per log buffer"</item> + <item msgid="7464037639415220106">"256K per log buffer"</item> + <item msgid="8539423820514360724">"1M per log buffer"</item> + <item msgid="1984761927103140651">"4M per log buffer"</item> + <item msgid="2983219471251787208">"8M per log buffer"</item> </string-array> <string-array name="select_logpersist_titles"> <item msgid="704720725704372366">"Off"</item> @@ -222,7 +222,7 @@ </string-array> <string-array name="overlay_display_devices_entries"> <item msgid="4497393944195787240">"None"</item> - <item msgid="8461943978957133391">"480 p"</item> + <item msgid="8461943978957133391">"480p"</item> <item msgid="6923083594932909205">"480p (secure)"</item> <item msgid="1226941831391497335">"720p"</item> <item msgid="7051983425968643928">"720p (secure)"</item> @@ -258,10 +258,10 @@ <string-array name="app_process_limit_entries"> <item msgid="794656271086646068">"Standard limit"</item> <item msgid="8628438298170567201">"No background processes"</item> - <item msgid="915752993383950932">"At most, 1 process"</item> - <item msgid="8554877790859095133">"At most, 2 processes"</item> - <item msgid="9060830517215174315">"At most, 3 processes"</item> - <item msgid="6506681373060736204">"At most, 4 processes"</item> + <item msgid="915752993383950932">"At most 1 process"</item> + <item msgid="8554877790859095133">"At most 2 processes"</item> + <item msgid="9060830517215174315">"At most 3 processes"</item> + <item msgid="6506681373060736204">"At most 4 processes"</item> </string-array> <string-array name="usb_configuration_titles"> <item msgid="3358668781763928157">"Charging"</item> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index 77b20a630461..4714a0b44cb9 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -46,8 +46,8 @@ <string name="wifi_security_passpoint" msgid="2209078477216565387">"Passpoint"</string> <string name="wifi_security_sae" msgid="3644520541721422843">"WPA3-Personal"</string> <string name="wifi_security_psk_sae" msgid="8135104122179904684">"WPA2/WPA3-Personal"</string> - <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced open"</string> - <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced open"</string> + <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced Open"</string> + <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced Open"</string> <string name="wifi_security_eap_suiteb" msgid="415842785991698142">"WPA3-Enterprise 192-bit"</string> <string name="wifi_remembered" msgid="3266709779723179188">"Saved"</string> <string name="wifi_disconnected" msgid="7054450256284661757">"Disconnected"</string> @@ -59,29 +59,29 @@ <string name="wifi_check_password_try_again" msgid="8817789642851605628">"Check password and try again"</string> <string name="wifi_not_in_range" msgid="1541760821805777772">"Not in range"</string> <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string> - <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string> + <string name="wifi_no_internet" msgid="1774198889176926299">"No internet access"</string> <string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string> <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatically connected via network rating provider"</string> <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> - <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> - <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign-in required"</string> + <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string> + <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign in required"</string> <string name="wifi_ap_unable_to_handle_new_sta" msgid="5885145407184194503">"Access point temporarily full"</string> <string name="osu_opening_provider" msgid="4318105381295178285">"Opening <xliff:g id="PASSPOINTPROVIDER">%1$s</xliff:g>"</string> <string name="osu_connect_failed" msgid="9107873364807159193">"Couldn’t connect"</string> <string name="osu_completing_sign_up" msgid="8412636665040390901">"Completing sign-up…"</string> - <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again"</string> + <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again."</string> <string name="osu_sign_up_complete" msgid="7640183358878916847">"Sign-up complete. Connecting…"</string> <string name="speed_label_slow" msgid="6069917670665664161">"Slow"</string> <string name="speed_label_okay" msgid="1253594383880810424">"OK"</string> <string name="speed_label_fast" msgid="2677719134596044051">"Fast"</string> - <string name="speed_label_very_fast" msgid="8215718029533182439">"Very fast"</string> + <string name="speed_label_very_fast" msgid="8215718029533182439">"Very Fast"</string> <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expired"</string> - <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string> + <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string> <string name="bluetooth_disconnected" msgid="7739366554710388701">"Disconnected"</string> <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnecting…"</string> <string name="bluetooth_connecting" msgid="5871702668260192755">"Connecting…"</string> @@ -110,8 +110,8 @@ <string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Contacts and call history sharing"</string> <string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Use for contacts and call history sharing"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Internet connection sharing"</string> - <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text messages"</string> - <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string> + <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text Messages"</string> + <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Access"</string> <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string> <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string> <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string> @@ -120,14 +120,14 @@ <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string> <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string> <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string> - <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string> + <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file transfer server"</string> <string name="bluetooth_map_profile_summary_connected" msgid="4141725591784669181">"Connected to map"</string> <string name="bluetooth_sap_profile_summary_connected" msgid="1280297388033001037">"Connected to SAP"</string> - <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file-transfer server"</string> + <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file transfer server"</string> <string name="bluetooth_hid_profile_summary_connected" msgid="3923653977051684833">"Connected to input device"</string> - <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for Internet access"</string> - <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local Internet connection with device"</string> - <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for Internet access"</string> + <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for internet access"</string> + <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local internet connection with device"</string> + <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for internet access"</string> <string name="bluetooth_map_profile_summary_use_for" msgid="4453622103977592583">"Use for map"</string> <string name="bluetooth_sap_profile_summary_use_for" msgid="6204902866176714046">"Use for SIM access"</string> <string name="bluetooth_a2dp_profile_summary_use_for" msgid="7324694226276491807">"Use for media audio"</string> @@ -146,17 +146,17 @@ <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Pairing rejected by <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Computer"</string> <string name="bluetooth_talkback_headset" msgid="3406852564400882682">"Headset"</string> - <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Telephone"</string> + <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Phone"</string> <string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string> <string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string> <string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string> <string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string> - <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi off."</string> - <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi disconnected."</string> - <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wi-Fi one bar."</string> - <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string> - <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string> - <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string> + <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi off."</string> + <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi disconnected."</string> + <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wifi one bar."</string> + <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi two bars."</string> + <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi three bars."</string> + <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi signal full."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string> <string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string> @@ -178,7 +178,7 @@ <string name="tts_default_rate_title" msgid="3964187817364304022">"Speech rate"</string> <string name="tts_default_rate_summary" msgid="3781937042151716987">"Speed at which the text is spoken"</string> <string name="tts_default_pitch_title" msgid="6988592215554485479">"Pitch"</string> - <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesised speech"</string> + <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesized speech"</string> <string name="tts_default_lang_title" msgid="4698933575028098940">"Language"</string> <string name="tts_lang_use_system" msgid="6312945299804012406">"Use system language"</string> <string name="tts_lang_not_selected" msgid="7927823081096056147">"Language not selected"</string> @@ -188,7 +188,7 @@ <string name="tts_install_data_title" msgid="1829942496472751703">"Install voice data"</string> <string name="tts_install_data_summary" msgid="3608874324992243851">"Install the voice data required for speech synthesis"</string> <string name="tts_engine_security_warning" msgid="3372432853837988146">"This speech synthesis engine may be able to collect all the text that will be spoken, including personal data like passwords and credit card numbers. It comes from the <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> engine. Enable the use of this speech synthesis engine?"</string> - <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for Text-to-Speech output."</string> + <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for text-to-speech output."</string> <string name="tts_default_sample_string" msgid="6388016028292967973">"This is an example of speech synthesis"</string> <string name="tts_status_title" msgid="8190784181389278640">"Default language status"</string> <string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> is fully supported"</string> @@ -224,7 +224,7 @@ <string name="apn_settings_not_available" msgid="1147111671403342300">"Access Point Name settings are not available for this user"</string> <string name="enable_adb" msgid="8072776357237289039">"USB debugging"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"Debug mode when USB is connected"</string> - <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorisations"</string> + <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorizations"</string> <string name="enable_adb_wireless" msgid="6973226350963971018">"Wireless debugging"</string> <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Debug mode when Wi‑Fi is connected"</string> <string name="adb_wireless_error" msgid="721958772149779856">"Error"</string> @@ -233,22 +233,22 @@ <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Pair device with QR code"</string> <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Pair new devices using QR code scanner"</string> <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Pair device with pairing code"</string> - <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six-digit code"</string> + <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six digit code"</string> <string name="adb_paired_devices_title" msgid="5268997341526217362">"Paired devices"</string> <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Currently connected"</string> <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Device details"</string> <string name="adb_device_forget" msgid="193072400783068417">"Forget"</string> <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Device fingerprint: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string> <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Connection unsuccessful"</string> - <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure that <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string> + <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string> <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Pair with device"</string> <string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi‑Fi pairing code"</string> <string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Pairing unsuccessful"</string> - <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure that the device is connected to the same network."</string> + <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure the device is connected to the same network."</string> <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string> <string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string> <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string> - <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string> + <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address & Port"</string> <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string> <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string> <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string> @@ -268,28 +268,28 @@ <string name="mock_location_app_set" msgid="4706722469342913843">"Mock location app: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="6829757985772659599">"Networking"</string> <string name="wifi_display_certification" msgid="1805579519992520381">"Wireless display certification"</string> - <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi verbose logging"</string> + <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi Verbose Logging"</string> <string name="wifi_scan_throttling" msgid="2985624788509913617">"Wi‑Fi scan throttling"</string> - <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomisation"</string> + <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomization"</string> <string name="mobile_data_always_on" msgid="8275958101875563572">"Mobile data always active"</string> <string name="tethering_hardware_offload" msgid="4116053719006939161">"Tethering hardware acceleration"</string> <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string> <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string> <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string> - <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string> + <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Version"</string> <string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string> - <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string> - <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string> - <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string> + <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Version"</string> + <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP Version"</string> + <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio Codec"</string> <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string> - <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string> + <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio Sample Rate"</string> <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Trigger Bluetooth Audio Codec\nSelection: Sample Rate"</string> - <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Grey-out means not supported by phone or headset"</string> - <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth audio bits per sample"</string> + <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Gray-out means not supported by phone or headset"</string> + <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth Audio Bits Per Sample"</string> <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"Trigger Bluetooth Audio Codec\nSelection: Bits Per Sample"</string> - <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth audio channel mode"</string> + <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth Audio Channel Mode"</string> <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"Trigger Bluetooth Audio Codec\nSelection: Channel Mode"</string> - <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth audio LDAC codec: Playback quality"</string> + <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth Audio LDAC Codec: Playback Quality"</string> <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"Trigger Bluetooth Audio LDAC\nCodec Selection: Playback Quality"</string> <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string> <string name="select_private_dns_configuration_title" msgid="7887550926056143018">"Private DNS"</string> @@ -301,14 +301,14 @@ <string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Couldn\'t connect"</string> <string name="wifi_display_certification_summary" msgid="8111151348106907513">"Show options for wireless display certification"</string> <string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string> - <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain and improves network performance"</string> - <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time that it connects to a network that has MAC randomisation enabled."</string> + <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain & improves network performance"</string> + <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time it connects to a network that has MAC randomization enabled."</string> <string name="wifi_metered_label" msgid="8737187690304098638">"Metered"</string> <string name="wifi_unmetered_label" msgid="6174142840934095093">"Unmetered"</string> <string name="select_logd_size_title" msgid="1604578195914595173">"Logger buffer sizes"</string> <string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Select Logger sizes per log buffer"</string> <string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Clear logger persistent storage?"</string> - <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we are no longer monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string> + <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we no longer are monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string> <string name="select_logpersist_title" msgid="447071974007104196">"Store logger data persistently on device"</string> <string name="select_logpersist_dialog_title" msgid="7745193591195485594">"Select log buffers to store persistently on device"</string> <string name="select_usb_configuration_title" msgid="6339801314922294586">"Select USB Configuration"</string> @@ -319,22 +319,22 @@ <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Always keep mobile data active, even when Wi‑Fi is active (for fast network switching)."</string> <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Use tethering hardware acceleration if available"</string> <string name="adb_warning_title" msgid="7708653449506485728">"Allow USB debugging?"</string> - <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification and read log data."</string> + <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string> <string name="adbwifi_warning_title" msgid="727104571653031865">"Allow wireless debugging?"</string> <string name="adbwifi_warning_message" msgid="8005936574322702388">"Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string> - <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you\'ve previously authorised?"</string> + <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you’ve previously authorized?"</string> <string name="dev_settings_warning_title" msgid="8251234890169074553">"Allow development settings?"</string> <string name="dev_settings_warning_message" msgid="37741686486073668">"These settings are intended for development use only. They can cause your device and the applications on it to break or misbehave."</string> <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verify apps over USB"</string> - <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string> + <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behavior."</string> <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string> <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string> <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string> - <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the enhanced connectivity feature."</string> + <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the Enhanced Connectivity feature."</string> <string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string> <string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string> <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string> - <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behaviour"</string> + <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behavior"</string> <string name="debug_debugging_category" msgid="535341063709248842">"Debugging"</string> <string name="debug_app" msgid="8903350241392391766">"Select debug app"</string> <string name="debug_app_not_set" msgid="1934083001283807188">"No debug application set"</string> @@ -363,7 +363,7 @@ <string name="debug_hw_overdraw" msgid="8944851091008756796">"Debug GPU overdraw"</string> <string name="disable_overlays" msgid="4206590799671557143">"Disable HW overlays"</string> <string name="disable_overlays_summary" msgid="1954852414363338166">"Always use GPU for screen compositing"</string> - <string name="simulate_color_space" msgid="1206503300335835151">"Simulate colour space"</string> + <string name="simulate_color_space" msgid="1206503300335835151">"Simulate color space"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Enable OpenGL traces"</string> <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Disable USB audio routing"</string> <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Disable automatic routing to USB audio peripherals"</string> @@ -379,31 +379,31 @@ <string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Enable GPU debug layers"</string> <string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Allow loading GPU debug layers for debug apps"</string> <string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"Enable verbose vendor logging"</string> - <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery and/or use more storage."</string> + <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery, and/or use more storage."</string> <string name="window_animation_scale_title" msgid="5236381298376812508">"Window animation scale"</string> <string name="transition_animation_scale_title" msgid="1278477690695439337">"Transition animation scale"</string> <string name="animator_duration_scale_title" msgid="7082913931326085176">"Animator duration scale"</string> <string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulate secondary displays"</string> <string name="debug_applications_category" msgid="5394089406638954196">"Apps"</string> - <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don\'t keep activities"</string> + <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don’t keep activities"</string> <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Destroy every activity as soon as the user leaves it"</string> <string name="app_process_limit_title" msgid="8361367869453043007">"Background process limit"</string> <string name="show_all_anrs" msgid="9160563836616468726">"Show background ANRs"</string> - <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialogue for background apps"</string> + <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialog for background apps"</string> <string name="show_notification_channel_warnings" msgid="3448282400127597331">"Show notification channel warnings"</string> <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Displays on-screen warning when an app posts a notification without a valid channel"</string> <string name="force_allow_on_external" msgid="9187902444231637880">"Force allow apps on external"</string> <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Makes any app eligible to be written to external storage, regardless of manifest values"</string> - <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizeable"</string> - <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string> + <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizable"</string> + <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizable for multi-window, regardless of manifest values."</string> <string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string> <string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string> <string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string> <string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string> - <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string> + <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren’t currently protected"</string> <string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string> <string name="local_backup_password_toast_success" msgid="4891666204428091604">"New backup password set"</string> - <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don\'t match"</string> + <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don’t match"</string> <string name="local_backup_password_toast_validation_failure" msgid="714669442363647122">"Failure setting backup password"</string> <string name="loading_injected_setting_summary" msgid="8394446285689070348">"Loading…"</string> <string-array name="color_mode_names"> @@ -412,9 +412,9 @@ <item msgid="6564241960833766170">"Standard"</item> </string-array> <string-array name="color_mode_descriptions"> - <item msgid="6828141153199944847">"Enhanced colours"</item> - <item msgid="4548987861791236754">"Natural colours as seen by the eye"</item> - <item msgid="1282170165150762976">"Colours optimised for digital content"</item> + <item msgid="6828141153199944847">"Enhanced colors"</item> + <item msgid="4548987861791236754">"Natural colors as seen by the eye"</item> + <item msgid="1282170165150762976">"Colors optimized for digital content"</item> </string-array> <string name="inactive_apps_title" msgid="5372523625297212320">"Standby apps"</string> <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Inactive. Tap to toggle."</string> @@ -431,15 +431,15 @@ <string name="select_webview_provider_title" msgid="3917815648099445503">"WebView implementation"</string> <string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Set WebView implementation"</string> <string name="select_webview_provider_toast_text" msgid="8512254949169359848">"This choice is no longer valid. Try again."</string> - <string name="picture_color_mode" msgid="1013807330552931903">"Picture colour mode"</string> + <string name="picture_color_mode" msgid="1013807330552931903">"Picture color mode"</string> <string name="picture_color_mode_desc" msgid="151780973768136200">"Use sRGB"</string> <string name="daltonizer_mode_disabled" msgid="403424372812399228">"Disabled"</string> <string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Monochromacy"</string> <string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Deuteranomaly (red-green)"</string> <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> - <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Colour correction can be helpful when you want to:<br/> <ol> <li>&nbsp;See colours more accurately</li> <li>&nbsp;Remove colours to help you focus</li> </ol>"</string> + <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Color correction"</string> + <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Color correction can be helpful when you want to:<br/> <ol> <li>&nbsp;See colors more accurately</li> <li>&nbsp;Remove colors to help you focus</li> </ol>"</string> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -466,7 +466,7 @@ <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Device may shut down soon (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string> <string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string> <string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string> - <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string> + <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string> <string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> @@ -477,9 +477,9 @@ <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string> <string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string> - <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string> + <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully Charged"</string> <string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string> - <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string> + <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string> <string name="disabled" msgid="8017887509554714950">"Disabled"</string> <string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string> <string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string> @@ -505,13 +505,13 @@ <string name="active_input_method_subtypes" msgid="4232680535471633046">"Active input methods"</string> <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Use system languages"</string> <string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string> - <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string> + <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string> <string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Note: After a reboot, this app can\'t start until you unlock your phone"</string> <string name="ims_reg_title" msgid="8197592958123671062">"IMS registration state"</string> <string name="ims_reg_status_registered" msgid="884916398194885457">"Registered"</string> <string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string> <string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string> - <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string> + <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomized"</string> <string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string> <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string> <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string> @@ -520,7 +520,7 @@ <string name="done" msgid="381184316122520313">"Done"</string> <string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarms and reminders"</string> <string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Allow setting alarms and reminders"</string> - <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms and reminders"</string> + <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms & reminders"</string> <string name="alarms_and_reminders_footer_title" msgid="6302587438389079695">"Allow this app to set alarms and schedule time-sensitive actions. This lets the app run in the background, which may use more battery.\n\nIf this permission is off, existing alarms and time-based events scheduled by this app won’t work."</string> <string name="keywords_alarms_and_reminders" msgid="6633360095891110611">"schedule, alarm, reminder, clock"</string> <string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Turn on"</string> @@ -539,7 +539,7 @@ <string name="media_transfer_this_device_name" product="default" msgid="2357329267148436433">"This phone"</string> <string name="media_transfer_this_device_name" product="tablet" msgid="3714653244000242800">"This tablet"</string> <string name="media_transfer_this_phone" msgid="7194341457812151531">"This phone"</string> - <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string> + <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off & back on"</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string> <string name="help_label" msgid="3528360748637781274">"Help and feedback"</string> <string name="storage_category" msgid="2287342585424631813">"Storage"</string> @@ -548,23 +548,23 @@ <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"There is no shared data for this user."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"There was an error fetching shared data. Try again."</string> <string name="blob_id_text" msgid="8680078988996308061">"Shared data ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> - <string name="blob_expires_text" msgid="7882727111491739331">"Expires on <xliff:g id="DATE">%s</xliff:g>"</string> + <string name="blob_expires_text" msgid="7882727111491739331">"Expires at <xliff:g id="DATE">%s</xliff:g>"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"There was an error deleting the shared data."</string> <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"There are no leases acquired for this shared data. Would you like to delete it?"</string> <string name="accessor_info_title" msgid="8289823651512477787">"Apps sharing data"</string> <string name="accessor_no_description_text" msgid="7510967452505591456">"No description provided by the app."</string> - <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires on <xliff:g id="DATE">%s</xliff:g>"</string> + <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires at <xliff:g id="DATE">%s</xliff:g>"</string> <string name="delete_blob_text" msgid="2819192607255625697">"Delete shared data"</string> - <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure that you want to delete this shared data?"</string> + <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure you want to delete this shared data?"</string> <string name="user_add_user_item_summary" msgid="5748424612724703400">"Users have their own apps and content"</string> <string name="user_add_profile_item_summary" msgid="5418602404308968028">"You can restrict access to apps and content from your account"</string> <string name="user_add_user_item_title" msgid="2394272381086965029">"User"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"Restricted profile"</string> <string name="user_add_user_title" msgid="5457079143694924885">"Add new user?"</string> - <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customise with apps, wallpaper and so on. Users can also adjust device settings such as Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string> + <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customize with apps, wallpaper, and so on. Users can also adjust device settings like Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string> <string name="user_add_user_message_short" msgid="3295959985795716166">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string> <string name="user_setup_dialog_title" msgid="8037342066381939995">"Set up user now?"</string> - <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure that the person is available to take the device and set up their space."</string> + <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure the person is available to take the device and set up their space"</string> <string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Set up profile now?"</string> <string name="user_setup_button_setup_now" msgid="1708269547187760639">"Set up now"</string> <string name="user_setup_button_setup_later" msgid="8712980133555493516">"Not now"</string> @@ -573,7 +573,7 @@ <string name="user_new_profile_name" msgid="2405500423304678841">"New profile"</string> <string name="user_info_settings_title" msgid="6351390762733279907">"User info"</string> <string name="profile_info_settings_title" msgid="105699672534365099">"Profile info"</string> - <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string> + <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you’ll need to set up a screen lock to protect your apps and personal data."</string> <string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string> <string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string> <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string> @@ -616,7 +616,7 @@ <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string> <string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string> <string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string> - <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string> + <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string> <string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"On"</string> <string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Off"</string> <string name="carrier_network_change_mode" msgid="4257621815706644026">"Carrier network changing"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 1c4e4a05d517..b1ef50af9535 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -174,7 +174,7 @@ <string name="launch_defaults_some" msgid="3631650616557252926">"कुछ डिफ़ॉल्ट सेट हैं"</string> <string name="launch_defaults_none" msgid="8049374306261262709">"कोई डिफ़ॉल्ट सेट नहीं है"</string> <string name="tts_settings" msgid="8130616705989351312">"लेख से बोली सेटिंग"</string> - <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलना"</string> + <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलने की सुविधा"</string> <string name="tts_default_rate_title" msgid="3964187817364304022">"बोली दर"</string> <string name="tts_default_rate_summary" msgid="3781937042151716987">"बोलने की गति तय करें"</string> <string name="tts_default_pitch_title" msgid="6988592215554485479">"पिच"</string> diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml index 7e65fa0d42df..928ebc3b413e 100644 --- a/packages/SettingsLib/res/values-nb/arrays.xml +++ b/packages/SettingsLib/res/values-nb/arrays.xml @@ -49,9 +49,9 @@ <item msgid="1999413958589971747">"Unngår dårlig tilkobling midlertidig"</item> </string-array> <string-array name="hdcp_checking_titles"> - <item msgid="2377230797542526134">"Kontrollér aldri"</item> - <item msgid="3919638466823112484">"Kontrollér kun DRM-innhold"</item> - <item msgid="9048424957228926377">"Kontrollér alltid"</item> + <item msgid="2377230797542526134">"Kontroller aldri"</item> + <item msgid="3919638466823112484">"Kontroller kun DRM-innhold"</item> + <item msgid="9048424957228926377">"Kontroller alltid"</item> </string-array> <string-array name="hdcp_checking_summaries"> <item msgid="4045840870658484038">"Bruk aldri HDCP-kontroll"</item> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index c292e8817c69..59a2517db38c 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -427,7 +427,7 @@ <string name="transcode_notification" msgid="5560515979793436168">"Vis omkodingsvarsler"</string> <string name="transcode_disable_cache" msgid="3160069309377467045">"Slå av omkodingsbuffer"</string> <string name="runningservices_settings_title" msgid="6460099290493086515">"Aktive tjenester"</string> - <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontrollér tjenester som kjører"</string> + <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontroller tjenester som kjører"</string> <string name="select_webview_provider_title" msgid="3917815648099445503">"WebView-implementering"</string> <string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Angi WebView-implementering"</string> <string name="select_webview_provider_toast_text" msgid="8512254949169359848">"Dette valget er ikke gyldig lenger. Prøv på nytt."</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 950ee21ae7b5..9583a59148fc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -668,6 +668,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> * @param bluetoothProfile the Bluetooth profile */ public void onActiveDeviceChanged(boolean isActive, int bluetoothProfile) { + if (BluetoothUtils.D) { + Log.d(TAG, "onActiveDeviceChanged: " + + "profile " + BluetoothProfile.getProfileName(bluetoothProfile) + + ", device " + mDevice.getAnonymizedAddress() + + ", isActive " + isActive); + } boolean changed = false; switch (bluetoothProfile) { case BluetoothProfile.A2DP: diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index fa96a2f0ee7f..0b7b2f935e91 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -112,10 +112,10 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.FrameworkStatsLog; import com.android.providers.settings.SettingsState.Setting; -import libcore.util.HexEncoding; - import com.google.android.collect.Sets; +import libcore.util.HexEncoding; + import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; @@ -1144,7 +1144,7 @@ public class SettingsProvider extends ContentProvider { Slog.v(LOG_TAG, "getConfigSetting(" + name + ")"); } - DeviceConfig.enforceReadPermission(getContext(), /*namespace=*/name.split("/")[0]); + DeviceConfig.enforceReadPermission(/*namespace=*/name.split("/")[0]); // Get the value. synchronized (mLock) { @@ -1317,7 +1317,7 @@ public class SettingsProvider extends ContentProvider { Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix); } - DeviceConfig.enforceReadPermission(getContext(), + DeviceConfig.enforceReadPermission( prefix != null ? prefix.split("/")[0] : null); synchronized (mLock) { diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 2e4a245df6a6..01c080990cfd 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -771,6 +771,9 @@ <!-- Permissions required for CTS test - CtsAppFgsTestCases --> <uses-permission android:name="android.permission.USE_EXACT_ALARM" /> + <!-- Permission required for CTS test - ApplicationExemptionsTests --> + <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt index 1d808ba7ee16..74e6d85f5374 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt @@ -59,11 +59,11 @@ class BindServiceOnMainThreadDetector : Detector(), SourceCodeScanner { !hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java)) ) { context.report( - ISSUE, - method, - context.getLocation(node), - "This method should be annotated with `@WorkerThread` because " + - "it calls ${method.name}", + issue = ISSUE, + location = context.getLocation(node), + message = + "This method should be annotated with `@WorkerThread` because " + + "it calls ${method.name}", ) } } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt index 112992913661..344d0a3f3187 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt @@ -52,10 +52,9 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner { val evaluator = context.evaluator if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( - ISSUE, - method, - context.getNameLocation(node), - "`Context.${method.name}()` should be replaced with " + + issue = ISSUE, + location = context.getNameLocation(node), + message = "`Context.${method.name}()` should be replaced with " + "`BroadcastSender.${method.name}()`" ) } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt index bab76ab4bce2..14099ebef56c 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt @@ -38,10 +38,9 @@ class NonInjectedMainThreadDetector : Detector(), SourceCodeScanner { override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( - ISSUE, - method, - context.getNameLocation(node), - "Replace with injected `@Main Executor`." + issue = ISSUE, + location = context.getNameLocation(node), + message = "Replace with injected `@Main Executor`." ) } } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt index b62290025437..aa4b2f766bf0 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt @@ -44,11 +44,11 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner { method.containingClass?.qualifiedName == CLASS_CONTEXT ) { context.report( - ISSUE, - method, - context.getNameLocation(node), - "Use `@Inject` to get system-level service handles instead of " + - "`Context.getSystemService()`" + issue = ISSUE, + location = context.getNameLocation(node), + message = + "Use `@Inject` to get system-level service handles instead of " + + "`Context.getSystemService()`" ) } else if ( evaluator.isStatic(method) && @@ -56,10 +56,10 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner { method.containingClass?.qualifiedName == "android.accounts.AccountManager" ) { context.report( - ISSUE, - method, - context.getNameLocation(node), - "Replace `AccountManager.get()` with an injected instance of `AccountManager`" + issue = ISSUE, + location = context.getNameLocation(node), + message = + "Replace `AccountManager.get()` with an injected instance of `AccountManager`" ) } } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt index 4ba3afc7f7e2..5840e8f8dfb6 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt @@ -38,10 +38,10 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner { override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) { context.report( - ISSUE, - method, - context.getNameLocation(node), - "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`" + issue = ISSUE, + location = context.getNameLocation(node), + message = "Register `BroadcastReceiver` using `BroadcastDispatcher` instead " + + "of `Context`" ) } } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt index 7be21a512f89..b15a41b226df 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt @@ -46,10 +46,10 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { method.containingClass?.qualifiedName == "android.app.ActivityManager" ) { context.report( - ISSUE_SLOW_USER_ID_QUERY, - method, - context.getNameLocation(node), - "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`" + issue = ISSUE_SLOW_USER_ID_QUERY, + location = context.getNameLocation(node), + message = + "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`" ) } if ( @@ -58,10 +58,9 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner { method.containingClass?.qualifiedName == "android.os.UserManager" ) { context.report( - ISSUE_SLOW_USER_INFO_QUERY, - method, - context.getNameLocation(node), - "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`" + issue = ISSUE_SLOW_USER_INFO_QUERY, + location = context.getNameLocation(node), + message = "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`" ) } } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt index 4b9aa13c0240..bf025894d66f 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt @@ -44,10 +44,9 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner { val evaluator = context.evaluator if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) { context.report( - ISSUE, - referenced, - context.getNameLocation(reference), - "Replace software bitmap with `Config.HARDWARE`" + issue = ISSUE, + location = context.getNameLocation(reference), + message = "Replace software bitmap with `Config.HARDWARE`" ) } } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt index 1db072548a76..22f15bdcb5bd 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt @@ -66,10 +66,9 @@ class StaticSettingsProviderDetector : Detector(), SourceCodeScanner { val subclassName = className.substring(CLASS_SETTINGS.length + 1) context.report( - ISSUE, - method, - context.getNameLocation(node), - "`@Inject` a ${subclassName}Settings instead" + issue = ISSUE, + location = context.getNameLocation(node), + message = "`@Inject` a ${subclassName}Settings instead" ) } diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt index c35ac61a6543..426211e0f327 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt @@ -126,6 +126,32 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() { } @Test + fun testSuppressUnbindService() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.content.ServiceConnection; + + @SuppressLint("BindServiceOnMainThread") + public class TestClass { + public void unbind(Context context, ServiceConnection connection) { + context.unbindService(connection); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(BindServiceOnMainThreadDetector.ISSUE) + .run() + .expectClean() + } + + @Test fun testWorkerMethod() { lint() .files( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt index 376acb56fac9..30b68f7e7a75 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt @@ -129,6 +129,34 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() { } @Test + fun testSuppressSendBroadcastInActivity() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.app.Activity; + import android.os.UserHandle; + + public class TestClass { + @SuppressWarnings("BroadcastSentViaContext") + public void send(Activity activity) { + Intent intent = new Intent(Intent.ACTION_VIEW); + activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission"); + } + + } + """ + ) + .indented(), + *stubs + ) + .issues(BroadcastSentViaContextDetector.ISSUE) + .run() + .expectClean() + } + + @Test fun testSendBroadcastInBroadcastSender() { lint() .files( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt index 301c338f9b42..ed3d14a1f33f 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt @@ -61,6 +61,32 @@ class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() { } @Test + fun testSuppressGetMainThreadHandler() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.os.Handler; + + @SuppressWarnings("NonInjectedMainThread") + public class TestClass { + public void test(Context context) { + Handler mainThreadHandler = context.getMainThreadHandler(); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(NonInjectedMainThreadDetector.ISSUE) + .run() + .expectClean() + } + + @Test fun testGetMainLooper() { lint() .files( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt index 0a74bfcfee57..846129aa12c1 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt @@ -91,6 +91,32 @@ class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() { } @Test + fun testSuppressGetServiceWithClass() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.Context; + import android.os.UserManager; + + public class TestClass { + @SuppressLint("NonInjectedService") + public void getSystemServiceWithoutDagger(Context context) { + context.getSystemService(UserManager.class); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(NonInjectedServiceDetector.ISSUE) + .run() + .expectClean() + } + + @Test fun testGetAccountManager() { lint() .files( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt index 9ed7aa029b1d..0ac8f8e7c672 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt @@ -63,6 +63,34 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() { } @Test + fun testSuppressRegisterReceiver() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.content.BroadcastReceiver; + import android.content.Context; + import android.content.IntentFilter; + + @SuppressWarnings("RegisterReceiverViaContext") + public class TestClass { + public void bind(Context context, BroadcastReceiver receiver, + IntentFilter filter) { + context.registerReceiver(receiver, filter, 0); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(RegisterReceiverViaContextDetector.ISSUE) + .run() + .expectClean() + } + + @Test fun testRegisterReceiverAsUser() { lint() .files( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt index 54cac7b35598..34a424918a79 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt @@ -76,7 +76,7 @@ class SlowUserQueryDetectorTest : SystemUILintDetectorTest() { import android.os.UserManager; public class TestClass { - public void slewlyGetUserInfo(UserManager userManager) { + public void slowlyGetUserInfo(UserManager userManager) { userManager.getUserInfo(); } } @@ -101,6 +101,34 @@ class SlowUserQueryDetectorTest : SystemUILintDetectorTest() { } @Test + fun testSuppressGetUserInfo() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + import android.os.UserManager; + + public class TestClass { + @SuppressWarnings("SlowUserInfoQuery") + public void slowlyGetUserInfo(UserManager userManager) { + userManager.getUserInfo(); + } + } + """ + ) + .indented(), + *stubs + ) + .issues( + SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY, + SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY + ) + .run() + .expectClean() + } + + @Test fun testUserTrackerGetUserId() { lint() .files( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt index c632636eb9c8..34becc6a5b04 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt @@ -63,6 +63,31 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() { } @Test + fun testSuppressSoftwareBitmap() { + lint() + .files( + TestFiles.java( + """ + import android.graphics.Bitmap; + + @SuppressWarnings("SoftwareBitmap") + public class TestClass { + public void test() { + Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565); + Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(SoftwareBitmapDetector.ISSUE) + .run() + .expectClean() + } + + @Test fun testHardwareBitmap() { lint() .files( diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt index b83ed7067bc3..efe4c90ec44f 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt @@ -28,7 +28,7 @@ class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() { override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE) @Test - fun testGetServiceWithString() { + fun testSuppressGetServiceWithString() { lint() .files( TestFiles.java( @@ -204,5 +204,34 @@ class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() { ) } + @Test + fun testGetServiceWithString() { + lint() + .files( + TestFiles.java( + """ + package test.pkg; + + import android.provider.Settings; + import android.provider.Settings.Global; + import android.provider.Settings.Secure; + + public class TestClass { + @SuppressWarnings("StaticSettingsProvider") + public void getSystemServiceWithoutDagger(Context context) { + final ContentResolver cr = mContext.getContentResolver(); + Global.getFloat(cr, Settings.Global.UNLOCK_SOUND); + } + } + """ + ) + .indented(), + *stubs + ) + .issues(StaticSettingsProviderDetector.ISSUE) + .run() + .expectClean() + } + private val stubs = androidStubs } diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index a3b4b385f5bd..69767867ebd7 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -25,9 +25,15 @@ -keep class ** extends androidx.preference.PreferenceFragment -keep class com.android.systemui.tuner.* + +# The plugins and animation subpackages both act as shared libraries that might be referenced in +# dynamically-loaded plugin APKs. -keep class com.android.systemui.plugins.** { *; } +-keep class !com.android.systemui.animation.R$**,com.android.systemui.animation.** { + *; +} -keep class com.android.systemui.fragments.FragmentService$FragmentCreator { *; } diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml index c8ba237088f2..a948c04020f3 100644 --- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml @@ -23,7 +23,7 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Enter your PIN"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Enter your pattern"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"Enter your password"</string> - <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid card."</string> + <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid Card."</string> <string name="keyguard_charged" msgid="5478247181205188995">"Charged"</string> <string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging wirelessly"</string> <string name="keyguard_plugged_in_dock" msgid="2122073051904360987">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string> @@ -70,7 +70,7 @@ <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Incorrect SIM PIN code you must now contact your carrier to unlock your device."</string> <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}other{Incorrect SIM PIN code, you have # remaining attempts. }}"</string> <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM is unusable. Contact your carrier."</string> - <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code; you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string> + <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code, you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string> <string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM PIN operation failed!"</string> <string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK operation failed!"</string> <string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string> @@ -83,12 +83,12 @@ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string> - <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string> + <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognized"</string> <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"To use Face Unlock, turn on camera access in Settings"</string> <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Enter SIM PIN. You have # remaining attempt before you must contact your carrier to unlock your device.}other{Enter SIM PIN. You have # remaining attempts.}}"</string> <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact carrier for details.}other{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}}"</string> <string name="clock_title_default" msgid="6342735240617459864">"Default"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string> - <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string> + <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string> </resources> diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml index d27fa192e741..8b8594032816 100644 --- a/packages/SystemUI/res/layout/keyguard_status_bar.xml +++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml @@ -34,30 +34,13 @@ android:paddingTop="@dimen/status_bar_padding_top" android:layout_alignParentEnd="true" android:gravity="center_vertical|end" > - <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer + + <include android:id="@+id/user_switcher_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal" - android:paddingTop="4dp" - android:paddingBottom="4dp" - android:paddingStart="8dp" - android:paddingEnd="8dp" - android:background="@drawable/status_bar_user_chip_bg" - android:visibility="visible" > - <ImageView android:id="@+id/current_user_avatar" - android:layout_width="@dimen/multi_user_avatar_keyguard_size" - android:layout_height="@dimen/multi_user_avatar_keyguard_size" - android:scaleType="centerInside" - android:paddingEnd="4dp" /> - - <TextView android:id="@+id/current_user_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.StatusBar.Clock" - /> - </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer> + android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin" + layout="@layout/status_bar_user_chip_container" /> <FrameLayout android:id="@+id/system_icons_container" android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 80e65a3b3295..f7600e606731 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -136,31 +136,12 @@ android:gravity="center_vertical|end" android:clipChildren="false"> - <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer + <include android:id="@+id/user_switcher_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal" - android:paddingTop="4dp" - android:paddingBottom="4dp" - android:paddingStart="8dp" - android:paddingEnd="8dp" - android:layout_marginEnd="16dp" - android:background="@drawable/status_bar_user_chip_bg" - android:visibility="visible" > - <ImageView android:id="@+id/current_user_avatar" - android:layout_width="@dimen/multi_user_avatar_keyguard_size" - android:layout_height="@dimen/multi_user_avatar_keyguard_size" - android:scaleType="centerInside" - android:paddingEnd="4dp" /> - - <TextView android:id="@+id/current_user_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.StatusBar.Clock" - /> - </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer> + android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin" + layout="@layout/status_bar_user_chip_container" /> <include layout="@layout/system_icons" /> </com.android.keyguard.AlphaOptimizedLinearLayout> diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml new file mode 100644 index 000000000000..b374074958cb --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml @@ -0,0 +1,40 @@ +<?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. + --> + +<com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/user_switcher_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal" + android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin" + android:background="@drawable/status_bar_user_chip_bg" + android:visibility="visible" > + <ImageView android:id="@+id/current_user_avatar" + android:layout_width="@dimen/status_bar_user_chip_avatar_size" + android:layout_height="@dimen/status_bar_user_chip_avatar_size" + android:layout_margin="4dp" + android:scaleType="centerInside" /> + + <TextView android:id="@+id/current_user_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingEnd="8dp" + android:textAppearance="@style/TextAppearance.StatusBar.UserChip" + /> +</com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer> diff --git a/packages/SystemUI/res/values-en-rCA/strings_tv.xml b/packages/SystemUI/res/values-en-rCA/strings_tv.xml index e97dbe4bd491..a62884693ed8 100644 --- a/packages/SystemUI/res/values-en-rCA/strings_tv.xml +++ b/packages/SystemUI/res/values-en-rCA/strings_tv.xml @@ -23,13 +23,13 @@ <string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string> <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string> <string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string> - <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No notifications"</string> + <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No Notifications"</string> <string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string> <string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string> - <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and microphone are recording"</string> + <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and Microphone are recording"</string> <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string> <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string> - <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and microphone stopped recording"</string> + <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and Microphone stopped recording"</string> <string name="screen_recording_announcement" msgid="2996750593472241520">"Screen recording started"</string> <string name="screen_stopped_recording_announcement" msgid="979749439036681416">"Screen recording stopped"</string> </resources> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 6f871695c491..99bc794547f2 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -24,11 +24,6 @@ <item name="android:windowIsFloating">true</item> </style> - <style name="TextAppearance.QS.Status" parent="TextAppearance.QS.TileLabel.Secondary"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> - </style> - <!-- Screenshots --> <style name="LongScreenshotActivity" parent="@android:style/Theme.DeviceDefault.DayNight"> <item name="android:windowNoTitle">true</item> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 7a362040427a..b8e2caf2a211 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -285,6 +285,9 @@ <!-- Whether to show the full screen user switcher. --> <bool name="config_enableFullscreenUserSwitcher">false</bool> + <!-- Determines whether the shell features all run on another thread. --> + <bool name="config_enableShellMainThread">true</bool> + <!-- SystemUIFactory component --> <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index fbdccff38731..8501b5c4547b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1341,6 +1341,7 @@ <dimen name="accessibility_floating_menu_large_width_height">56dp</dimen> <dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen> <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen> + <dimen name="accessibility_floating_menu_ime_shifting_space">48dp</dimen> <dimen name="accessibility_floating_menu_message_container_horizontal_padding">15dp</dimen> <dimen name="accessibility_floating_menu_message_text_vertical_padding">8dp</dimen> @@ -1422,6 +1423,11 @@ <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen> <dimen name="ongoing_call_chip_corner_radius">28dp</dimen> + <!-- Status bar user chip --> + <dimen name="status_bar_user_chip_avatar_size">16dp</dimen> + <dimen name="status_bar_user_chip_end_margin">12dp</dimen> + <dimen name="status_bar_user_chip_text_size">12sp</dimen> + <!-- Internet panel related dimensions --> <dimen name="internet_dialog_list_max_height">662dp</dimen> <!-- The height of the WiFi network in Internet panel. --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 72c8163e7517..9eafdb959f07 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1996,6 +1996,9 @@ <!-- SysUI Tuner: Summary of no shortcut being selected [CHAR LIMIT=60] --> <string name="lockscreen_none">None</string> + <!-- ClockId to use when none is set by user --> + <string name="lockscreen_clock_id_fallback" translatable="false">DEFAULT</string> + <!-- SysUI Tuner: Format string for describing launching an app [CHAR LIMIT=60] --> <string name="tuner_launch_app">Launch <xliff:g id="app" example="Settings">%1$s</xliff:g></string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index ae80070dfa97..fe4f639c307e 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -23,6 +23,12 @@ <item name="android:textColor">@color/status_bar_clock_color</item> </style> + <style name="TextAppearance.StatusBar.UserChip" parent="@*android:style/TextAppearance.StatusBar.Icon"> + <item name="android:textSize">@dimen/status_bar_user_chip_text_size</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + <item name="android:textColor">@color/status_bar_clock_color</item> + </style> + <style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar"> <item name="android:textColor">?android:attr/textColorTertiary</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 601cb66d99c2..5c2c27a36e59 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -41,6 +41,7 @@ open class ClockRegistry( val isEnabled: Boolean, userHandle: Int, defaultClockProvider: ClockProvider, + val fallbackClockId: ClockId = DEFAULT_CLOCK_ID, ) { // Usually this would be a typealias, but a SAM provides better java interop fun interface ClockChangeListener { @@ -69,10 +70,13 @@ open class ClockRegistry( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE ) - ClockSetting.deserialize(json)?.clockId ?: DEFAULT_CLOCK_ID + if (json == null || json.isEmpty()) { + return fallbackClockId + } + ClockSetting.deserialize(json).clockId } catch (ex: Exception) { Log.e(TAG, "Failed to parse clock setting", ex) - DEFAULT_CLOCK_ID + fallbackClockId } } set(value) { diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt index 450784ea8f03..f59bf8e766fe 100644 --- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt +++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt @@ -69,10 +69,16 @@ open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) : super.reloadColor() } - override fun setMessage(msg: CharSequence?) { + override fun setMessage(msg: CharSequence?, animate: Boolean) { if ((msg == textAboutToShow && msg != null) || msg == text) { return } + + if (!animate) { + super.setMessage(msg, animate) + return + } + textAboutToShow = msg if (animatorSet.isRunning) { @@ -89,7 +95,7 @@ open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) : hideAnimator.addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { - super@BouncerKeyguardMessageArea.setMessage(msg) + super@BouncerKeyguardMessageArea.setMessage(msg, animate) } } ) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 92ba619e7eb9..3e32cf5521ff 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -159,10 +159,12 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); Map<String, Object> arguments = new HashMap<>(); arguments.put("count", secondsRemaining); - mMessageAreaController.setMessage(PluralsMessageFormatter.format( - mView.getResources(), - arguments, - R.string.kg_too_many_failed_attempts_countdown)); + mMessageAreaController.setMessage( + PluralsMessageFormatter.format( + mView.getResources(), + arguments, + R.string.kg_too_many_failed_attempts_countdown), + /* animate= */ false); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index a0206f1f1e70..819768544b0c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -50,10 +50,9 @@ data class KeyguardFaceListenModel( override val listening: Boolean, // keep sorted val authInterruptActive: Boolean, - val becauseCannotSkipBouncer: Boolean, val biometricSettingEnabledForUser: Boolean, val bouncerFullyShown: Boolean, - val faceAuthenticated: Boolean, + val faceAndFpNotAuthenticated: Boolean, val faceDisabled: Boolean, val faceLockedOut: Boolean, val fpLockedOut: Boolean, @@ -67,7 +66,9 @@ data class KeyguardFaceListenModel( val secureCameraLaunched: Boolean, val switchingUser: Boolean, val udfpsBouncerShowing: Boolean, -) : KeyguardListenModel() + val udfpsFingerDown: Boolean, + val userNotTrustedOrDetectionIsNeeded: Boolean, + ) : KeyguardListenModel() /** * Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock]. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java index c79fc2c27f0e..0e5f8c1c7a26 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java @@ -59,6 +59,7 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe @Nullable private ViewGroup mContainer; private int mTopMargin; + protected boolean mAnimate; public KeyguardMessageArea(Context context, AttributeSet attrs) { super(context, attrs); @@ -106,7 +107,7 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe } @Override - public void setMessage(CharSequence msg) { + public void setMessage(CharSequence msg, boolean animate) { if (!TextUtils.isEmpty(msg)) { securityMessageChanged(msg); } else { @@ -115,21 +116,12 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe } @Override - public void setMessage(int resId) { - CharSequence message = null; - if (resId != 0) { - message = getContext().getResources().getText(resId); - } - setMessage(message); - } - - @Override public void formatMessage(int resId, Object... formatArgs) { CharSequence message = null; if (resId != 0) { message = getContext().getString(resId, formatArgs); } - setMessage(message); + setMessage(message, true); } private void securityMessageChanged(CharSequence message) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java index db986e0a631a..c29f632b88d3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java @@ -92,11 +92,19 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea> } public void setMessage(CharSequence s) { - mView.setMessage(s); + setMessage(s, true); + } + + /** + * Sets a message to the underlying text view. + */ + public void setMessage(CharSequence s, boolean animate) { + mView.setMessage(s, animate); } public void setMessage(int resId) { - mView.setMessage(resId); + String message = resId != 0 ? mView.getResources().getString(resId) : null; + setMessage(message); } public void setNextMessageColor(ColorStateList colorState) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 1f0bd54f8e09..cdbfb2492e27 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -372,10 +372,13 @@ public class KeyguardPatternViewController Map<String, Object> arguments = new HashMap<>(); arguments.put("count", secondsRemaining); - mMessageAreaController.setMessage(PluralsMessageFormatter.format( - mView.getResources(), - arguments, - R.string.kg_too_many_failed_attempts_countdown)); + mMessageAreaController.setMessage( + PluralsMessageFormatter.format( + mView.getResources(), + arguments, + R.string.kg_too_many_failed_attempts_countdown), + /* animate= */ false + ); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 2d3dda9b44a5..ce22a81befb5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -794,9 +794,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } // Don't send cancel if authentication succeeds mFingerprintCancelSignal = null; + mLogger.logFingerprintSuccess(userId, isStrongBiometric); updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_FP_AUTHENTICATED); - mLogger.logFingerprintSuccess(userId, isStrongBiometric); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -2711,9 +2711,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean canBypass = mKeyguardBypassController != null && mKeyguardBypassController.canBypass(); // There's no reason to ask the HAL for authentication when the user can dismiss the - // bouncer, unless we're bypassing and need to auto-dismiss the lock screen even when - // TrustAgents or biometrics are keeping the device unlocked. - final boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass; + // bouncer because the user is trusted, unless we're bypassing and need to auto-dismiss + // the lock screen even when TrustAgents are keeping the device unlocked. + final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass; // Scan even when encrypted or timeout to show a preemptive bouncer when bypassing. // Lock-down mode shouldn't scan, since it is more explicit. @@ -2730,11 +2730,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab strongAuthAllowsScanning = false; } - // If the face has recently been authenticated do not attempt to authenticate again. - final boolean faceAuthenticated = getIsFaceAuthenticated(); + // If the face or fp has recently been authenticated do not attempt to authenticate again. + final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user); final boolean faceDisabledForUser = isFaceDisabled(user); final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user); final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant(); + final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown(); // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. @@ -2744,13 +2745,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || mOccludingAppRequestingFace || awakeKeyguard || shouldListenForFaceAssistant - || mAuthController.isUdfpsFingerDown() + || isUdfpsFingerDown || mUdfpsBouncerShowing) - && !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer + && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded && !mKeyguardGoingAway && biometricEnabledForUser && strongAuthAllowsScanning && mIsPrimaryUser && (!mSecureCameraLaunched || mOccludingAppRequestingFace) - && !faceAuthenticated + && faceAndFpNotAuthenticated && !mGoingToSleep // We only care about fp locked out state and not face because we still trigger // face auth even when face is locked out to show the user a message that face @@ -2764,10 +2765,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab user, shouldListen, mAuthInterruptActive, - becauseCannotSkipBouncer, biometricEnabledForUser, mPrimaryBouncerFullyShown, - faceAuthenticated, + faceAndFpNotAuthenticated, faceDisabledForUser, isFaceLockedOut(), fpLockedOut, @@ -2780,7 +2780,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab strongAuthAllowsScanning, mSecureCameraLaunched, mSwitchingUser, - mUdfpsBouncerShowing)); + mUdfpsBouncerShowing, + isUdfpsFingerDown, + userNotTrustedOrDetectionIsNeeded)); return shouldListen; } diff --git a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java index 777bd19864bf..3392a1cda90e 100644 --- a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java +++ b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java @@ -23,9 +23,10 @@ public interface SecurityMessageDisplay { /** Set text color for the next security message. */ default void setNextMessageColor(ColorStateList colorState) {} - void setMessage(CharSequence msg); - - void setMessage(int resId); + /** + * Sets a message to the underlying text view. + */ + void setMessage(CharSequence msg, boolean animate); void formatMessage(int resId, Object... formatArgs); } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 9767313331d3..b514f60efc7d 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -20,6 +20,7 @@ import android.content.Context; import android.os.Handler; import android.os.UserHandle; +import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Main; @@ -50,6 +51,7 @@ public abstract class ClockRegistryModule { handler, featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), UserHandle.USER_ALL, - defaultClockProvider); + defaultClockProvider, + context.getString(R.string.lockscreen_clock_id_fallback)); } } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java index 8fc86004c400..a7d4455b43c2 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java @@ -21,10 +21,7 @@ import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl; -import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -50,10 +47,4 @@ public abstract class KeyguardStatusBarViewModule { static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) { return view.findViewById(R.id.user_switcher_container); } - - /** */ - @Binds - @KeyguardStatusBarViewScope - abstract StatusBarUserSwitcherController bindStatusBarUserSwitcherController( - StatusBarUserSwitcherControllerImpl controller); } diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 9aa5fae1044d..70750a1dafb5 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -72,7 +72,8 @@ public final class Prefs { Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, Key.ACCESSIBILITY_FLOATING_MENU_POSITION, Key.HAS_CLICKED_NUDGE_TO_SETUP_DREAM, - Key.HAS_DISMISSED_NUDGE_TO_SETUP_DREAM + Key.HAS_DISMISSED_NUDGE_TO_SETUP_DREAM, + Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED }) // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them public @interface Key { @@ -117,6 +118,7 @@ public final class Prefs { String ACCESSIBILITY_FLOATING_MENU_POSITION = "AccessibilityFloatingMenuPosition"; String HAS_CLICKED_NUDGE_TO_SETUP_DREAM = "HasClickedNudgeToSetupDream"; String HAS_DISMISSED_NUDGE_TO_SETUP_DREAM = "HasDismissedNudgeToSetupDream"; + String HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED = "HasAccessibilityFloatingMenuTucked"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index 396f584d76a6..1e14763e57d5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -16,8 +16,6 @@ package com.android.systemui.accessibility.floatingmenu; -import static android.util.MathUtils.constrain; - import static java.util.Objects.requireNonNull; import android.animation.ValueAnimator; @@ -64,7 +62,6 @@ class MenuAnimationController { private final MenuView mMenuView; private final ValueAnimator mFadeOutAnimator; private final Handler mHandler; - private boolean mIsMovedToEdge; private boolean mIsFadeEffectEnabled; private DismissAnimationController.DismissCallback mDismissCallback; @@ -111,25 +108,25 @@ class MenuAnimationController { } void moveToTopLeftPosition() { - mIsMovedToEdge = false; + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.top)); } void moveToTopRightPosition() { - mIsMovedToEdge = false; + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.top)); } void moveToBottomLeftPosition() { - mIsMovedToEdge = false; + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.bottom)); } void moveToBottomRightPosition() { - mIsMovedToEdge = false; + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.bottom)); } @@ -254,6 +251,8 @@ class MenuAnimationController { // If the translation x is zero, it should be at the left of the bound. if (currentXTranslation < draggableBounds.left || currentXTranslation > draggableBounds.right) { + constrainPositionAndUpdate( + new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY())); moveToEdgeAndHide(); return true; } @@ -262,37 +261,33 @@ class MenuAnimationController { return false; } - private boolean isOnLeftSide() { + boolean isOnLeftSide() { return mMenuView.getTranslationX() < mMenuView.getMenuDraggableBounds().centerX(); } - boolean isMovedToEdge() { - return mIsMovedToEdge; + boolean isMoveToTucked() { + return mMenuView.isMoveToTucked(); } void moveToEdgeAndHide() { - mIsMovedToEdge = true; + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true); - final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); - final float endY = constrain(mMenuView.getTranslationY(), draggableBounds.top, - draggableBounds.bottom); - final float menuHalfWidth = mMenuView.getWidth() / 2.0f; + final PointF position = mMenuView.getMenuPosition(); + final float menuHalfWidth = mMenuView.getMenuWidth() / 2.0f; final float endX = isOnLeftSide() - ? draggableBounds.left - menuHalfWidth - : draggableBounds.right + menuHalfWidth; - moveAndPersistPosition(new PointF(endX, endY)); + ? position.x - menuHalfWidth + : position.x + menuHalfWidth; + moveToPosition(new PointF(endX, position.y)); // Keep the touch region let users could click extra space to pop up the menu view // from the screen edge - mMenuView.onBoundsInParentChanged(isOnLeftSide() - ? draggableBounds.left - : draggableBounds.right, (int) mMenuView.getTranslationY()); + mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y); fadeOutIfEnabled(); } void moveOutEdgeAndShow() { - mIsMovedToEdge = false; + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); mMenuView.onPositionChanged(); mMenuView.onEdgeChangedIfNeeded(); @@ -345,7 +340,7 @@ class MenuAnimationController { } private void constrainPositionAndUpdate(PointF position) { - final Rect draggableBounds = mMenuView.getMenuDraggableBounds(); + final Rect draggableBounds = mMenuView.getMenuDraggableBoundsExcludeIme(); // Have the space gap margin between the top bound and the menu view, so actually the // position y range needs to cut the margin. position.offset(-draggableBounds.left, -draggableBounds.top); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java index 4c52b331497d..5bc7406bb9d0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java @@ -51,6 +51,7 @@ class MenuInfoRepository { @FloatRange(from = 0.0, to = 1.0) private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.77f; + private static final boolean DEFAULT_MOVE_TO_TUCKED_VALUE = false; private final Context mContext; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -92,6 +93,12 @@ class MenuInfoRepository { mPercentagePosition = getStartPosition(); } + void loadMenuMoveToTucked(OnInfoReady<Boolean> callback) { + callback.onReady( + Prefs.getBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, + DEFAULT_MOVE_TO_TUCKED_VALUE)); + } + void loadMenuPosition(OnInfoReady<Position> callback) { callback.onReady(mPercentagePosition); } @@ -113,6 +120,11 @@ class MenuInfoRepository { getMenuOpacityFromSettings(mContext)); } + void updateMoveToTucked(boolean isMoveToTucked) { + Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, + isMoveToTucked); + } + void updateMenuSavingPosition(Position percentagePosition) { mPercentagePosition = percentagePosition; Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java index ac5736b0c26d..14517ba5bdb4 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java @@ -74,10 +74,10 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It R.string.accessibility_floating_button_action_move_bottom_right)); info.addAction(moveBottomRight); - final int moveEdgeId = mAnimationController.isMovedToEdge() + final int moveEdgeId = mAnimationController.isMoveToTucked() ? R.id.action_move_out_edge_and_show : R.id.action_move_to_edge_and_hide; - final int moveEdgeTextResId = mAnimationController.isMovedToEdge() + final int moveEdgeTextResId = mAnimationController.isMoveToTucked() ? R.string.accessibility_floating_button_action_move_out_edge_and_show : R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half; final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveToOrOutEdge = diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index 6a14af52fbaf..986aa51ecce1 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -58,12 +58,15 @@ class MenuView extends FrameLayout implements this::updateSystemGestureExcludeRects; private final Observer<MenuFadeEffectInfo> mFadeEffectInfoObserver = this::onMenuFadeEffectInfoChanged; + private final Observer<Boolean> mMoveToTuckedObserver = this::onMoveToTucked; private final Observer<Position> mPercentagePositionObserver = this::onPercentagePosition; private final Observer<Integer> mSizeTypeObserver = this::onSizeTypeChanged; private final Observer<List<AccessibilityTarget>> mTargetFeaturesObserver = this::onTargetFeaturesChanged; private final MenuViewAppearance mMenuViewAppearance; + private boolean mIsMoveToTucked; + private OnTargetFeaturesChangeListener mFeaturesChangeListener; MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) { @@ -161,6 +164,12 @@ class MenuView extends FrameLayout implements mMenuViewAppearance.getMenuStrokeColor()); } + private void onMoveToTucked(boolean isMoveToTucked) { + mIsMoveToTucked = isMoveToTucked; + + onPositionChanged(); + } + private void onPercentagePosition(Position percentagePosition) { mMenuViewAppearance.setPercentagePosition(percentagePosition); @@ -171,6 +180,10 @@ class MenuView extends FrameLayout implements final PointF position = mMenuViewAppearance.getMenuPosition(); mMenuAnimationController.moveToPosition(position); onBoundsInParentChanged((int) position.x, (int) position.y); + + if (isMoveToTucked()) { + mMenuAnimationController.moveToEdgeAndHide(); + } } @SuppressLint("NotifyDataSetChanged") @@ -219,6 +232,22 @@ class MenuView extends FrameLayout implements return mMenuViewAppearance.getMenuDraggableBounds(); } + Rect getMenuDraggableBoundsExcludeIme() { + return mMenuViewAppearance.getMenuDraggableBoundsExcludeIme(); + } + + int getMenuHeight() { + return mMenuViewAppearance.getMenuHeight(); + } + + int getMenuWidth() { + return mMenuViewAppearance.getMenuWidth(); + } + + PointF getMenuPosition() { + return mMenuViewAppearance.getMenuPosition(); + } + void persistPositionAndUpdateEdge(Position percentagePosition) { mMenuViewModel.updateMenuSavingPosition(percentagePosition); mMenuViewAppearance.setPercentagePosition(percentagePosition); @@ -226,6 +255,16 @@ class MenuView extends FrameLayout implements onEdgeChangedIfNeeded(); } + boolean isMoveToTucked() { + return mIsMoveToTucked; + } + + void updateMenuMoveToTucked(boolean isMoveToTucked) { + mIsMoveToTucked = isMoveToTucked; + mMenuViewModel.updateMenuMoveToTucked(isMoveToTucked); + } + + /** * Uses the touch events from the parent view to identify if users clicked the extra * space of the menu view. If yes, will use the percentage position and update the @@ -241,7 +280,7 @@ class MenuView extends FrameLayout implements boolean maybeMoveOutEdgeAndShow(int x, int y) { // Utilizes the touch region of the parent view to implement that users could tap extra // the space region to show the menu from the edge. - if (!mMenuAnimationController.isMovedToEdge() || !mBoundsInParent.contains(x, y)) { + if (!isMoveToTucked() || !mBoundsInParent.contains(x, y)) { return false; } @@ -258,6 +297,7 @@ class MenuView extends FrameLayout implements mMenuViewModel.getFadeEffectInfoData().observeForever(mFadeEffectInfoObserver); mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver); mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver); + mMenuViewModel.getMoveToTuckedData().observeForever(mMoveToTuckedObserver); setVisibility(VISIBLE); mMenuViewModel.registerContentObservers(); getViewTreeObserver().addOnComputeInternalInsetsListener(this); @@ -271,6 +311,7 @@ class MenuView extends FrameLayout implements mMenuViewModel.getFadeEffectInfoData().removeObserver(mFadeEffectInfoObserver); mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver); mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver); + mMenuViewModel.getMoveToTuckedData().removeObserver(mMoveToTuckedObserver); mMenuViewModel.unregisterContentObservers(); getViewTreeObserver().removeOnComputeInternalInsetsListener(this); getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index 4a9807febc7f..a7cdeab7c127 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -47,6 +47,9 @@ class MenuViewAppearance { private final Resources mRes; private final Position mPercentagePosition = new Position(/* percentageX= */ 0f, /* percentageY= */ 0f); + private boolean mIsImeShowing; + // Avoid the menu view overlapping on the primary action button under the bottom as possible. + private int mImeShiftingSpace; private int mTargetFeaturesSize; private int mSizeType; private int mMargin; @@ -62,6 +65,7 @@ class MenuViewAppearance { private int mStrokeColor; private int mInset; private int mElevation; + private float mImeTop; private float[] mRadii; private Drawable mBackgroundDrawable; private String mContentDescription; @@ -106,6 +110,8 @@ class MenuViewAppearance { mStrokeColor = mRes.getColor(R.color.accessibility_floating_menu_stroke_dark); mInset = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset); mElevation = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation); + mImeShiftingSpace = mRes.getDimensionPixelSize( + R.dimen.accessibility_floating_menu_ime_shifting_space); final Drawable drawable = mRes.getDrawable(R.drawable.accessibility_floating_menu_background); mBackgroundDrawable = new InstantInsetLayerDrawable(new Drawable[]{drawable}); @@ -131,29 +137,56 @@ class MenuViewAppearance { mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize)); } + void onImeVisibilityChanged(boolean imeShowing, float imeTop) { + mIsImeShowing = imeShowing; + mImeTop = imeTop; + } + Rect getMenuDraggableBounds() { + return getMenuDraggableBoundsWith(/* includeIme= */ true); + } + + Rect getMenuDraggableBoundsExcludeIme() { + return getMenuDraggableBoundsWith(/* includeIme= */ false); + } + + private Rect getMenuDraggableBoundsWith(boolean includeIme) { final int margin = getMenuMargin(); - final Rect draggableBounds = getWindowAvailableBounds(); + final Rect draggableBounds = new Rect(getWindowAvailableBounds()); // Initializes start position for mapping the translation of the menu view. draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0); draggableBounds.top += margin; draggableBounds.right -= getMenuWidth(); - draggableBounds.bottom -= Math.min( - getWindowAvailableBounds().height() - draggableBounds.top, - calculateActualMenuHeight() + margin); + + if (includeIme && mIsImeShowing) { + final int imeHeight = (int) (draggableBounds.bottom - mImeTop); + draggableBounds.bottom -= (imeHeight + mImeShiftingSpace); + } + draggableBounds.bottom -= (calculateActualMenuHeight() + margin); + draggableBounds.bottom = Math.max(draggableBounds.top, draggableBounds.bottom); + return draggableBounds; } PointF getMenuPosition() { - final Rect draggableBounds = getMenuDraggableBounds(); - - return new PointF( - draggableBounds.left - + draggableBounds.width() * mPercentagePosition.getPercentageX(), - draggableBounds.top - + draggableBounds.height() * mPercentagePosition.getPercentageY()); + final Rect draggableBounds = getMenuDraggableBoundsExcludeIme(); + final float x = draggableBounds.left + + draggableBounds.width() * mPercentagePosition.getPercentageX(); + + float y = draggableBounds.top + + draggableBounds.height() * mPercentagePosition.getPercentageY(); + + // If the bottom of the menu view and overlap on the ime, its position y will be + // overridden with new y. + final float menuBottom = y + getMenuHeight() + mMargin; + if (mIsImeShowing && (menuBottom >= mImeTop)) { + y = Math.max(draggableBounds.top, + mImeTop - getMenuHeight() - mMargin - mImeShiftingSpace); + } + + return new PointF(x, y); } String getContentDescription() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index b8f14aef648a..c42943cf56e2 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -16,6 +16,10 @@ package com.android.systemui.accessibility.floatingmenu; +import static android.view.WindowInsets.Type.ime; + +import static androidx.core.view.WindowInsetsCompat.Type; + import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE; import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType; import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState; @@ -26,13 +30,16 @@ import android.annotation.IntDef; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.provider.Settings; import android.util.PluralsMessageFormatter; import android.view.MotionEvent; +import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.TextView; @@ -62,14 +69,17 @@ import java.util.Map; class MenuViewLayer extends FrameLayout { private static final int SHOW_MESSAGE_DELAY_MS = 3000; + private final WindowManager mWindowManager; private final MenuView mMenuView; private final MenuMessageView mMessageView; private final DismissView mDismissView; + private final MenuViewAppearance mMenuViewAppearance; private final MenuAnimationController mMenuAnimationController; private final AccessibilityManager mAccessibilityManager; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final IAccessibilityFloatingMenu mFloatingMenu; private final DismissAnimationController mDismissAnimationController; + private final Rect mImeInsetsRect = new Rect(); @IntDef({ LayerIndex.MENU_VIEW, @@ -111,13 +121,13 @@ class MenuViewLayer extends FrameLayout { AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) { super(context); + mWindowManager = windowManager; mAccessibilityManager = accessibilityManager; mFloatingMenu = floatingMenu; final MenuViewModel menuViewModel = new MenuViewModel(context); - final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, - windowManager); - mMenuView = new MenuView(context, menuViewModel, menuViewAppearance); + mMenuViewAppearance = new MenuViewAppearance(context, windowManager); + mMenuView = new MenuView(context, menuViewModel, mMenuViewAppearance); mMenuAnimationController = mMenuView.getMenuAnimationController(); mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage); @@ -200,6 +210,7 @@ class MenuViewLayer extends FrameLayout { super.onAttachedToWindow(); mMenuView.show(); + setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets)); mMessageView.setUndoListener(view -> undo()); mContext.registerComponentCallbacks(mDismissAnimationController); } @@ -209,10 +220,35 @@ class MenuViewLayer extends FrameLayout { super.onDetachedFromWindow(); mMenuView.hide(); + setOnApplyWindowInsetsListener(null); mHandler.removeCallbacksAndMessages(/* token= */ null); mContext.unregisterComponentCallbacks(mDismissAnimationController); } + private WindowInsets onWindowInsetsApplied(WindowInsets insets) { + final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); + final WindowInsets windowInsets = windowMetrics.getWindowInsets(); + final Rect imeInsetsRect = windowInsets.getInsets(ime()).toRect(); + if (!imeInsetsRect.equals(mImeInsetsRect)) { + final Rect windowBounds = new Rect(windowMetrics.getBounds()); + final Rect systemBarsAndDisplayCutoutInsetsRect = + windowInsets.getInsetsIgnoringVisibility( + Type.systemBars() | Type.displayCutout()).toRect(); + final float imeTop = + windowBounds.height() - systemBarsAndDisplayCutoutInsetsRect.top + - imeInsetsRect.bottom; + + mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop); + + mMenuView.onEdgeChanged(); + mMenuView.onPositionChanged(); + + mImeInsetsRect.set(imeInsetsRect); + } + + return insets; + } + private void hideMenuAndShowMessage() { final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis( SHOW_MESSAGE_DELAY_MS, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java index e8a2b6e8767b..bd417877c124 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java @@ -35,6 +35,7 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { private final MutableLiveData<Integer> mSizeTypeData = new MutableLiveData<>(); private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData = new MutableLiveData<>(); + private final MutableLiveData<Boolean> mMoveToTuckedData = new MutableLiveData<>(); private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>(); private final MenuInfoRepository mInfoRepository; @@ -57,10 +58,19 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { mFadeEffectInfoData.setValue(fadeEffectInfo); } + void updateMenuMoveToTucked(boolean isMoveToTucked) { + mInfoRepository.updateMoveToTucked(isMoveToTucked); + } + void updateMenuSavingPosition(Position percentagePosition) { mInfoRepository.updateMenuSavingPosition(percentagePosition); } + LiveData<Boolean> getMoveToTuckedData() { + mInfoRepository.loadMenuMoveToTucked(mMoveToTuckedData::setValue); + return mMoveToTuckedData; + } + LiveData<Position> getPercentagePositionData() { mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue); return mPercentagePositionData; diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index c853671519c0..fb37defd844d 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -266,6 +266,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv mExitAnimator.cancel(); } reset(); + mClipboardLogger.setClipSource(clipSource); String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null @@ -525,21 +526,27 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv static class ClipboardLogger { private final UiEventLogger mUiEventLogger; + private String mClipSource; private boolean mGuarded = false; ClipboardLogger(UiEventLogger uiEventLogger) { mUiEventLogger = uiEventLogger; } + void setClipSource(String clipSource) { + mClipSource = clipSource; + } + void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) { if (!mGuarded) { mGuarded = true; - mUiEventLogger.log(event); + mUiEventLogger.log(event, 0, mClipSource); } } void reset() { mGuarded = false; + mClipSource = null; } } } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java index cece764580d1..c194e66f8e92 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java @@ -20,7 +20,10 @@ import android.content.ClipData; import android.content.ClipDescription; import android.content.ComponentName; import android.content.Context; +import android.os.Build; +import android.provider.DeviceConfig; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.R; import javax.inject.Inject; @@ -35,6 +38,12 @@ class ClipboardOverlayUtils { if (clipData != null && clipData.getDescription().getExtras() != null && clipData.getDescription().getExtras().getBoolean( ClipDescription.EXTRA_IS_REMOTE_DEVICE)) { + if (Build.isDebuggable() && DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.CLIPBOARD_IGNORE_REMOTE_COPY_SOURCE, + false)) { + return true; + } ComponentName remoteComponent = ComponentName.unflattenFromString( context.getResources().getString(R.string.config_remoteCopyPackage)); if (remoteComponent != null) { diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index 81df4ed51d8d..267e0366b00e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -87,7 +87,7 @@ public class FeatureFlagsDebug implements FeatureFlags { new ServerFlagReader.ChangeListener() { @Override public void onChange() { - mRestarter.restart(); + mRestarter.restartSystemUI(); } }; @@ -327,9 +327,7 @@ public class FeatureFlagsDebug implements FeatureFlags { Log.i(TAG, "SystemUI Restart Suppressed"); return; } - Log.i(TAG, "Restarting SystemUI"); - // SysUI starts back when up exited. Is there a better way to do this? - System.exit(0); + mRestarter.restartSystemUI(); } private void restartAndroid(boolean requestSuppress) { @@ -337,7 +335,7 @@ public class FeatureFlagsDebug implements FeatureFlags { Log.i(TAG, "Android Restart Suppressed"); return; } - mRestarter.restart(); + mRestarter.restartAndroid(); } void setBooleanFlagInternal(Flag<?> flag, boolean value) { diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt index 3d9f62768a37..069e6127aa30 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt @@ -28,6 +28,8 @@ constructor( private val systemExitRestarter: SystemExitRestarter, ) : Restarter { + private var androidRestartRequested = false + val observer = object : WakefulnessLifecycle.Observer { override fun onFinishedGoingToSleep() { @@ -36,8 +38,18 @@ constructor( } } - override fun restart() { - Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.") + override fun restartSystemUI() { + Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.") + scheduleRestart() + } + + override fun restartAndroid() { + Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.") + androidRestartRequested = true + scheduleRestart() + } + + fun scheduleRestart() { if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) { restartNow() } else { @@ -46,6 +58,10 @@ constructor( } private fun restartNow() { - systemExitRestarter.restart() + if (androidRestartRequested) { + systemExitRestarter.restartAndroid() + } else { + systemExitRestarter.restartSystemUI() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java index 3c83682210b6..8bddacc4e32c 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java @@ -61,7 +61,7 @@ public class FeatureFlagsRelease implements FeatureFlags { new ServerFlagReader.ChangeListener() { @Override public void onChange() { - mRestarter.restart(); + mRestarter.restartSystemUI(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt index a3f0f665c056..7ff3876f5d4e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt @@ -34,35 +34,48 @@ constructor( @Background private val bgExecutor: DelayableExecutor, private val systemExitRestarter: SystemExitRestarter ) : Restarter { - var shouldRestart = false + var listenersAdded = false var pendingRestart: Runnable? = null + var androidRestartRequested = false val observer = object : WakefulnessLifecycle.Observer { override fun onFinishedGoingToSleep() { - maybeScheduleRestart() + scheduleRestart() } } val batteryCallback = object : BatteryController.BatteryStateChangeCallback { override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { - maybeScheduleRestart() + scheduleRestart() } } - override fun restart() { - Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.") - if (!shouldRestart) { - // Don't bother scheduling twice. - shouldRestart = true + override fun restartSystemUI() { + Log.d( + FeatureFlagsDebug.TAG, + "SystemUI Restart requested. Restarting when plugged in and idle." + ) + scheduleRestart() + } + + override fun restartAndroid() { + Log.d( + FeatureFlagsDebug.TAG, + "Android Restart requested. Restarting when plugged in and idle." + ) + androidRestartRequested = true + scheduleRestart() + } + + private fun scheduleRestart() { + // Don't bother adding listeners twice. + if (!listenersAdded) { + listenersAdded = true wakefulnessLifecycle.addObserver(observer) batteryController.addCallback(batteryCallback) - maybeScheduleRestart() } - } - - private fun maybeScheduleRestart() { if ( wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn ) { @@ -77,6 +90,10 @@ constructor( private fun restartNow() { Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change") - systemExitRestarter.restart() + if (androidRestartRequested) { + systemExitRestarter.restartAndroid() + } else { + systemExitRestarter.restartSystemUI() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index e27660f7edb0..cd28cd35243e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -61,6 +61,9 @@ object Flags { // TODO(b/254512517): Tracking Bug val FSI_REQUIRES_KEYGUARD = unreleasedFlag(110, "fsi_requires_keyguard", teamfood = true) + // TODO(b/259130119): Tracking Bug + val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true) + // TODO(b/254512538): Tracking Bug val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true) @@ -386,7 +389,9 @@ object Flags { // 1700 - clipboard @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor") - @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = unreleasedFlag(1701, "clipboard_remote_behavior") + @JvmField + val CLIPBOARD_REMOTE_BEHAVIOR = + unreleasedFlag(1701, "clipboard_remote_behavior", teamfood = true) // 1800 - shade container @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt index 8f095a24de94..ce8b821b1182 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt @@ -16,5 +16,7 @@ package com.android.systemui.flags interface Restarter { - fun restart() -}
\ No newline at end of file + fun restartSystemUI() + + fun restartAndroid() +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt index f1b1be47a84f..89daa6487fbc 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt @@ -16,10 +16,19 @@ package com.android.systemui.flags +import com.android.internal.statusbar.IStatusBarService import javax.inject.Inject -class SystemExitRestarter @Inject constructor() : Restarter { - override fun restart() { +class SystemExitRestarter +@Inject +constructor( + private val barService: IStatusBarService, +) : Restarter { + override fun restartAndroid() { + barService.restart() + } + + override fun restartSystemUI() { System.exit(0) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 3d976d43759c..dd222c0bf8f8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -137,6 +137,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; @@ -852,6 +853,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { mOccludeAnimationPlaying = true; + mScrimControllerLazy.get().setOccludeAnimationPlaying(true); } @Override @@ -862,6 +864,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // Ensure keyguard state is set correctly if we're cancelled. mCentralSurfaces.updateIsKeyguard(); + mScrimControllerLazy.get().setOccludeAnimationPlaying(false); } @Override @@ -875,6 +878,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // Hide the keyguard now that we're done launching the occluding activity over // it. mCentralSurfaces.updateIsKeyguard(); + mScrimControllerLazy.get().setOccludeAnimationPlaying(false); mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); } @@ -1132,6 +1136,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private ScreenOnCoordinator mScreenOnCoordinator; private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator; + private Lazy<ScrimController> mScrimControllerLazy; /** * Injected constructor. See {@link KeyguardModule}. @@ -1162,7 +1167,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, DreamOverlayStateController dreamOverlayStateController, Lazy<ShadeController> shadeControllerLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, - Lazy<ActivityLaunchAnimator> activityLaunchAnimator) { + Lazy<ActivityLaunchAnimator> activityLaunchAnimator, + Lazy<ScrimController> scrimControllerLazy) { mContext = context; mUserTracker = userTracker; mFalsingCollector = falsingCollector; @@ -1207,6 +1213,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mDreamOverlayStateController = dreamOverlayStateController; mActivityLaunchAnimator = activityLaunchAnimator; + mScrimControllerLazy = scrimControllerLazy; mPowerButtonY = context.getResources().getDimensionPixelSize( R.dimen.physical_power_button_center_screen_location_y); 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 ef3c44340e57..47ef0fac17ab 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -54,6 +54,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.DeviceConfigProxy; @@ -113,7 +114,8 @@ public class KeyguardModule { DreamOverlayStateController dreamOverlayStateController, Lazy<ShadeController> shadeController, Lazy<NotificationShadeWindowController> notificationShadeWindowController, - Lazy<ActivityLaunchAnimator> activityLaunchAnimator) { + Lazy<ActivityLaunchAnimator> activityLaunchAnimator, + Lazy<ScrimController> scrimControllerLazy) { return new KeyguardViewMediator( context, userTracker, @@ -142,7 +144,8 @@ public class KeyguardModule { dreamOverlayStateController, shadeController, notificationShadeWindowController, - activityLaunchAnimator); + activityLaunchAnimator, + scrimControllerLazy); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 691953aaba36..cc5e256c0956 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -56,7 +56,7 @@ import javax.inject.Inject * TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator. */ @SysUISingleton -class MediaTttChipControllerReceiver @Inject constructor( +open class MediaTttChipControllerReceiver @Inject constructor( private val commandQueue: CommandQueue, context: Context, @MediaTttReceiverLogger logger: MediaTttLogger, @@ -183,15 +183,28 @@ class MediaTttChipControllerReceiver @Inject constructor( val appIconView = view.getAppIconView() appIconView.animate() .translationYBy(-1 * getTranslationAmount().toFloat()) - .setDuration(30.frames) + .setDuration(ICON_TRANSLATION_ANIM_DURATION) .start() appIconView.animate() .alpha(1f) - .setDuration(5.frames) + .setDuration(ICON_ALPHA_ANIM_DURATION) .start() // Using withEndAction{} doesn't apply a11y focus when screen is unlocked. appIconView.postOnAnimation { view.requestAccessibilityFocus() } - startRipple(view.requireViewById(R.id.ripple)) + expandRipple(view.requireViewById(R.id.ripple)) + } + + override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { + val appIconView = view.getAppIconView() + appIconView.animate() + .translationYBy(getTranslationAmount().toFloat()) + .setDuration(ICON_TRANSLATION_ANIM_DURATION) + .start() + appIconView.animate() + .alpha(0f) + .setDuration(ICON_ALPHA_ANIM_DURATION) + .start() + (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd) } override fun getTouchableRegion(view: View, outRect: Rect) { @@ -205,11 +218,22 @@ class MediaTttChipControllerReceiver @Inject constructor( return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation) } - private fun startRipple(rippleView: ReceiverChipRippleView) { + private fun expandRipple(rippleView: ReceiverChipRippleView) { if (rippleView.rippleInProgress()) { // Skip if ripple is still playing return } + + // In case the device orientation changes, we need to reset the layout. + rippleView.addOnLayoutChangeListener ( + View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> + if (v == null) return@OnLayoutChangeListener + + val layoutChangedRippleView = v as ReceiverChipRippleView + layoutRipple(layoutChangedRippleView) + layoutChangedRippleView.invalidate() + } + ) rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { override fun onViewDetachedFromWindow(view: View?) {} @@ -219,7 +243,7 @@ class MediaTttChipControllerReceiver @Inject constructor( } val attachedRippleView = view as ReceiverChipRippleView layoutRipple(attachedRippleView) - attachedRippleView.startRipple() + attachedRippleView.expandRipple() attachedRippleView.removeOnAttachStateChangeListener(this) } }) @@ -242,6 +266,9 @@ class MediaTttChipControllerReceiver @Inject constructor( } } +val ICON_TRANSLATION_ANIM_DURATION = 30.frames +val ICON_ALPHA_ANIM_DURATION = 5.frames + data class ChipReceiverInfo( val routeInfo: MediaRoute2Info, val appIconDrawableOverride: Drawable?, diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt index 1ea202582f83..6e9fc5c33940 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt @@ -16,6 +16,8 @@ package com.android.systemui.media.taptotransfer.receiver +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.content.Context import android.util.AttributeSet import com.android.systemui.surfaceeffects.ripple.RippleShader @@ -25,10 +27,36 @@ import com.android.systemui.surfaceeffects.ripple.RippleView * An expanding ripple effect for the media tap-to-transfer receiver chip. */ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) { + + // Indicates whether the ripple started expanding. + private var isStarted: Boolean + init { setupShader(RippleShader.RippleShape.ELLIPSE) setRippleFill(true) setSparkleStrength(0f) duration = 3000L + isStarted = false + } + + fun expandRipple(onAnimationEnd: Runnable? = null) { + isStarted = true + super.startRipple(onAnimationEnd) + } + + /** Used to animate out the ripple. No-op if the ripple was never started via [startRipple]. */ + fun collapseRipple(onAnimationEnd: Runnable? = null) { + if (!isStarted) { + return // Ignore if ripple is not started yet. + } + // Reset all listeners to animator. + animator.removeAllListeners() + animator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + onAnimationEnd?.run() + isStarted = false + } + }) + animator.reverse() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt index 9ba3501c3434..03bb7a0f45da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt @@ -32,8 +32,6 @@ import com.android.systemui.animation.Expandable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.FgsManagerController @@ -42,10 +40,9 @@ import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig -import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.security.data.repository.SecurityRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController -import com.android.systemui.user.UserSwitcherActivity +import com.android.systemui.user.domain.interactor.UserInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -100,13 +97,12 @@ class FooterActionsInteractorImpl @Inject constructor( private val activityStarter: ActivityStarter, - private val featureFlags: FeatureFlags, private val metricsLogger: MetricsLogger, private val uiEventLogger: UiEventLogger, private val deviceProvisionedController: DeviceProvisionedController, private val qsSecurityFooterUtils: QSSecurityFooterUtils, private val fgsManagerController: FgsManagerController, - private val userSwitchDialogController: UserSwitchDialogController, + private val userInteractor: UserInteractor, securityRepository: SecurityRepository, foregroundServicesRepository: ForegroundServicesRepository, userSwitcherRepository: UserSwitcherRepository, @@ -182,22 +178,6 @@ constructor( } override fun showUserSwitcher(context: Context, expandable: Expandable) { - if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) { - userSwitchDialogController.showDialog(context, expandable) - return - } - - val intent = - Intent(context, UserSwitcherActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) - } - - activityStarter.startActivity( - intent, - true /* dismissShade */, - expandable.activityLaunchController(), - true /* showOverlockscreenwhenlocked */, - UserHandle.SYSTEM, - ) + userInteractor.showUserSwitcher(context, expandable) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index b92cf5a0a49d..e0c4884e4c7a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -5499,8 +5499,11 @@ public final class NotificationPanelViewController implements Dumpable { if (!animatingUnlockedShadeToKeyguard) { // Only make the status bar visible if we're not animating the screen off, since // we only want to be showing the clock/notifications during the animation. - mShadeLog.v("Updating keyguard status bar state to " - + (keyguardShowing ? "visible" : "invisible")); + if (keyguardShowing) { + mShadeLog.v("Updating keyguard status bar state to visible"); + } else { + mShadeLog.v("Updating keyguard status bar state to invisible"); + } mKeyguardStatusBarViewController.updateViewState( /* alpha= */ 1f, keyguardShowing ? View.VISIBLE : View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 41dbf1d6dc60..962eeb962b81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -3733,7 +3733,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - private void debugLog(@CompileTimeConstant String s) { + private void debugLog(@CompileTimeConstant final String s) { if (mLogger == null) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 62f57b8d4068..d8c68780951a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -62,7 +62,8 @@ public class StackScrollAlgorithm { private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); private boolean mIsExpanded; private boolean mClipNotificationScrollToTop; - @VisibleForTesting float mHeadsUpInset; + @VisibleForTesting + float mHeadsUpInset; private int mPinnedZTranslationExtra; private float mNotificationScrimPadding; private int mMarginBottom; @@ -456,7 +457,7 @@ public class StackScrollAlgorithm { /** * @return Fraction to apply to view height and gap between views. - * Does not include shelf height even if shelf is showing. + * Does not include shelf height even if shelf is showing. */ protected float getExpansionFractionWithoutShelf( StackScrollAlgorithmState algorithmState, @@ -470,7 +471,7 @@ public class StackScrollAlgorithm { && (!ambientState.isBypassEnabled() || !ambientState.isPulseExpanding()) ? 0 : mNotificationScrimPadding; - final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding; + final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding; final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding; if (stackEndHeight == 0f) { // This should not happen, since even when the shade is empty we show EmptyShadeView @@ -504,13 +505,14 @@ public class StackScrollAlgorithm { } // TODO(b/172289889) polish shade open from HUN + /** * Populates the {@link ExpandableViewState} for a single child. * - * @param i The index of the child in - * {@link StackScrollAlgorithmState#visibleChildren}. - * @param algorithmState The overall output state of the algorithm. - * @param ambientState The input state provided to the algorithm. + * @param i The index of the child in + * {@link StackScrollAlgorithmState#visibleChildren}. + * @param algorithmState The overall output state of the algorithm. + * @param ambientState The input state provided to the algorithm. */ protected void updateChild( int i, @@ -584,8 +586,8 @@ public class StackScrollAlgorithm { final float stackBottom = !ambientState.isShadeExpanded() || ambientState.getDozeAmount() == 1f || bypassPulseNotExpanding - ? ambientState.getInnerHeight() - : ambientState.getStackHeight(); + ? ambientState.getInnerHeight() + : ambientState.getStackHeight(); final float shelfStart = stackBottom - ambientState.getShelf().getIntrinsicHeight() - mPaddingBetweenElements; @@ -621,9 +623,9 @@ public class StackScrollAlgorithm { * Get the gap height needed for before a view * * @param sectionProvider the sectionProvider used to understand the sections - * @param visibleIndex the visible index of this view in the list - * @param child the child asked about - * @param previousChild the child right before it or null if none + * @param visibleIndex the visible index of this view in the list + * @param child the child asked about + * @param previousChild the child right before it or null if none * @return the size of the gap needed or 0 if none is needed */ public float getGapHeightForChild( @@ -657,9 +659,9 @@ public class StackScrollAlgorithm { * Does a given child need a gap, i.e spacing before a view? * * @param sectionProvider the sectionProvider used to understand the sections - * @param visibleIndex the visible index of this view in the list - * @param child the child asked about - * @param previousChild the child right before it or null if none + * @param visibleIndex the visible index of this view in the list + * @param child the child asked about + * @param previousChild the child right before it or null if none * @return if the child needs a gap height */ private boolean childNeedsGapHeight( @@ -862,30 +864,53 @@ public class StackScrollAlgorithm { } } + /** + * Calculate and update the Z positions for a given child. We currently only give shadows to + * HUNs to distinguish a HUN from its surroundings. + * + * @param isTopHun Whether the child is a top HUN. A top HUN means a HUN that shows on the + * vertically top of screen. Top HUNs should have drop shadows + * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated + * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height + * that overlaps with QQS Panel. The integer part represents the count of + * previous HUNs whose Z positions are greater than 0. + */ protected float updateChildZValue(int i, float childrenOnTop, StackScrollAlgorithmState algorithmState, AmbientState ambientState, - boolean shouldElevateHun) { + boolean isTopHun) { ExpandableView child = algorithmState.visibleChildren.get(i); ExpandableViewState childViewState = child.getViewState(); - int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements(); float baseZ = ambientState.getBaseZHeight(); + + // Handles HUN shadow when Shade is opened + if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible && !ambientState.isDozingAndNotPulsing(child) && childViewState.getYTranslation() < ambientState.getTopPadding() + ambientState.getStackTranslation()) { + // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0 + // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel. + // When scrolling down shade to make HUN back to in-position in Notification Panel, + // The over-lapping fraction goes to 0, and shadows hides gradually. if (childrenOnTop != 0.0f) { + // To elevate the later HUN over previous HUN childrenOnTop++; } else { float overlap = ambientState.getTopPadding() + ambientState.getStackTranslation() - childViewState.getYTranslation(); - childrenOnTop += Math.min(1.0f, overlap / childViewState.height); + // To prevent over-shadow during HUN entry + childrenOnTop += Math.min( + 1.0f, + overlap / childViewState.height + ); + MathUtils.saturate(childrenOnTop); } childViewState.setZTranslation(baseZ - + childrenOnTop * zDistanceBetweenElements); - } else if (shouldElevateHun) { + + childrenOnTop * mPinnedZTranslationExtra); + } else if (isTopHun) { // In case this is a new view that has never been measured before, we don't want to - // elevate if we are currently expanded more then the notification + // elevate if we are currently expanded more than the notification int shelfHeight = ambientState.getShelf() == null ? 0 : ambientState.getShelf().getIntrinsicHeight(); float shelfStart = ambientState.getInnerHeight() @@ -894,23 +919,28 @@ public class StackScrollAlgorithm { float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight() + mPaddingBetweenElements; if (shelfStart > notificationEnd) { + // When the notification doesn't overlap with Notification Shelf, there's no shadow childViewState.setZTranslation(baseZ); } else { + // Give shadow to the notification if it overlaps with Notification Shelf float factor = (notificationEnd - shelfStart) / shelfHeight; if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0. factor = 1.0f; } factor = Math.min(factor, 1.0f); - childViewState.setZTranslation(baseZ + factor * zDistanceBetweenElements); + childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra); } } else { childViewState.setZTranslation(baseZ); } - // We need to scrim the notification more from its surrounding content when we are pinned, - // and we therefore elevate it higher. - // We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when - // expanding after which we have a normal elevation again. + // Handles HUN shadow when shade is closed. + // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays. + // During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes + // gradually from 0 to 1, shadow hides gradually. + // Header visibility is a deprecated concept, we are using headerVisibleAmount only because + // this value nicely goes from 0 to 1 during the HUN-to-Shade process. + childViewState.setZTranslation(childViewState.getZTranslation() + (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra); return childrenOnTop; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 7a49a495155b..13566ef8c630 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -48,6 +48,9 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; +import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; +import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; import java.io.PrintWriter; import java.util.ArrayList; @@ -70,7 +73,7 @@ public class KeyguardStatusBarView extends RelativeLayout { private ImageView mMultiUserAvatar; private BatteryMeterView mBatteryView; private StatusIconContainer mStatusIconContainer; - private ViewGroup mUserSwitcherContainer; + private StatusBarUserSwitcherContainer mUserSwitcherContainer; private boolean mKeyguardUserSwitcherEnabled; private boolean mKeyguardUserAvatarEnabled; @@ -121,8 +124,12 @@ public class KeyguardStatusBarView extends RelativeLayout { loadDimens(); } - public ViewGroup getUserSwitcherContainer() { - return mUserSwitcherContainer; + /** + * Should only be called from {@link KeyguardStatusBarViewController} + * @param viewModel view model for the status bar user chip + */ + void init(StatusBarUserChipViewModel viewModel) { + StatusBarUserChipViewBinder.bind(mUserSwitcherContainer, viewModel); } @Override @@ -304,10 +311,7 @@ public class KeyguardStatusBarView extends RelativeLayout { lp = (LayoutParams) mStatusIconArea.getLayoutParams(); lp.removeRule(RelativeLayout.RIGHT_OF); lp.width = LayoutParams.WRAP_CONTENT; - - LinearLayout.LayoutParams llp = - (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams(); - llp.setMarginStart(getResources().getDimensionPixelSize( + lp.setMarginStart(getResources().getDimensionPixelSize( R.dimen.system_icons_super_container_margin_start)); return true; } @@ -339,10 +343,7 @@ public class KeyguardStatusBarView extends RelativeLayout { lp = (LayoutParams) mStatusIconArea.getLayoutParams(); lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view); lp.width = LayoutParams.MATCH_PARENT; - - LinearLayout.LayoutParams llp = - (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams(); - llp.setMarginStart(0); + lp.setMarginStart(0); return true; } 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 14cebf4b9f4b..d4dc1dc197a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -59,13 +59,11 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt; import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoController; +import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.SecureSettings; @@ -110,9 +108,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final SysuiStatusBarStateController mStatusBarStateController; private final StatusBarContentInsetsProvider mInsetsProvider; private final UserManager mUserManager; - private final StatusBarUserSwitcherFeatureController mFeatureController; - private final StatusBarUserSwitcherController mUserSwitcherController; - private final StatusBarUserInfoTracker mStatusBarUserInfoTracker; + private final StatusBarUserChipViewModel mStatusBarUserChipViewModel; private final SecureSettings mSecureSettings; private final CommandQueue mCommandQueue; private final Executor mMainExecutor; @@ -276,9 +272,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat SysuiStatusBarStateController statusBarStateController, StatusBarContentInsetsProvider statusBarContentInsetsProvider, UserManager userManager, - StatusBarUserSwitcherFeatureController featureController, - StatusBarUserSwitcherController userSwitcherController, - StatusBarUserInfoTracker statusBarUserInfoTracker, + StatusBarUserChipViewModel userChipViewModel, SecureSettings secureSettings, CommandQueue commandQueue, @Main Executor mainExecutor, @@ -301,9 +295,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mStatusBarStateController = statusBarStateController; mInsetsProvider = statusBarContentInsetsProvider; mUserManager = userManager; - mFeatureController = featureController; - mUserSwitcherController = userSwitcherController; - mStatusBarUserInfoTracker = statusBarUserInfoTracker; + mStatusBarUserChipViewModel = userChipViewModel; mSecureSettings = secureSettings; mCommandQueue = commandQueue; mMainExecutor = mainExecutor; @@ -328,8 +320,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat R.dimen.header_notifications_collide_distance); mView.setKeyguardUserAvatarEnabled( - !mFeatureController.isStatusBarUserSwitcherFeatureEnabled()); - mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled)); + !mStatusBarUserChipViewModel.getChipEnabled()); mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r); mDisableStateTracker = new DisableStateTracker( @@ -344,11 +335,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat super.onInit(); mCarrierTextController.init(); mBatteryMeterViewController.init(); - mUserSwitcherController.init(); } @Override protected void onViewAttached() { + mView.init(mStatusBarUserChipViewModel); mConfigurationController.addCallback(mConfigurationListener); mAnimationScheduler.addCallback(mAnimationCallback); mUserInfoController.addCallback(mOnUserInfoChangedListener); @@ -394,9 +385,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /** Sets whether user switcher is enabled. */ public void setKeyguardUserSwitcherEnabled(boolean enabled) { mView.setKeyguardUserSwitcherEnabled(enabled); - // We don't have a listener for when the user switcher setting changes, so this is - // where we re-check the state - mStatusBarUserInfoTracker.checkEnabled(); } /** Sets whether this controller should listen to battery updates. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 7aeb08dd5ddb..28bc64de2cb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -38,6 +38,9 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; +import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder; +import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; import com.android.systemui.util.leak.RotationUtils; import java.util.Objects; @@ -73,6 +76,11 @@ public class PhoneStatusBarView extends FrameLayout { mTouchEventHandler = handler; } + void init(StatusBarUserChipViewModel viewModel) { + StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container); + StatusBarUserChipViewBinder.bind(container, viewModel); + } + @Override public void onFinishInflate() { super.onFinishInflate(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index f9c4c8f3b4fe..a6c2b2c2771c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -23,11 +23,11 @@ import android.view.ViewGroup import android.view.ViewTreeObserver import com.android.systemui.R import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.UNFOLD_STATUS_BAR import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider +import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel import com.android.systemui.util.ViewController import com.android.systemui.util.kotlin.getOrNull import com.android.systemui.util.view.ViewUtil @@ -40,7 +40,7 @@ class PhoneStatusBarViewController private constructor( view: PhoneStatusBarView, @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?, private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, - private val userSwitcherController: StatusBarUserSwitcherController, + private val userChipViewModel: StatusBarUserChipViewModel, private val viewUtil: ViewUtil, touchEventHandler: PhoneStatusBarView.TouchEventHandler, private val configurationController: ConfigurationController @@ -91,10 +91,10 @@ class PhoneStatusBarViewController private constructor( init { mView.setTouchEventHandler(touchEventHandler) + mView.init(userChipViewModel) } override fun onInit() { - userSwitcherController.init() } fun setImportantForAccessibility(mode: Int) { @@ -156,9 +156,9 @@ class PhoneStatusBarViewController private constructor( private val unfoldComponent: Optional<SysUIUnfoldComponent>, @Named(UNFOLD_STATUS_BAR) private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>, - private val userSwitcherController: StatusBarUserSwitcherController, + private val userChipViewModel: StatusBarUserChipViewModel, private val viewUtil: ViewUtil, - private val configurationController: ConfigurationController + private val configurationController: ConfigurationController, ) { fun create( view: PhoneStatusBarView, @@ -168,7 +168,7 @@ class PhoneStatusBarViewController private constructor( view, progressProvider.getOrNull(), unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(), - userSwitcherController, + userChipViewModel, viewUtil, touchEventHandler, configurationController diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index c527f30c424c..fb0d3e4406bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -110,6 +110,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private boolean mClipsQsScrim; /** + * Whether an activity is launching over the lockscreen. During the launch animation, we want to + * delay certain scrim changes until after the animation ends. + */ + private boolean mOccludeAnimationPlaying = false; + + /** * The amount of progress we are currently in if we're transitioning to the full shade. * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full * shade. @@ -733,6 +739,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump return mClipsQsScrim; } + public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) { + mOccludeAnimationPlaying = occludeAnimationPlaying; + applyAndDispatchState(); + } + private void setOrAdaptCurrentAnimation(@Nullable View scrim) { if (scrim == null) { return; @@ -772,11 +783,15 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) { - // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding - // because we're doing the screen off animation OR the shade is collapsing because - // we're playing the unlock animation + final boolean occluding = + mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview; + + // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the + // screen off/occlusion animations, ignore expansion changes while those animations + // play. if (!mScreenOffAnimationController.shouldExpandNotifications() - && !mAnimatingPanelExpansionOnUnlock) { + && !mAnimatingPanelExpansionOnUnlock + && !occluding) { float behindFraction = getInterpolatedFraction(); behindFraction = (float) Math.pow(behindFraction, 0.8f); if (mClipsQsScrim) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index 41f1f9589ce4..efec27099dcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -29,8 +29,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; @@ -39,7 +37,6 @@ import java.util.Set; import javax.inject.Named; -import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.Multibinds; @@ -126,12 +123,6 @@ public interface StatusBarFragmentModule { } /** */ - @Binds - @StatusBarFragmentScope - StatusBarUserSwitcherController bindStatusBarUserSwitcherController( - StatusBarUserSwitcherControllerImpl controller); - - /** */ @Provides @StatusBarFragmentScope static PhoneStatusBarViewController providePhoneStatusBarViewController( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt deleted file mode 100644 index f6b8cb0782cd..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone.userswitcher - -import android.graphics.drawable.Drawable -import android.os.UserManager -import com.android.systemui.Dumpable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.dump.DumpManager -import com.android.systemui.statusbar.policy.CallbackController -import com.android.systemui.statusbar.policy.UserInfoController -import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener -import java.io.PrintWriter -import java.util.concurrent.Executor -import javax.inject.Inject - -/** - * Since every user switcher chip will user the exact same information and logic on whether or not - * to show, and what data to show, it makes sense to create a single tracker here - */ -@SysUISingleton -class StatusBarUserInfoTracker @Inject constructor( - private val userInfoController: UserInfoController, - private val userManager: UserManager, - private val dumpManager: DumpManager, - @Main private val mainExecutor: Executor, - @Background private val backgroundExecutor: Executor -) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable { - var currentUserName: String? = null - private set - var currentUserAvatar: Drawable? = null - private set - var userSwitcherEnabled = false - private set - private var listening = false - - private val listeners = mutableListOf<CurrentUserChipInfoUpdatedListener>() - - private val userInfoChangedListener = OnUserInfoChangedListener { name, picture, _ -> - currentUserAvatar = picture - currentUserName = name - notifyListenersUserInfoChanged() - } - - init { - dumpManager.registerDumpable(TAG, this) - } - - override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) { - if (listeners.isEmpty()) { - startListening() - } - - if (!listeners.contains(listener)) { - listeners.add(listener) - } - } - - override fun removeCallback(listener: CurrentUserChipInfoUpdatedListener) { - listeners.remove(listener) - - if (listeners.isEmpty()) { - stopListening() - } - } - - private fun notifyListenersUserInfoChanged() { - listeners.forEach { - it.onCurrentUserChipInfoUpdated() - } - } - - private fun notifyListenersSettingChanged() { - listeners.forEach { - it.onStatusBarUserSwitcherSettingChanged(userSwitcherEnabled) - } - } - - private fun startListening() { - listening = true - userInfoController.addCallback(userInfoChangedListener) - } - - private fun stopListening() { - listening = false - userInfoController.removeCallback(userInfoChangedListener) - } - - /** - * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has - * changed - */ - fun checkEnabled() { - backgroundExecutor.execute { - // Check on a background thread to avoid main thread Binder calls - val wasEnabled = userSwitcherEnabled - userSwitcherEnabled = userManager.isUserSwitcherEnabled - - if (wasEnabled != userSwitcherEnabled) { - mainExecutor.execute { - notifyListenersSettingChanged() - } - } - } - } - - override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println(" userSwitcherEnabled=$userSwitcherEnabled") - pw.println(" listening=$listening") - } -} - -interface CurrentUserChipInfoUpdatedListener { - fun onCurrentUserChipInfoUpdated() - fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {} -} - -private const val TAG = "StatusBarUserInfoTracker" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt deleted file mode 100644 index e498ae451400..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone.userswitcher - -import android.content.Intent -import android.os.UserHandle -import android.view.View -import com.android.systemui.animation.Expandable -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.plugins.FalsingManager - -import com.android.systemui.qs.user.UserSwitchDialogController -import com.android.systemui.user.UserSwitcherActivity -import com.android.systemui.util.ViewController - -import javax.inject.Inject - -/** - * ViewController for [StatusBarUserSwitcherContainer] - */ -class StatusBarUserSwitcherControllerImpl @Inject constructor( - view: StatusBarUserSwitcherContainer, - private val tracker: StatusBarUserInfoTracker, - private val featureController: StatusBarUserSwitcherFeatureController, - private val userSwitcherDialogController: UserSwitchDialogController, - private val featureFlags: FeatureFlags, - private val activityStarter: ActivityStarter, - private val falsingManager: FalsingManager -) : ViewController<StatusBarUserSwitcherContainer>(view), - StatusBarUserSwitcherController { - private val listener = object : CurrentUserChipInfoUpdatedListener { - override fun onCurrentUserChipInfoUpdated() { - updateChip() - } - - override fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) { - updateEnabled() - } - } - - private val featureFlagListener = object : OnUserSwitcherPreferenceChangeListener { - override fun onUserSwitcherPreferenceChange(enabled: Boolean) { - updateEnabled() - } - } - - public override fun onViewAttached() { - tracker.addCallback(listener) - featureController.addCallback(featureFlagListener) - mView.setOnClickListener { view: View -> - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return@setOnClickListener - } - - if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) { - val intent = Intent(context, UserSwitcherActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) - - activityStarter.startActivity(intent, true /* dismissShade */, - null /* ActivityLaunchAnimator.Controller */, - true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM) - } else { - userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view)) - } - } - - updateEnabled() - } - - override fun onViewDetached() { - tracker.removeCallback(listener) - featureController.removeCallback(featureFlagListener) - mView.setOnClickListener(null) - } - - private fun updateChip() { - mView.text.text = tracker.currentUserName - mView.avatar.setImageDrawable(tracker.currentUserAvatar) - } - - private fun updateEnabled() { - if (featureController.isStatusBarUserSwitcherFeatureEnabled() && - tracker.userSwitcherEnabled) { - mView.visibility = View.VISIBLE - updateChip() - } else { - mView.visibility = View.GONE - } - } -} - -interface StatusBarUserSwitcherController { - fun init() -} - -private const val TAG = "SbUserSwitcherController" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt deleted file mode 100644 index 7bae9ff72760..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone.userswitcher - -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.statusbar.policy.CallbackController - -import javax.inject.Inject - -@SysUISingleton -class StatusBarUserSwitcherFeatureController @Inject constructor( - private val flags: FeatureFlags -) : CallbackController<OnUserSwitcherPreferenceChangeListener> { - private val listeners = mutableListOf<OnUserSwitcherPreferenceChangeListener>() - - init { - flags.addListener(Flags.STATUS_BAR_USER_SWITCHER) { - it.requestNoRestart() - notifyListeners() - } - } - - fun isStatusBarUserSwitcherFeatureEnabled(): Boolean { - return flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER) - } - - override fun addCallback(listener: OnUserSwitcherPreferenceChangeListener) { - if (!listeners.contains(listener)) { - listeners.add(listener) - } - } - - override fun removeCallback(listener: OnUserSwitcherPreferenceChangeListener) { - listeners.remove(listener) - } - - private fun notifyListeners() { - val enabled = flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER) - listeners.forEach { - it.onUserSwitcherPreferenceChange(enabled) - } - } -} - -interface OnUserSwitcherPreferenceChangeListener { - fun onUserSwitcherPreferenceChange(enabled: Boolean) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 38b3769c1071..acdf0d2bc32b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -146,7 +146,13 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } private String getDeviceString(CachedBluetoothDevice device) { - return device.getName() + " " + device.getBondState() + " " + device.isConnected(); + return device.getName() + + " bondState=" + device.getBondState() + + " connected=" + device.isConnected() + + " active[A2DP]=" + device.isActiveDevice(BluetoothProfile.A2DP) + + " active[HEADSET]=" + device.isActiveDevice(BluetoothProfile.HEADSET) + + " active[HEARING_AID]=" + device.isActiveDevice(BluetoothProfile.HEARING_AID) + + " active[LE_AUDIO]=" + device.isActiveDevice(BluetoothProfile.LE_AUDIO); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt index 2ad824348794..ae28a8b6dc2e 100644 --- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt @@ -41,7 +41,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a private set private val ripplePaint = Paint() - private val animator = ValueAnimator.ofFloat(0f, 1f) + protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) var duration: Long = 1750 diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index 44e5ce865241..fb17b693e17e 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -174,7 +174,7 @@ open class ChipbarCoordinator @Inject constructor( chipInnerView, ViewHierarchyAnimator.Hotspot.TOP, Interpolators.EMPHASIZED_DECELERATE, - duration = ANIMATION_DURATION, + duration = ANIMATION_IN_DURATION, includeMargins = true, includeFadeIn = true, // We can only request focus once the animation finishes. @@ -187,7 +187,7 @@ open class ChipbarCoordinator @Inject constructor( view.requireViewById<ViewGroup>(R.id.chipbar_inner), ViewHierarchyAnimator.Hotspot.TOP, Interpolators.EMPHASIZED_ACCELERATE, - ANIMATION_DURATION, + ANIMATION_OUT_DURATION, includeMargins = true, onAnimationEnd, ) @@ -208,4 +208,5 @@ open class ChipbarCoordinator @Inject constructor( } } -private const val ANIMATION_DURATION = 500L +private const val ANIMATION_IN_DURATION = 500L +private const val ANIMATION_OUT_DURATION = 250L diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index ed53de7dbee7..4c9b8e4639ca 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -23,6 +23,7 @@ import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.annotation.VisibleForTesting +import com.android.systemui.R import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -79,6 +80,9 @@ interface UserRepository { /** Whether we've scheduled the creation of a guest user. */ val isGuestUserCreationScheduled: AtomicBoolean + /** Whether to enable the status bar user chip (which launches the user switcher) */ + val isStatusBarUserChipEnabled: Boolean + /** The user of the secondary service. */ var secondaryUserId: Int @@ -127,6 +131,9 @@ constructor( override val isGuestUserCreationScheduled = AtomicBoolean() + override val isStatusBarUserChipEnabled: Boolean = + appContext.resources.getBoolean(R.bool.flag_user_switcher_chip) + override var secondaryUserId: Int = UserHandle.USER_NULL override var isRefreshUsersPaused: Boolean = false 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 516c6501c5ff..83f0711caa38 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 @@ -34,15 +34,19 @@ import android.util.Log import com.android.internal.util.UserIcons import com.android.systemui.R import com.android.systemui.SystemUISecondaryUserService +import com.android.systemui.animation.Expandable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags 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.telephony.domain.interactor.TelephonyInteractor +import com.android.systemui.user.UserSwitcherActivity import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.user.data.source.UserRecord @@ -81,6 +85,7 @@ constructor( private val repository: UserRepository, private val activityStarter: ActivityStarter, private val keyguardInteractor: KeyguardInteractor, + private val featureFlags: FeatureFlags, private val manager: UserManager, @Application private val applicationScope: CoroutineScope, telephonyInteractor: TelephonyInteractor, @@ -256,6 +261,9 @@ constructor( /** Whether the guest user is currently being reset. */ val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting + /** Whether to enable the user chip in the status bar */ + val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled + private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null) val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow() @@ -468,6 +476,26 @@ constructor( } } + fun showUserSwitcher(context: Context, expandable: Expandable) { + if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) { + showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog) + return + } + + val intent = + Intent(context, UserSwitcherActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + } + + activityStarter.startActivity( + intent, + true /* dismissShade */, + expandable.activityLaunchController(), + true /* showOverlockscreenwhenlocked */, + UserHandle.SYSTEM, + ) + } + private fun showDialog(request: ShowDialogRequestModel) { _dialogShowRequests.value = request } 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 177356e6b573..85c29647719b 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 @@ -43,4 +43,7 @@ sealed class ShowDialogRequestModel( val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit, override val dialogShower: UserSwitchDialogController.DialogShower?, ) : ShowDialogRequestModel(dialogShower) + + /** Show the user switcher dialog */ + object ShowUserSwitcherDialog : ShowDialogRequestModel() } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt new file mode 100644 index 000000000000..8e40f68e27e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt @@ -0,0 +1,65 @@ +/* + * 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.binder + +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.animation.Expandable +import com.android.systemui.common.ui.binder.TextViewBinder +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer +import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@OptIn(InternalCoroutinesApi::class) +object StatusBarUserChipViewBinder { + /** Binds the status bar user chip view model to the given view */ + @JvmStatic + fun bind( + view: StatusBarUserSwitcherContainer, + viewModel: StatusBarUserChipViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.isChipVisible.collect { isVisible -> view.isVisible = isVisible } + } + + launch { + viewModel.userName.collect { name -> TextViewBinder.bind(view.text, name) } + } + + launch { + viewModel.userAvatar.collect { avatar -> view.avatar.setImageDrawable(avatar) } + } + + bindButton(view, viewModel) + } + } + } + + private fun bindButton( + view: StatusBarUserSwitcherContainer, + viewModel: StatusBarUserChipViewModel, + ) { + view.setOnClickListener { viewModel.onClick(Expandable.fromView(view)) } + } +} 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 new file mode 100644 index 000000000000..ed2589889435 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt @@ -0,0 +1,68 @@ +package com.android.systemui.user.ui.dialog + +import android.content.Context +import android.content.Intent +import android.provider.Settings +import android.view.LayoutInflater +import com.android.internal.logging.UiEventLogger +import com.android.systemui.R +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.plugins.ActivityStarter +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 + +/** + * Extracted from the old UserSwitchDialogController. This is the dialog version of the full-screen + * user switcher. See config_enableFullscreenUserSwitcher + */ +class UserSwitchDialog( + context: Context, + adapter: UserDetailView.Adapter, + uiEventLogger: UiEventLogger, + falsingManager: FalsingManager, + activityStarter: ActivityStarter, + dialogLaunchAnimator: DialogLaunchAnimator, +) : SystemUIDialog(context) { + init { + setShowForAllUsers(true) + setCanceledOnTouchOutside(true) + setTitle(R.string.qs_user_switch_dialog_title) + setPositiveButton(R.string.quick_settings_done) { _, _ -> + uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE) + } + setNeutralButton( + R.string.quick_settings_more_user_settings, + { _, _ -> + if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS) + val controller = + dialogLaunchAnimator.createActivityLaunchController( + getButton(BUTTON_NEUTRAL) + ) + + if (controller == null) { + dismiss() + } + + activityStarter.postStartActivityDismissingKeyguard( + USER_SETTINGS_INTENT, + 0, + controller + ) + } + }, + false /* dismissOnClick */ + ) + val gridFrame = + LayoutInflater.from(this.context).inflate(R.layout.qs_user_dialog_content, null) + setView(gridFrame) + + adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid)) + } + + companion object { + private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS) + } +} 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 58a4473186b3..41410542204c 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 @@ -20,6 +20,7 @@ package com.android.systemui.user.ui.dialog import android.app.Dialog import android.content.Context import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.logging.UiEventLogger import com.android.settingslib.users.UserCreatingDialog import com.android.systemui.CoreStartable import com.android.systemui.animation.DialogCuj @@ -27,11 +28,14 @@ import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.tiles.UserDetailView import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.domain.model.ShowDialogRequestModel import dagger.Lazy import javax.inject.Inject +import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch @@ -47,6 +51,9 @@ constructor( private val broadcastSender: Lazy<BroadcastSender>, private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>, private val interactor: Lazy<UserInteractor>, + private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>, + private val eventLogger: Lazy<UiEventLogger>, + private val activityStarter: Lazy<ActivityStarter>, ) : CoreStartable { private var currentDialog: Dialog? = null @@ -108,6 +115,21 @@ constructor( INTERACTION_JANK_EXIT_GUEST_MODE_TAG, ), ) + is ShowDialogRequestModel.ShowUserSwitcherDialog -> + Pair( + UserSwitchDialog( + context = context.get(), + adapter = userDetailAdapterProvider.get(), + uiEventLogger = eventLogger.get(), + falsingManager = falsingManager.get(), + activityStarter = activityStarter.get(), + dialogLaunchAnimator = dialogLaunchAnimator.get(), + ), + DialogCuj( + InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, + INTERACTION_JANK_EXIT_GUEST_MODE_TAG, + ), + ) } currentDialog = dialog diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt new file mode 100644 index 000000000000..3300e8e5b2a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt @@ -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.systemui.user.ui.viewmodel + +import android.content.Context +import android.graphics.drawable.Drawable +import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.Text +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.user.domain.interactor.UserInteractor +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.mapLatest + +@OptIn(ExperimentalCoroutinesApi::class) +class StatusBarUserChipViewModel +@Inject +constructor( + @Application private val context: Context, + interactor: UserInteractor, +) { + /** Whether the status bar chip ui should be available */ + val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled + + /** Whether or not the chip should be showing, based on the number of users */ + val isChipVisible: Flow<Boolean> = + if (!chipEnabled) { + flowOf(false) + } else { + interactor.users.mapLatest { users -> users.size > 1 } + } + + /** The display name of the current user */ + val userName: Flow<Text> = interactor.selectedUser.mapLatest { userModel -> userModel.name } + + /** Avatar for the current user */ + val userAvatar: Flow<Drawable> = + interactor.selectedUser.mapLatest { userModel -> userModel.image } + + /** Action to execute on click. Should launch the user switcher */ + val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java index 6b5556b3ea91..0f3eddf2eb7c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java +++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java @@ -19,7 +19,6 @@ package com.android.systemui.util; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.Context; import android.provider.DeviceConfig; import android.provider.Settings; @@ -53,8 +52,8 @@ public class DeviceConfigProxy { /** * Wrapped version of {@link DeviceConfig#enforceReadPermission}. */ - public void enforceReadPermission(Context context, String namespace) { - DeviceConfig.enforceReadPermission(context, namespace); + public void enforceReadPermission(String namespace) { + DeviceConfig.enforceReadPermission(namespace); } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt b/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt new file mode 100644 index 000000000000..da81d540f189 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt @@ -0,0 +1,42 @@ +/* + * 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.util.condition + +/** + * A higher order [Condition] which combines multiple conditions with a specified + * [Evaluator.ConditionOperand]. + */ +internal class CombinedCondition +constructor( + private val conditions: Collection<Condition>, + @Evaluator.ConditionOperand private val operand: Int +) : Condition(null, false), Condition.Callback { + + override fun start() { + onConditionChanged(this) + conditions.forEach { it.addCallback(this) } + } + + override fun onConditionChanged(condition: Condition) { + Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) } + ?: clearCondition() + } + + override fun stop() { + conditions.forEach { it.removeCallback(this) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java index 2c317dd391c0..b39adefa238b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java @@ -24,7 +24,10 @@ import org.jetbrains.annotations.NotNull; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; +import java.util.List; /** * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform @@ -181,6 +184,42 @@ public abstract class Condition implements CallbackController<Condition.Callback } /** + * Creates a new condition which will only be true when both this condition and all the provided + * conditions are true. + */ + public Condition and(Collection<Condition> others) { + final List<Condition> conditions = new ArrayList<>(others); + conditions.add(this); + return new CombinedCondition(conditions, Evaluator.OP_AND); + } + + /** + * Creates a new condition which will only be true when both this condition and the provided + * condition is true. + */ + public Condition and(Condition other) { + return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND); + } + + /** + * Creates a new condition which will only be true when either this condition or any of the + * provided conditions are true. + */ + public Condition or(Collection<Condition> others) { + final List<Condition> conditions = new ArrayList<>(others); + conditions.add(this); + return new CombinedCondition(conditions, Evaluator.OP_OR); + } + + /** + * Creates a new condition which will only be true when either this condition or the provided + * condition is true. + */ + public Condition or(Condition other) { + return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR); + } + + /** * Callback that receives updates about whether the condition has been fulfilled. */ public interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt b/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt new file mode 100644 index 000000000000..cf44e84a563f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt @@ -0,0 +1,92 @@ +package com.android.systemui.util.condition + +import android.annotation.IntDef + +/** + * Helper for evaluating a collection of [Condition] objects with a given + * [Evaluator.ConditionOperand] + */ +internal object Evaluator { + /** Operands for combining multiple conditions together */ + @Retention(AnnotationRetention.SOURCE) + @IntDef(value = [OP_AND, OP_OR]) + annotation class ConditionOperand + + /** + * 3-valued logical AND operand, with handling for unknown values (represented as null) + * + * ``` + * +-----+----+---+---+ + * | AND | T | F | U | + * +-----+----+---+---+ + * | T | T | F | U | + * | F | F | F | F | + * | U | U | F | U | + * +-----+----+---+---+ + * ``` + */ + const val OP_AND = 0 + + /** + * 3-valued logical OR operand, with handling for unknown values (represented as null) + * + * ``` + * +-----+----+---+---+ + * | OR | T | F | U | + * +-----+----+---+---+ + * | T | T | T | T | + * | F | T | F | U | + * | U | T | U | U | + * +-----+----+---+---+ + * ``` + */ + const val OP_OR = 1 + + /** + * Evaluates a set of conditions with a given operand + * + * If overriding conditions are present, they take precedence over normal conditions if set. + * + * @param conditions The collection of conditions to evaluate. If empty, null is returned. + * @param operand The operand to use when evaluating. + * @return Either true or false if the value is known, or null if value is unknown + */ + fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? { + if (conditions.isEmpty()) return null + // If there are overriding conditions with values set, they take precedence. + val targetConditions = + conditions + .filter { it.isConditionSet && it.isOverridingCondition } + .ifEmpty { conditions } + return when (operand) { + OP_AND -> + threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false) + OP_OR -> + threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true) + else -> null + } + } + + /** + * Helper for evaluating 3-valued logical AND/OR. + * + * @param returnValueIfAnyMatches AND returns false if any value is false. OR returns true if + * any value is true. + */ + private fun threeValuedAndOrOr( + conditions: Collection<Condition>, + returnValueIfAnyMatches: Boolean + ): Boolean? { + var hasUnknown = false + for (condition in conditions) { + if (!condition.isConditionSet) { + hasUnknown = true + continue + } + if (condition.isConditionMet == returnValueIfAnyMatches) { + return returnValueIfAnyMatches + } + } + return if (hasUnknown) null else !returnValueIfAnyMatches + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java index cb430ba454f0..24bc9078475f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java @@ -24,12 +24,10 @@ import com.android.systemui.dagger.qualifiers.Main; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Set; import java.util.concurrent.Executor; -import java.util.stream.Collectors; import javax.inject.Inject; @@ -57,21 +55,10 @@ public class Monitor { } public void update() { - // Only consider set conditions. - final Collection<Condition> setConditions = mSubscription.mConditions.stream() - .filter(Condition::isConditionSet).collect(Collectors.toSet()); - - // Overriding conditions do not override each other - final Collection<Condition> overridingConditions = setConditions.stream() - .filter(Condition::isOverridingCondition).collect(Collectors.toSet()); - - final Collection<Condition> targetCollection = overridingConditions.isEmpty() - ? setConditions : overridingConditions; - - final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection - .stream() - .map(Condition::isConditionMet) - .allMatch(conditionMet -> conditionMet); + final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions, + Evaluator.OP_AND); + // Consider unknown (null) as true + final boolean newAllConditionsMet = result == null || result; if (mAllConditionsMet != null && newAllConditionsMet == mAllConditionsMet) { return; @@ -109,6 +96,7 @@ public class Monitor { /** * Registers a callback and the set of conditions to trigger it. + * * @param subscription A {@link Subscription} detailing the desired conditions and callback. * @return A {@link Subscription.Token} that can be used to remove the subscription. */ @@ -139,6 +127,7 @@ public class Monitor { /** * Removes a subscription from participating in future callbacks. + * * @param token The {@link Subscription.Token} returned when the {@link Subscription} was * originally added. */ @@ -179,7 +168,9 @@ public class Monitor { private final Set<Condition> mConditions; private final Callback mCallback; - /** */ + /** + * + */ public Subscription(Set<Condition> conditions, Callback callback) { this.mConditions = Collections.unmodifiableSet(conditions); this.mCallback = callback; @@ -209,7 +200,6 @@ public class Monitor { /** * Default constructor specifying the {@link Callback} for the {@link Subscription}. - * @param callback */ public Builder(Callback callback) { mCallback = callback; @@ -218,7 +208,7 @@ public class Monitor { /** * Adds a {@link Condition} to be associated with the {@link Subscription}. - * @param condition + * * @return The updated {@link Builder}. */ public Builder addCondition(Condition condition) { @@ -228,7 +218,7 @@ public class Monitor { /** * Adds a set of {@link Condition} to be associated with the {@link Subscription}. - * @param condition + * * @return The updated {@link Builder}. */ public Builder addConditions(Set<Condition> condition) { @@ -238,6 +228,7 @@ public class Monitor { /** * Builds the {@link Subscription}. + * * @return The resulting {@link Subscription}. */ public Subscription build() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java index 0a9c745525c2..ffedb30a404d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java @@ -46,7 +46,7 @@ public class AuthKeyguardMessageAreaTest extends SysuiTestCase { @Test public void testShowsTextField() { mKeyguardMessageArea.setVisibility(View.INVISIBLE); - mKeyguardMessageArea.setMessage("oobleck"); + mKeyguardMessageArea.setMessage("oobleck", /* animate= */ true); assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck"); } @@ -55,7 +55,7 @@ public class AuthKeyguardMessageAreaTest extends SysuiTestCase { public void testHiddenWhenBouncerHidden() { mKeyguardMessageArea.setIsVisible(false); mKeyguardMessageArea.setVisibility(View.INVISIBLE); - mKeyguardMessageArea.setMessage("oobleck"); + mKeyguardMessageArea.setMessage("oobleck", /* animate= */ true); assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE); assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck"); } @@ -63,7 +63,7 @@ public class AuthKeyguardMessageAreaTest extends SysuiTestCase { @Test public void testClearsTextField() { mKeyguardMessageArea.setVisibility(View.VISIBLE); - mKeyguardMessageArea.setMessage(""); + mKeyguardMessageArea.setMessage("", /* animate= */ true); assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE); assertThat(mKeyguardMessageArea.getText()).isEqualTo(""); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt index 7b9b39f23c29..ba46a874c260 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt @@ -49,30 +49,30 @@ class BouncerKeyguardMessageAreaTest : SysuiTestCase() { @Test fun testSetSameMessage() { val underTestSpy = spy(underTest) - underTestSpy.setMessage("abc") - underTestSpy.setMessage("abc") + underTestSpy.setMessage("abc", animate = true) + underTestSpy.setMessage("abc", animate = true) verify(underTestSpy, times(1)).text = "abc" } @Test fun testSetDifferentMessage() { - underTest.setMessage("abc") - underTest.setMessage("def") + underTest.setMessage("abc", animate = true) + underTest.setMessage("def", animate = true) assertThat(underTest.text).isEqualTo("def") } @Test fun testSetNullMessage() { - underTest.setMessage(null) + underTest.setMessage(null, animate = true) assertThat(underTest.text).isEqualTo("") } @Test fun testSetNullClearsPreviousMessage() { - underTest.setMessage("something not null") + underTest.setMessage("something not null", animate = true) assertThat(underTest.text).isEqualTo("something not null") - underTest.setMessage(null) + underTest.setMessage(null, animate = true) assertThat(underTest.text).isEqualTo("") } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index 131cf7d33e3a..88396628017b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -84,10 +84,9 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel( userId = user, listening = false, authInterruptActive = false, - becauseCannotSkipBouncer = false, biometricSettingEnabledForUser = false, bouncerFullyShown = false, - faceAuthenticated = false, + faceAndFpNotAuthenticated = false, faceDisabled = false, faceLockedOut = false, fpLockedOut = false, @@ -101,4 +100,6 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel( secureCameraLaunched = false, switchingUser = false, udfpsBouncerShowing = false, + udfpsFingerDown = false, + userNotTrustedOrDetectionIsNeeded = false ) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java index 829008403e02..0e837d2976ba 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java @@ -85,7 +85,7 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase { @Test public void testClearsTextField() { mMessageAreaController.setMessage(""); - verify(mKeyguardMessageArea).setMessage(""); + verify(mKeyguardMessageArea).setMessage("", /* animate= */ true); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 1ec23e9b542d..7231b3427a71 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -1381,6 +1381,29 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testShouldListenForFace_whenFpIsAlreadyAuthenticated_returnsFalse() + throws RemoteException { + // Face auth should run when the following is true. + bouncerFullyVisibleAndNotGoingToSleep(); + keyguardNotGoingAway(); + currentUserIsPrimary(); + strongAuthNotRequired(); + biometricsEnabledForCurrentUser(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + userNotCurrentlySwitching(); + + mTestableLooper.processAllMessages(); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); + + successfulFingerprintAuth(); + mTestableLooper.processAllMessages(); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); + } + + @Test public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException { cleanupKeyguardUpdateMonitor(); // This disables face auth @@ -1934,6 +1957,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { .onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START); } + private void successfulFingerprintAuth() { + mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback + .onAuthenticationSucceeded( + new FingerprintManager.AuthenticationResult(null, + null, + mCurrentUserId, + true)); + } + private void triggerSuccessfulFaceAuth() { mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); verify(mFaceManager).authenticate(any(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index d0bd4f7026eb..b2c52668e057 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -32,8 +32,10 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; +import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,6 +46,7 @@ import org.junit.runner.RunWith; @SmallTest public class MenuAnimationControllerTest extends SysuiTestCase { + private boolean mLastIsMoveToTucked; private ViewPropertyAnimator mViewPropertyAnimator; private MenuView mMenuView; private MenuAnimationController mMenuAnimationController; @@ -60,6 +63,14 @@ public class MenuAnimationControllerTest extends SysuiTestCase { doReturn(mViewPropertyAnimator).when(mMenuView).animate(); mMenuAnimationController = new MenuAnimationController(mMenuView); + mLastIsMoveToTucked = Prefs.getBoolean(mContext, + Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false); + } + + @After + public void tearDown() throws Exception { + Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, + mLastIsMoveToTucked); } @Test @@ -81,10 +92,34 @@ public class MenuAnimationControllerTest extends SysuiTestCase { @Test public void startGrowAnimation_menuCompletelyOpaque() { - mMenuAnimationController.startShrinkAnimation(null); + mMenuAnimationController.startShrinkAnimation(/* endAction= */ null); mMenuAnimationController.startGrowAnimation(); assertThat(mMenuView.getAlpha()).isEqualTo(/* completelyOpaque */ 1.0f); } + + @Test + public void moveToEdgeAndHide_untucked_expectedSharedPreferenceValue() { + Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* value= */ + false); + + mMenuAnimationController.moveToEdgeAndHide(); + final boolean isMoveToTucked = Prefs.getBoolean(mContext, + Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false); + + assertThat(isMoveToTucked).isTrue(); + } + + @Test + public void moveOutEdgeAndShow_tucked_expectedSharedPreferenceValue() { + Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* value= */ + true); + + mMenuAnimationController.moveOutEdgeAndShow(); + final boolean isMoveToTucked = Prefs.getBoolean(mContext, + Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ true); + + assertThat(isMoveToTucked).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 2d5188fcb95a..428a00a6dcef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -18,12 +18,17 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static android.view.WindowInsets.Type.displayCutout; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowInsets.Type.systemBars; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,13 +37,18 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.graphics.Insets; +import android.graphics.PointF; +import android.graphics.Rect; import android.os.Build; import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; @@ -68,9 +78,20 @@ public class MenuViewLayerTest extends SysuiTestCase { private static final ComponentName TEST_SELECT_TO_SPEAK_COMPONENT_NAME = new ComponentName( SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME); + private static final int DISPLAY_WINDOW_WIDTH = 1080; + private static final int DISPLAY_WINDOW_HEIGHT = 2340; + private static final int STATUS_BAR_HEIGHT = 75; + private static final int NAVIGATION_BAR_HEIGHT = 125; + private static final int IME_HEIGHT = 350; + private static final int IME_TOP = + DISPLAY_WINDOW_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT - IME_HEIGHT; + private MenuViewLayer mMenuViewLayer; private String mLastAccessibilityButtonTargets; private String mLastEnabledAccessibilityServices; + private WindowMetrics mWindowMetrics; + private MenuView mMenuView; + private MenuAnimationController mMenuAnimationController; @Rule public MockitoRule mockito = MockitoJUnit.rule(); @@ -79,13 +100,23 @@ public class MenuViewLayerTest extends SysuiTestCase { private IAccessibilityFloatingMenu mFloatingMenu; @Mock + private WindowManager mStubWindowManager; + + @Mock private AccessibilityManager mStubAccessibilityManager; @Before public void setUp() throws Exception { - final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class); - mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, mStubAccessibilityManager, + final Rect mDisplayBounds = new Rect(); + mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH, + DISPLAY_WINDOW_HEIGHT); + mWindowMetrics = spy(new WindowMetrics(mDisplayBounds, fakeDisplayInsets())); + doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics(); + + mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager, mFloatingMenu); + mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); + mMenuAnimationController = mMenuView.getMenuAnimationController(); mLastAccessibilityButtonTargets = Settings.Secure.getStringForUser(mContext.getContentResolver(), @@ -93,6 +124,12 @@ public class MenuViewLayerTest extends SysuiTestCase { mLastEnabledAccessibilityServices = Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT); + + mMenuViewLayer.onAttachedToWindow(); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT); + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "", UserHandle.USER_CURRENT); } @After @@ -103,6 +140,9 @@ public class MenuViewLayerTest extends SysuiTestCase { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices, UserHandle.USER_CURRENT); + + mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); + mMenuViewLayer.onDetachedFromWindow(); } @Test @@ -168,4 +208,69 @@ public class MenuViewLayerTest extends SysuiTestCase { assertThat(value).isEqualTo(""); } + + @Test + public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() { + final float menuTop = STATUS_BAR_HEIGHT + 100; + mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); + + dispatchShowingImeInsets(); + + assertThat(mMenuView.getTranslationX()).isEqualTo(0); + assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop); + } + + @Test + public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() { + final float menuTop = IME_TOP + 100; + mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); + + dispatchShowingImeInsets(); + + final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight(); + assertThat(mMenuView.getTranslationX()).isEqualTo(0); + assertThat(menuBottom).isLessThan(IME_TOP); + } + + @Test + public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() { + final float menuTop = IME_TOP + 200; + mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); + dispatchShowingImeInsets(); + + dispatchHidingImeInsets(); + + assertThat(mMenuView.getTranslationX()).isEqualTo(0); + assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop); + } + + private void dispatchShowingImeInsets() { + final WindowInsets fakeShowingImeInsets = fakeImeInsets(/* isImeVisible= */ true); + doReturn(fakeShowingImeInsets).when(mWindowMetrics).getWindowInsets(); + mMenuViewLayer.dispatchApplyWindowInsets(fakeShowingImeInsets); + } + + private void dispatchHidingImeInsets() { + final WindowInsets fakeHidingImeInsets = fakeImeInsets(/* isImeVisible= */ false); + doReturn(fakeHidingImeInsets).when(mWindowMetrics).getWindowInsets(); + mMenuViewLayer.dispatchApplyWindowInsets(fakeHidingImeInsets); + } + + private WindowInsets fakeDisplayInsets() { + return new WindowInsets.Builder() + .setVisible(systemBars() | displayCutout(), /* visible= */ true) + .setInsets(systemBars() | displayCutout(), + Insets.of(/* left= */ 0, STATUS_BAR_HEIGHT, /* right= */ 0, + NAVIGATION_BAR_HEIGHT)) + .build(); + } + + private WindowInsets fakeImeInsets(boolean isImeVisible) { + final int bottom = isImeVisible ? (IME_HEIGHT + NAVIGATION_BAR_HEIGHT) : 0; + return new WindowInsets.Builder() + .setVisible(ime(), isImeVisible) + .setInsets(ime(), + Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom)) + .build(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index a872e4b96cce..d6e621f1f135 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.animation.Animator; @@ -171,7 +172,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mCallbacks.onShareButtonTapped(); - verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED); + verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, ""); verify(mClipboardOverlayView, times(1)).getExitAnimation(); } @@ -181,7 +182,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mCallbacks.onDismissButtonTapped(); - verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED); + verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, ""); verify(mClipboardOverlayView, times(1)).getExitAnimation(); } @@ -192,7 +193,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mCallbacks.onSwipeDismissInitiated(mAnimator); mCallbacks.onDismissButtonTapped(); - verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED); + verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null); verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED); } @@ -224,4 +225,16 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { verify(mTimeoutHandler).resetTimeout(); } + + @Test + public void test_logsUseLastClipSource() { + mOverlayController.setClipData(mSampleClipData, "first.package"); + mCallbacks.onDismissButtonTapped(); + mOverlayController.setClipData(mSampleClipData, "second.package"); + mCallbacks.onDismissButtonTapped(); + + verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package"); + verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package"); + verifyNoMoreInteractions(mUiEventLogger); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt index 1e7b1f26ba8c..ed167212c96a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt @@ -48,22 +48,22 @@ class FeatureFlagsDebugRestarterTest : SysuiTestCase() { @Test fun testRestart_ImmediateWhenAsleep() { whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) - restarter.restart() - verify(systemExitRestarter).restart() + restarter.restartSystemUI() + verify(systemExitRestarter).restartSystemUI() } @Test fun testRestart_WaitsForSceenOff() { whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) - restarter.restart() - verify(systemExitRestarter, never()).restart() + restarter.restartSystemUI() + verify(systemExitRestarter, never()).restartSystemUI() val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java) verify(wakefulnessLifecycle).addObserver(captor.capture()) captor.value.onFinishedGoingToSleep() - verify(systemExitRestarter).restart() + verify(systemExitRestarter).restartSystemUI() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt index 68ca48dd327d..7d807e2ff207 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt @@ -63,7 +63,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() { whenever(batteryController.isPluggedIn).thenReturn(true) assertThat(executor.numPending()).isEqualTo(0) - restarter.restart() + restarter.restartSystemUI() assertThat(executor.numPending()).isEqualTo(1) } @@ -72,11 +72,11 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() { whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) whenever(batteryController.isPluggedIn).thenReturn(true) - restarter.restart() - verify(systemExitRestarter, never()).restart() + restarter.restartSystemUI() + verify(systemExitRestarter, never()).restartSystemUI() executor.advanceClockToLast() executor.runAllReady() - verify(systemExitRestarter).restart() + verify(systemExitRestarter).restartSystemUI() } @Test @@ -85,7 +85,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() { whenever(batteryController.isPluggedIn).thenReturn(true) assertThat(executor.numPending()).isEqualTo(0) - restarter.restart() + restarter.restartSystemUI() assertThat(executor.numPending()).isEqualTo(0) } @@ -95,7 +95,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() { whenever(batteryController.isPluggedIn).thenReturn(false) assertThat(executor.numPending()).isEqualTo(0) - restarter.restart() + restarter.restartSystemUI() assertThat(executor.numPending()).isEqualTo(0) } @@ -105,8 +105,8 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() { whenever(batteryController.isPluggedIn).thenReturn(true) assertThat(executor.numPending()).isEqualTo(0) - restarter.restart() - restarter.restart() + restarter.restartSystemUI() + restarter.restartSystemUI() assertThat(executor.numPending()).isEqualTo(1) } @@ -115,7 +115,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() { whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) whenever(batteryController.isPluggedIn).thenReturn(true) assertThat(executor.numPending()).isEqualTo(0) - restarter.restart() + restarter.restartSystemUI() val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java) verify(wakefulnessLifecycle).addObserver(captor.capture()) @@ -131,7 +131,7 @@ class FeatureFlagsReleaseRestarterTest : SysuiTestCase() { whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) whenever(batteryController.isPluggedIn).thenReturn(false) assertThat(executor.numPending()).isEqualTo(0) - restarter.restart() + restarter.restartSystemUI() val captor = ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java) 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 45aaaa2418a1..d17e3744edc6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -65,6 +65,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -112,6 +113,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy; private @Mock DreamOverlayStateController mDreamOverlayStateController; private @Mock ActivityLaunchAnimator mActivityLaunchAnimator; + private @Mock ScrimController mScrimController; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -314,7 +316,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mDreamOverlayStateController, () -> mShadeController, mNotificationShadeWindowControllerLazy, - () -> mActivityLaunchAnimator); + () -> mActivityLaunchAnimator, + () -> mScrimController); mViewMediator.start(); mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 8bd71632638d..52b694fac07c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -62,7 +62,6 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -806,7 +805,6 @@ class MediaDataManagerTest : SysuiTestCase() { .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) } - @Ignore("b/229838140") @Test fun testMediaRecommendationDisabled_removesSmartspaceData() { // GIVEN a media recommendation card is present @@ -823,7 +821,9 @@ class MediaDataManagerTest : SysuiTestCase() { tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") // THEN listeners are notified + uiExecutor.advanceClockToLast() foregroundExecutor.advanceClockToLast() + uiExecutor.runAllReady() foregroundExecutor.runAllReady() verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt new file mode 100644 index 000000000000..4aa982ed1609 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt @@ -0,0 +1,67 @@ +/* + * 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.media.taptotransfer.receiver + +import android.content.Context +import android.os.Handler +import android.os.PowerManager +import android.view.ViewGroup +import android.view.WindowManager +import android.view.accessibility.AccessibilityManager +import com.android.systemui.media.taptotransfer.MediaTttFlags +import com.android.systemui.media.taptotransfer.common.MediaTttLogger +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.view.ViewUtil +import com.android.systemui.util.wakelock.WakeLock + +class FakeMediaTttChipControllerReceiver( + commandQueue: CommandQueue, + context: Context, + logger: MediaTttLogger, + windowManager: WindowManager, + mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, + configurationController: ConfigurationController, + powerManager: PowerManager, + mainHandler: Handler, + mediaTttFlags: MediaTttFlags, + uiEventLogger: MediaTttReceiverUiEventLogger, + viewUtil: ViewUtil, + wakeLockBuilder: WakeLock.Builder, +) : + MediaTttChipControllerReceiver( + commandQueue, + context, + logger, + windowManager, + mainExecutor, + accessibilityManager, + configurationController, + powerManager, + mainHandler, + mediaTttFlags, + uiEventLogger, + viewUtil, + wakeLockBuilder, + ) { + override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { + // Just bypass the animation in tests + onAnimationEnd.run() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 885cc54af7cb..23f7cdb45026 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -114,7 +114,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { fakeWakeLockBuilder = WakeLockFake.Builder(context) fakeWakeLockBuilder.setWakeLock(fakeWakeLock) - controllerReceiver = MediaTttChipControllerReceiver( + controllerReceiver = FakeMediaTttChipControllerReceiver( commandQueue, context, logger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt index 2c2ddbb9b8c5..645b1cde632f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt @@ -16,10 +16,8 @@ package com.android.systemui.qs.footer.domain.interactor -import android.content.ComponentName import android.content.Context import android.content.Intent -import android.os.UserHandle import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -30,17 +28,13 @@ import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.Expandable -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.QSSecurityFooterUtils import com.android.systemui.qs.footer.FooterActionsTestUtils -import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.truth.correspondence.FakeUiEvent import com.android.systemui.truth.correspondence.LogMaker -import com.android.systemui.user.UserSwitcherActivity import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq @@ -156,54 +150,4 @@ class FooterActionsInteractorTest : SysuiTestCase() { // We only unlock the device. verify(activityStarter).postQSRunnableDismissingKeyguard(any()) } - - @Test - fun showUserSwitcher_fullScreenDisabled() { - val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } - val userSwitchDialogController = mock<UserSwitchDialogController>() - val underTest = - utils.footerActionsInteractor( - featureFlags = featureFlags, - userSwitchDialogController = userSwitchDialogController, - ) - - val expandable = mock<Expandable>() - underTest.showUserSwitcher(context, expandable) - - // Dialog is shown. - verify(userSwitchDialogController).showDialog(context, expandable) - } - - @Test - fun showUserSwitcher_fullScreenEnabled() { - val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) } - val activityStarter = mock<ActivityStarter>() - val underTest = - utils.footerActionsInteractor( - featureFlags = featureFlags, - activityStarter = activityStarter, - ) - - // The clicked expandable. - val expandable = mock<Expandable>() - underTest.showUserSwitcher(context, expandable) - - // Dialog is shown. - val intentCaptor = argumentCaptor<Intent>() - verify(activityStarter) - .startActivity( - intentCaptor.capture(), - /* dismissShade= */ eq(true), - /* ActivityLaunchAnimator.Controller= */ nullable(), - /* showOverLockscreenWhenLocked= */ eq(true), - eq(UserHandle.SYSTEM), - ) - assertThat(intentCaptor.value.component) - .isEqualTo( - ComponentName( - context, - UserSwitcherActivity::class.java, - ) - ) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 743e7d69cfc0..4d9db8c28e07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -14,6 +14,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse @@ -31,10 +32,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() { private val hostView = FrameLayout(context) private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView) - private val notificationRow = mock(ExpandableNotificationRow::class.java) - private val dumpManager = mock(DumpManager::class.java) - private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java) - private val notificationShelf = mock(NotificationShelf::class.java) + private val notificationRow = mock<ExpandableNotificationRow>() + private val dumpManager = mock<DumpManager>() + private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>() + private val notificationShelf = mock<NotificationShelf>() private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply { layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100) } @@ -46,7 +47,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { mStatusBarKeyguardViewManager ) - private val testableResources = mContext.orCreateTestableResources + private val testableResources = mContext.getOrCreateTestableResources() private fun px(@DimenRes id: Int): Float = testableResources.resources.getDimensionPixelSize(id).toFloat() @@ -98,7 +99,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) val marginBottom = - context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom) + context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom) val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY) @@ -507,6 +508,192 @@ class StackScrollAlgorithmTest : SysuiTestCase() { assertEquals(1f, currentRoundness) } + @Test + fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() { + // Given: shade is opened, yTranslation of HUN is 0, + // the height of HUN equals to the height of QQS Panel, + // and HUN fully overlaps with QQS Panel + ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + + px(R.dimen.qqs_layout_padding_bottom) + val childHunView = createHunViewMock( + isShadeOpen = true, + fullyVisible = false, + headerVisibleAmount = 1f + ) + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: full shadow would be applied + assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation) + } + + @Test + fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() { + // Given: shade is opened, yTranslation of HUN is greater than 0, + // the height of HUN is equal to the height of QQS Panel, + // and HUN partially overlaps with QQS Panel + ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + + px(R.dimen.qqs_layout_padding_bottom) + val childHunView = createHunViewMock( + isShadeOpen = true, + fullyVisible = false, + headerVisibleAmount = 1f + ) + // Use half of the HUN's height as overlap + childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat() + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: HUN should have shadow, but not as full size + assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f) + assertThat(childHunView.viewState.zTranslation) + .isLessThan(px(R.dimen.heads_up_pinned_elevation)) + } + + @Test + fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() { + // Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height, + // the height of HUN is equal to the height of QQS Panel, + // and HUN doesn't overlap with QQS Panel + ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) + + px(R.dimen.qqs_layout_padding_bottom) + // Mock the height of shade + ambientState.setLayoutMinHeight(1000) + val childHunView = createHunViewMock( + isShadeOpen = true, + fullyVisible = true, + headerVisibleAmount = 1f + ) + // HUN doesn't overlap with QQS Panel + childHunView.viewState.yTranslation = ambientState.topPadding + + ambientState.stackTranslation + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: HUN should not have shadow + assertEquals(0f, childHunView.viewState.zTranslation) + } + + @Test + fun shadeClosed_hunShouldHaveFullShadow() { + // Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding, + // the height of HUN is equal to the height of QQS Panel, + ambientState.stackTranslation = -ambientState.topPadding + // Mock the height of shade + ambientState.setLayoutMinHeight(1000) + val childHunView = createHunViewMock( + isShadeOpen = false, + fullyVisible = false, + headerVisibleAmount = 0f + ) + childHunView.viewState.yTranslation = 0f + // Shade is closed, thus childHunView's headerVisibleAmount is 0 + childHunView.headerVisibleAmount = 0f + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: HUN should have full shadow + assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation) + } + + @Test + fun draggingHunToOpenShade_hunShouldHavePartialShadow() { + // Given: shade is closed when HUN pops up, + // now drags down the HUN to open shade + ambientState.stackTranslation = -ambientState.topPadding + // Mock the height of shade + ambientState.setLayoutMinHeight(1000) + val childHunView = createHunViewMock( + isShadeOpen = false, + fullyVisible = false, + headerVisibleAmount = 0.5f + ) + childHunView.viewState.yTranslation = 0f + // Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1 + // use 0.5 as headerVisibleAmount here + childHunView.headerVisibleAmount = 0.5f + val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState() + algorithmState.visibleChildren.add(childHunView) + + // When: updateChildZValue() is called for the top HUN + stackScrollAlgorithm.updateChildZValue( + /* i= */ 0, + /* childrenOnTop= */ 0.0f, + /* StackScrollAlgorithmState= */ algorithmState, + /* ambientState= */ ambientState, + /* shouldElevateHun= */ true + ) + + // Then: HUN should have shadow, but not as full size + assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f) + assertThat(childHunView.viewState.zTranslation) + .isLessThan(px(R.dimen.heads_up_pinned_elevation)) + } + + private fun createHunViewMock( + isShadeOpen: Boolean, + fullyVisible: Boolean, + headerVisibleAmount: Float + ) = + mock<ExpandableNotificationRow>().apply { + val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible) + whenever(this.viewState).thenReturn(childViewStateMock) + + whenever(this.mustStayOnScreen()).thenReturn(true) + whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount) + } + + + private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) = + ExpandableViewState().apply { + // Mock the HUN's height with ambientState.topPadding + + // ambientState.stackTranslation + height = (ambientState.topPadding + ambientState.stackTranslation).toInt() + if (isShadeOpen && fullyVisible) { + yTranslation = + ambientState.topPadding + ambientState.stackTranslation + } else { + yTranslation = 0f + } + headsUpIsVisible = fullyVisible + } + private fun resetViewStates_expansionChanging_notificationAlphaUpdated( expansionFraction: Float, expectedAlpha: Float diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 6ec5cf82a81e..eb0b9b3a3fb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -56,14 +56,12 @@ import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController; -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoController; +import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -112,16 +110,12 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider; @Mock private UserManager mUserManager; + @Mock + private StatusBarUserChipViewModel mStatusBarUserChipViewModel; @Captor private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor; @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor; - @Mock - private StatusBarUserSwitcherFeatureController mStatusBarUserSwitcherFeatureController; - @Mock - private StatusBarUserSwitcherController mStatusBarUserSwitcherController; - @Mock - private StatusBarUserInfoTracker mStatusBarUserInfoTracker; @Mock private SecureSettings mSecureSettings; @Mock private CommandQueue mCommandQueue; @Mock private KeyguardLogger mLogger; @@ -169,9 +163,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mStatusBarStateController, mStatusBarContentInsetsProvider, mUserManager, - mStatusBarUserSwitcherFeatureController, - mStatusBarUserSwitcherController, - mStatusBarUserInfoTracker, + mStatusBarUserChipViewModel, mSecureSettings, mCommandQueue, mFakeExecutor, @@ -479,8 +471,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Test public void testNewUserSwitcherDisablesAvatar_newUiOn() { // GIVEN the status bar user switcher chip is enabled - when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled()) - .thenReturn(true); + when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true); // WHEN the controller is created mController = createController(); @@ -492,8 +483,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Test public void testNewUserSwitcherDisablesAvatar_newUiOff() { // GIVEN the status bar user switcher chip is disabled - when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled()) - .thenReturn(false); + when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false); // WHEN the controller is created mController = createController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index a61fba5c4000..320a08315843 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -27,11 +27,11 @@ import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.shade.NotificationPanelViewController -import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider +import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel import com.android.systemui.util.mockito.any import com.android.systemui.util.view.ViewUtil import com.google.common.truth.Truth.assertThat @@ -64,7 +64,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock - private lateinit var userSwitcherController: StatusBarUserSwitcherController + private lateinit var userChipViewModel: StatusBarUserChipViewModel @Mock private lateinit var viewUtil: ViewUtil @@ -79,14 +79,13 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { `when`(notificationPanelViewController.view).thenReturn(panelView) `when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController()) .thenReturn(moveFromCenterAnimation) - // create the view on main thread as it requires main looper + // create the view and controller on main thread as it requires main looper InstrumentationRegistry.getInstrumentation().runOnMainSync { val parent = FrameLayout(mContext) // add parent to keep layout params view = LayoutInflater.from(mContext) .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView + controller = createAndInitController(view) } - - controller = createAndInitController(view) } @Test @@ -106,7 +105,10 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { val view = createViewMock() val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) unfoldConfig.isEnabled = true - controller = createAndInitController(view) + // create the controller on main thread as it requires main looper + InstrumentationRegistry.getInstrumentation().runOnMainSync { + controller = createAndInitController(view) + } verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture()) argumentCaptor.value.onPreDraw() @@ -126,7 +128,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { return PhoneStatusBarViewController.Factory( Optional.of(sysuiUnfoldComponent), Optional.of(progressProvider), - userSwitcherController, + userChipViewModel, viewUtil, configurationController ).create(view, touchEventHandler).also { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt deleted file mode 100644 index eba3b04f3472..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone.userswitcher - -import android.content.Intent -import android.os.UserHandle -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.qs.user.UserSwitchDialogController -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.`when` -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -@SmallTest -class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() { - @Mock - private lateinit var tracker: StatusBarUserInfoTracker - - @Mock - private lateinit var featureController: StatusBarUserSwitcherFeatureController - - @Mock - private lateinit var userSwitcherDialogController: UserSwitchDialogController - - @Mock - private lateinit var featureFlags: FeatureFlags - - @Mock - private lateinit var activityStarter: ActivityStarter - - @Mock - private lateinit var falsingManager: FalsingManager - - private lateinit var statusBarUserSwitcherContainer: StatusBarUserSwitcherContainer - private lateinit var controller: StatusBarUserSwitcherControllerImpl - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - statusBarUserSwitcherContainer = StatusBarUserSwitcherContainer(mContext, null) - statusBarUserSwitcherContainer - controller = StatusBarUserSwitcherControllerImpl( - statusBarUserSwitcherContainer, - tracker, - featureController, - userSwitcherDialogController, - featureFlags, - activityStarter, - falsingManager - ) - controller.init() - controller.onViewAttached() - } - - @Test - fun testFalsingManager() { - statusBarUserSwitcherContainer.callOnClick() - verify(falsingManager).isFalseTap(FalsingManager.LOW_PENALTY) - } - - @Test - fun testStartActivity() { - `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false) - statusBarUserSwitcherContainer.callOnClick() - verify(userSwitcherDialogController).showDialog(any(), any()) - `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true) - statusBarUserSwitcherContainer.callOnClick() - verify(activityStarter).startActivity(any(Intent::class.java), - eq(true) /* dismissShade */, - eq(null) /* animationController */, - eq(true) /* showOverLockscreenWhenLocked */, - eq(UserHandle.SYSTEM)) - } -} 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 463f517d5063..4b49420c99be 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 @@ -19,6 +19,7 @@ package com.android.systemui.user.domain.interactor import android.app.ActivityManager import android.app.admin.DevicePolicyManager +import android.content.ComponentName import android.content.Intent import android.content.pm.UserInfo import android.graphics.Bitmap @@ -33,7 +34,10 @@ import com.android.systemui.GuestResetOrExitSessionReceiver import com.android.systemui.GuestResumeSessionReceiver 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.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 @@ -41,6 +45,7 @@ import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor +import com.android.systemui.user.UserSwitcherActivity import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.source.UserRecord @@ -48,9 +53,11 @@ import com.android.systemui.user.domain.model.ShowDialogRequestModel import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.user.shared.model.UserModel import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.kotlinArgumentCaptor 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 @@ -90,6 +97,7 @@ class UserInteractorTest : SysuiTestCase() { private lateinit var userRepository: FakeUserRepository private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var telephonyRepository: FakeTelephonyRepository + private lateinit var featureFlags: FakeFeatureFlags @Before fun setUp() { @@ -104,6 +112,7 @@ class UserInteractorTest : SysuiTestCase() { SUPERVISED_USER_CREATION_APP_PACKAGE, ) + featureFlags = FakeFeatureFlags() userRepository = FakeUserRepository() keyguardRepository = FakeKeyguardRepository() telephonyRepository = FakeTelephonyRepository() @@ -147,7 +156,8 @@ class UserInteractorTest : SysuiTestCase() { uiEventLogger = uiEventLogger, resumeSessionReceiver = resumeSessionReceiver, resetOrExitSessionReceiver = resetOrExitSessionReceiver, - ) + ), + featureFlags = featureFlags, ) } @@ -715,6 +725,52 @@ class UserInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun `show user switcher - full screen disabled - shows dialog switcher`() = + runBlocking(IMMEDIATE) { + featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false) + + var dialogRequest: ShowDialogRequestModel? = null + val expandable = mock<Expandable>() + underTest.showUserSwitcher(context, expandable) + + val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + + // Dialog is shown. + assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog) + + underTest.onDialogShown() + assertThat(dialogRequest).isNull() + + job.cancel() + } + + @Test + fun `show user switcher - full screen enabled - launches activity`() { + featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + + val expandable = mock<Expandable>() + underTest.showUserSwitcher(context, expandable) + + // Dialog is shown. + val intentCaptor = argumentCaptor<Intent>() + verify(activityStarter) + .startActivity( + intentCaptor.capture(), + /* dismissShade= */ eq(true), + /* ActivityLaunchAnimator.Controller= */ nullable(), + /* showOverLockscreenWhenLocked= */ eq(true), + eq(UserHandle.SYSTEM), + ) + assertThat(intentCaptor.value.component) + .isEqualTo( + ComponentName( + context, + UserSwitcherActivity::class.java, + ) + ) + } + private fun assertUsers( models: List<UserModel>?, count: Int, 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 new file mode 100644 index 000000000000..db348b8029a0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -0,0 +1,306 @@ +/* + * 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.viewmodel + +import android.app.ActivityManager +import android.app.admin.DevicePolicyManager +import android.content.pm.UserInfo +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.os.UserManager +import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger +import com.android.systemui.GuestResetOrExitSessionReceiver +import com.android.systemui.GuestResumeSessionReceiver +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Text +import com.android.systemui.flags.FakeFeatureFlags +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.policy.DeviceProvisionedController +import com.android.systemui.telephony.data.repository.FakeTelephonyRepository +import com.android.systemui.telephony.domain.interactor.TelephonyInteractor +import com.android.systemui.user.data.model.UserSwitcherSettingsModel +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.interactor.GuestUserInteractor +import com.android.systemui.user.domain.interactor.RefreshUsersScheduler +import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.yield +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.doAnswer +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class StatusBarUserChipViewModelTest : SysuiTestCase() { + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var activityManager: ActivityManager + @Mock private lateinit var manager: UserManager + @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController + @Mock private lateinit var devicePolicyManager: DevicePolicyManager + @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver + @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver + + private lateinit var underTest: StatusBarUserChipViewModel + + private val userRepository = FakeUserRepository() + private val keyguardRepository = FakeKeyguardRepository() + private val featureFlags = FakeFeatureFlags() + private lateinit var guestUserInteractor: GuestUserInteractor + private lateinit var refreshUsersScheduler: RefreshUsersScheduler + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + doAnswer { invocation -> + val userId = invocation.arguments[0] as Int + when (userId) { + USER_ID_0 -> return@doAnswer USER_IMAGE_0 + USER_ID_1 -> return@doAnswer USER_IMAGE_1 + USER_ID_2 -> return@doAnswer USER_IMAGE_2 + else -> return@doAnswer mock<Bitmap>() + } + } + .`when`(manager) + .getUserIcon(anyInt()) + + userRepository.isStatusBarUserChipEnabled = true + + refreshUsersScheduler = + RefreshUsersScheduler( + applicationScope = testScope.backgroundScope, + mainDispatcher = testDispatcher, + repository = userRepository, + ) + guestUserInteractor = + GuestUserInteractor( + applicationContext = context, + applicationScope = testScope.backgroundScope, + mainDispatcher = testDispatcher, + backgroundDispatcher = testDispatcher, + manager = manager, + repository = userRepository, + deviceProvisionedController = deviceProvisionedController, + devicePolicyManager = devicePolicyManager, + refreshUsersScheduler = refreshUsersScheduler, + uiEventLogger = uiEventLogger, + resumeSessionReceiver = resumeSessionReceiver, + resetOrExitSessionReceiver = resetOrExitSessionReceiver, + ) + + underTest = viewModel() + } + + @Test + fun `config is false - chip is disabled`() { + // the enabled bit is set at SystemUI startup, so recreate the view model here + userRepository.isStatusBarUserChipEnabled = false + underTest = viewModel() + + assertThat(underTest.chipEnabled).isFalse() + } + + @Test + fun `config is true - chip is enabled`() { + // the enabled bit is set at SystemUI startup, so recreate the view model here + userRepository.isStatusBarUserChipEnabled = true + underTest = viewModel() + + assertThat(underTest.chipEnabled).isTrue() + } + + @Test + fun `should show chip criteria - single user`() = + testScope.runTest { + userRepository.setUserInfos(listOf(USER_0)) + userRepository.setSelectedUserInfo(USER_0) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + + val values = mutableListOf<Boolean>() + + val job = launch { underTest.isChipVisible.toList(values) } + advanceUntilIdle() + + assertThat(values).containsExactly(false) + + job.cancel() + } + + @Test + fun `should show chip criteria - multiple users`() = + testScope.runTest { + setMultipleUsers() + + var latest: Boolean? = null + val job = underTest.isChipVisible.onEach { latest = it }.launchIn(this) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun `user chip name - shows selected user info`() = + testScope.runTest { + setMultipleUsers() + + var latest: Text? = null + val job = underTest.userName.onEach { latest = it }.launchIn(this) + + userRepository.setSelectedUserInfo(USER_0) + assertThat(latest).isEqualTo(USER_NAME_0) + + userRepository.setSelectedUserInfo(USER_1) + assertThat(latest).isEqualTo(USER_NAME_1) + + userRepository.setSelectedUserInfo(USER_2) + assertThat(latest).isEqualTo(USER_NAME_2) + + job.cancel() + } + + @Test + fun `user chip avatar - shows selected user info`() = + testScope.runTest { + setMultipleUsers() + + // A little hacky. System server passes us bitmaps and we wrap them in the interactor. + // Unwrap them to make sure we're always tracking the current user's bitmap + var latest: Bitmap? = null + val job = + underTest.userAvatar + .onEach { + if (it !is BitmapDrawable) { + latest = null + } + + latest = (it as BitmapDrawable).bitmap + } + .launchIn(this) + + userRepository.setSelectedUserInfo(USER_0) + assertThat(latest).isEqualTo(USER_IMAGE_0) + + userRepository.setSelectedUserInfo(USER_1) + assertThat(latest).isEqualTo(USER_IMAGE_1) + + userRepository.setSelectedUserInfo(USER_2) + assertThat(latest).isEqualTo(USER_IMAGE_2) + + job.cancel() + } + + private fun viewModel(): StatusBarUserChipViewModel { + return StatusBarUserChipViewModel( + context = context, + interactor = + UserInteractor( + applicationContext = context, + repository = userRepository, + activityStarter = activityStarter, + keyguardInteractor = + KeyguardInteractor( + repository = keyguardRepository, + ), + featureFlags = featureFlags, + manager = manager, + applicationScope = testScope.backgroundScope, + telephonyInteractor = + TelephonyInteractor( + repository = FakeTelephonyRepository(), + ), + broadcastDispatcher = fakeBroadcastDispatcher, + backgroundDispatcher = testDispatcher, + activityManager = activityManager, + refreshUsersScheduler = refreshUsersScheduler, + guestUserInteractor = guestUserInteractor, + ) + ) + } + + private suspend fun setMultipleUsers() { + userRepository.setUserInfos(listOf(USER_0, USER_1, USER_2)) + userRepository.setSelectedUserInfo(USER_0) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + } + + companion object { + private const val USER_ID_0 = 0 + private val USER_IMAGE_0 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + private val USER_NAME_0 = Text.Loaded("zero") + + private const val USER_ID_1 = 1 + private val USER_IMAGE_1 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + private val USER_NAME_1 = Text.Loaded("one") + + private const val USER_ID_2 = 2 + private val USER_IMAGE_2 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + private val USER_NAME_2 = Text.Loaded("two") + + private val USER_0 = + UserInfo( + USER_ID_0, + USER_NAME_0.text!!, + /* iconPath */ "", + /* flags */ 0, + /* userType */ UserManager.USER_TYPE_FULL_SYSTEM + ) + + private val USER_1 = + UserInfo( + USER_ID_1, + USER_NAME_1.text!!, + /* iconPath */ "", + /* flags */ 0, + /* userType */ UserManager.USER_TYPE_FULL_SYSTEM + ) + + private val USER_2 = + UserInfo( + USER_ID_2, + USER_NAME_2.text!!, + /* iconPath */ "", + /* flags */ 0, + /* userType */ UserManager.USER_TYPE_FULL_SYSTEM + ) + } +} 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 db136800a3cc..eac7fc21e505 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 @@ -27,6 +27,7 @@ import com.android.systemui.GuestResetOrExitSessionReceiver import com.android.systemui.GuestResumeSessionReceiver import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Text +import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter @@ -147,6 +148,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { KeyguardInteractor( repository = keyguardRepository, ), + featureFlags = FakeFeatureFlags(), manager = manager, applicationScope = injectedScope, telephonyInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java index 0b53133e9353..28788647dd58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java @@ -141,4 +141,158 @@ public class ConditionTest extends SysuiTestCase { mCondition.clearCondition(); assertThat(mCondition.isConditionSet()).isFalse(); } + + @Test + public void combineConditionsWithOr_allFalse_reportsNotMet() { + mCondition.fakeUpdateCondition(false); + + final Condition combinedCondition = mCondition.or( + new FakeCondition(/* initialValue= */ false)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isTrue(); + assertThat(combinedCondition.isConditionMet()).isFalse(); + verify(callback, times(1)).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithOr_allTrue_reportsMet() { + mCondition.fakeUpdateCondition(true); + + final Condition combinedCondition = mCondition.or( + new FakeCondition(/* initialValue= */ true)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isTrue(); + assertThat(combinedCondition.isConditionMet()).isTrue(); + verify(callback, times(1)).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithOr_singleTrue_reportsMet() { + mCondition.fakeUpdateCondition(false); + + final Condition combinedCondition = mCondition.or( + new FakeCondition(/* initialValue= */ true)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isTrue(); + assertThat(combinedCondition.isConditionMet()).isTrue(); + verify(callback, times(1)).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithOr_unknownAndTrue_reportsMet() { + mCondition.fakeUpdateCondition(true); + + // Combine with an unset condition. + final Condition combinedCondition = mCondition.or( + new FakeCondition(/* initialValue= */ null)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isTrue(); + assertThat(combinedCondition.isConditionMet()).isTrue(); + verify(callback, times(1)).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithOr_unknownAndFalse_reportsNotMet() { + mCondition.fakeUpdateCondition(false); + + // Combine with an unset condition. + final Condition combinedCondition = mCondition.or( + new FakeCondition(/* initialValue= */ null)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isFalse(); + assertThat(combinedCondition.isConditionMet()).isFalse(); + verify(callback, never()).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithAnd_allFalse_reportsNotMet() { + mCondition.fakeUpdateCondition(false); + + final Condition combinedCondition = mCondition.and( + new FakeCondition(/* initialValue= */ false)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isTrue(); + assertThat(combinedCondition.isConditionMet()).isFalse(); + verify(callback, times(1)).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithAnd_allTrue_reportsMet() { + mCondition.fakeUpdateCondition(true); + + final Condition combinedCondition = mCondition.and( + new FakeCondition(/* initialValue= */ true)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isTrue(); + assertThat(combinedCondition.isConditionMet()).isTrue(); + verify(callback, times(1)).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithAnd_singleTrue_reportsNotMet() { + mCondition.fakeUpdateCondition(true); + + final Condition combinedCondition = mCondition.and( + new FakeCondition(/* initialValue= */ false)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isTrue(); + assertThat(combinedCondition.isConditionMet()).isFalse(); + verify(callback, times(1)).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithAnd_unknownAndTrue_reportsNotMet() { + mCondition.fakeUpdateCondition(true); + + // Combine with an unset condition. + final Condition combinedCondition = mCondition.and( + new FakeCondition(/* initialValue= */ null)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isFalse(); + assertThat(combinedCondition.isConditionMet()).isFalse(); + verify(callback, never()).onConditionChanged(combinedCondition); + } + + @Test + public void combineConditionsWithAnd_unknownAndFalse_reportsMet() { + mCondition.fakeUpdateCondition(false); + + // Combine with an unset condition. + final Condition combinedCondition = mCondition.and( + new FakeCondition(/* initialValue= */ null)); + + final Condition.Callback callback = mock(Condition.Callback.class); + combinedCondition.addCallback(callback); + + assertThat(combinedCondition.isConditionSet()).isTrue(); + assertThat(combinedCondition.isConditionMet()).isFalse(); + verify(callback, times(1)).onConditionChanged(combinedCondition); + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt index 325da4ead666..63448e236867 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt @@ -28,8 +28,6 @@ import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.FeatureFlags import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -43,7 +41,6 @@ import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.security.data.repository.SecurityRepository import com.android.systemui.security.data.repository.SecurityRepositoryImpl import com.android.systemui.settings.FakeUserTracker @@ -54,6 +51,7 @@ import com.android.systemui.statusbar.policy.FakeUserInfoController import com.android.systemui.statusbar.policy.SecurityController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.GlobalSettings @@ -97,13 +95,12 @@ class FooterActionsTestUtils( /** Create a [FooterActionsInteractor] to be used in tests. */ fun footerActionsInteractor( activityStarter: ActivityStarter = mock(), - featureFlags: FeatureFlags = FakeFeatureFlags(), metricsLogger: MetricsLogger = FakeMetricsLogger(), uiEventLogger: UiEventLogger = UiEventLoggerFake(), deviceProvisionedController: DeviceProvisionedController = mock(), qsSecurityFooterUtils: QSSecurityFooterUtils = mock(), fgsManagerController: FgsManagerController = mock(), - userSwitchDialogController: UserSwitchDialogController = mock(), + userInteractor: UserInteractor = mock(), securityRepository: SecurityRepository = securityRepository(), foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(), userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(), @@ -112,13 +109,12 @@ class FooterActionsTestUtils( ): FooterActionsInteractor { return FooterActionsInteractorImpl( activityStarter, - featureFlags, metricsLogger, uiEventLogger, deviceProvisionedController, qsSecurityFooterUtils, fgsManagerController, - userSwitchDialogController, + userInteractor, securityRepository, foregroundServicesRepository, userSwitcherRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index b7c8cbf40bea..ea5a302ce05a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -49,6 +49,8 @@ class FakeUserRepository : UserRepository { override val isGuestUserCreationScheduled = AtomicBoolean() + override var isStatusBarUserChipEnabled: Boolean = false + override var secondaryUserId: Int = UserHandle.USER_NULL override var isRefreshUsersPaused: Boolean = false diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java index 33ece0084906..21e16a1e7be4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java @@ -16,7 +16,6 @@ package com.android.systemui.util; -import android.content.Context; import android.provider.DeviceConfig; import android.provider.DeviceConfig.OnPropertiesChangedListener; import android.provider.DeviceConfig.Properties; @@ -83,7 +82,7 @@ public class DeviceConfigProxyFake extends DeviceConfigProxy { } @Override - public void enforceReadPermission(Context context, String namespace) { + public void enforceReadPermission(String namespace) { // no-op } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java index 1353ad25d057..07ed1102e990 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java @@ -25,15 +25,21 @@ public class FakeCondition extends Condition { super(); } - FakeCondition(Boolean initialValue, Boolean overriding) { + FakeCondition(Boolean initialValue) { + super(initialValue, false); + } + + FakeCondition(Boolean initialValue, boolean overriding) { super(initialValue, overriding); } @Override - public void start() {} + public void start() { + } @Override - public void stop() {} + public void stop() { + } public void fakeUpdateCondition(boolean isConditionMet) { updateCondition(isConditionMet); diff --git a/services/Android.bp b/services/Android.bp index 76a148419506..f6570e9d2702 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -60,9 +60,17 @@ system_optimized_java_defaults { ignore_warnings: false, proguard_flags_files: ["proguard.flags"], }, - // Note: Optimizations are disabled by default if unspecified in - // the java_library rule. - conditions_default: {}, + conditions_default: { + optimize: { + enabled: true, + optimize: false, + shrink: true, + ignore_warnings: false, + // Note that this proguard config is very conservative, only shrinking the + // permission subpackage to prune unused jarjar'ed Kotlin dependencies. + proguard_flags_files: ["proguard_permission.flags"], + }, + }, }, }, } @@ -97,6 +105,7 @@ filegroup { ":services.midi-sources", ":services.musicsearch-sources", ":services.net-sources", + ":services.permission-sources", ":services.print-sources", ":services.profcollect-sources", ":services.restrictions-sources", @@ -131,6 +140,7 @@ java_library { app_image: true, profile: "art-profile", }, + exclude_kotlinc_generated_files: true, srcs: [":services-main-sources"], @@ -152,6 +162,7 @@ java_library { "services.musicsearch", "services.net", "services.people", + "services.permission", "services.print", "services.profcollect", "services.restrictions", diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index c77b59717678..786d4076191e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -76,6 +76,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.KeyEvent; import android.view.MagnificationSpec; +import android.view.SurfaceControl; import android.view.View; import android.view.WindowInfo; import android.view.accessibility.AccessibilityCache; @@ -289,6 +290,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ void requestImeLocked(AbstractAccessibilityServiceConnection connection); void unbindImeLocked(AbstractAccessibilityServiceConnection connection); + + void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc); } public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName, @@ -2486,4 +2489,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } -}
\ No newline at end of file + + @Override + public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) { + mSystemSupport.attachAccessibilityOverlayToDisplay(displayId, sc); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index e3ae03cbcdd8..87d166815323 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -109,6 +109,7 @@ import android.view.IWindow; import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.MotionEvent; +import android.view.SurfaceControl; import android.view.WindowInfo; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; @@ -291,12 +292,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private Point mTempPoint = new Point(); private boolean mIsAccessibilityButtonShown; - private boolean mInputBound; IRemoteAccessibilityInputConnection mRemoteInputConnection; EditorInfo mEditorInfo; boolean mRestarting; boolean mInputSessionRequested; + private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>(); private AccessibilityUserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); @@ -3967,6 +3968,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub synchronized (mLock) { mDisplaysList.add(display); + mA11yOverlayLayers.put( + displayId, mWindowManagerService.getA11yOverlayLayer(displayId)); if (mInputFilter != null) { mInputFilter.onDisplayAdded(display); } @@ -3990,6 +3993,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!removeDisplayFromList(displayId)) { return; } + mA11yOverlayLayers.remove(displayId); if (mInputFilter != null) { mInputFilter.onDisplayRemoved(displayId); } @@ -4691,4 +4695,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return true; } } + + @Override + public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) { + mMainHandler.sendMessage( + obtainMessage( + AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal, + this, + displayId, + sc)); + } + + void attachAccessibilityOverlayToDisplayInternal(int displayId, SurfaceControl sc) { + if (!mA11yOverlayLayers.contains(displayId)) { + mA11yOverlayLayers.put(displayId, mWindowManagerService.getA11yOverlayLayer(displayId)); + } + SurfaceControl parent = mA11yOverlayLayers.get(displayId); + if (parent == null) { + Slog.e(LOG_TAG, "Unable to get accessibility overlay SurfaceControl."); + mA11yOverlayLayers.remove(displayId); + return; + } + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction.reparent(sc, parent); + transaction.apply(); + transaction.close(); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9ea9e34f58a1..39f6ef2290f8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -181,6 +181,7 @@ import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; import android.app.ApplicationThreadConstants; import android.app.BroadcastOptions; +import android.app.ComponentOptions; import android.app.ContentProviderHolder; import android.app.IActivityController; import android.app.IActivityManager; @@ -13914,10 +13915,10 @@ public class ActivityManagerService extends IActivityManager.Stub throw new SecurityException( "Non-system callers may not flag broadcasts as alarm"); } - if (options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) { + if (options.containsKey(ComponentOptions.KEY_INTERACTIVE)) { enforceCallingPermission( - android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE, - "setInteractiveBroadcast"); + android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE, + "setInteractive"); } } } diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index b7de57f8fc71..45b11e1dd0c5 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -506,10 +506,14 @@ public class AppProfiler { } if (profile != null) { long startTime = SystemClock.currentThreadTimeMillis(); - // skip background PSS calculation of apps that are capturing - // camera imagery - final boolean usingCamera = mService.isCameraActiveForUid(profile.mApp.uid); - long pss = usingCamera ? 0 : Debug.getPss(pid, tmp, null); + // skip background PSS calculation under the following situations: + // - app is capturing camera imagery + // - app is frozen and we have already collected PSS once. + final boolean skipPSSCollection = + (profile.mApp.mOptRecord != null + && profile.mApp.mOptRecord.skipPSSCollectionBecauseFrozen()) + || mService.isCameraActiveForUid(profile.mApp.uid); + long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null); long endTime = SystemClock.currentThreadTimeMillis(); synchronized (mProfilerLock) { if (pss != 0 && profile.getThread() != null @@ -524,7 +528,7 @@ public class AppProfiler { if (DEBUG_PSS) { Slog.d(TAG_PSS, "Skipped pss collection of " + pid + ": " + (profile.getThread() == null ? "NO_THREAD " : "") - + (usingCamera ? "CAMERA " : "") + + (skipPSSCollection ? "SKIP_PSS_COLLECTION " : "") + (profile.getPid() != pid ? "PID_CHANGED " : "") + " initState=" + procState + " curState=" + profile.getSetProcState() + " " diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index dfac82cbe91b..04ba757d03e7 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -136,8 +136,8 @@ public class BroadcastConstants { private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = true; /** - * For {@link BroadcastQueueModernImpl}: Maximum number of process queues to - * dispatch broadcasts to simultaneously. + * For {@link BroadcastQueueModernImpl}: Maximum dispatch parallelism + * that we'll tolerate for ordinary broadcast dispatch. */ public int MAX_RUNNING_PROCESS_QUEUES = DEFAULT_MAX_RUNNING_PROCESS_QUEUES; private static final String KEY_MAX_RUNNING_PROCESS_QUEUES = "bcast_max_running_process_queues"; @@ -145,6 +145,15 @@ public class BroadcastConstants { ActivityManager.isLowRamDeviceStatic() ? 2 : 4; /** + * For {@link BroadcastQueueModernImpl}: Additional running process queue parallelism beyond + * {@link #MAX_RUNNING_PROCESS_QUEUES} for dispatch of "urgent" broadcasts. + */ + public int EXTRA_RUNNING_URGENT_PROCESS_QUEUES = DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES; + private static final String KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = + "bcast_extra_running_urgent_process_queues"; + private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1; + + /** * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts * to dispatch to a "running" process queue before we retire them back to * being "runnable" to give other processes a chance to run. @@ -250,6 +259,10 @@ public class BroadcastConstants { updateDeviceConfigConstants(); } + public int getMaxRunningQueues() { + return MAX_RUNNING_PROCESS_QUEUES + EXTRA_RUNNING_URGENT_PROCESS_QUEUES; + } + private void updateSettingsConstants() { synchronized (this) { try { @@ -317,6 +330,9 @@ public class BroadcastConstants { DEFAULT_MODERN_QUEUE_ENABLED); MAX_RUNNING_PROCESS_QUEUES = getDeviceConfigInt(KEY_MAX_RUNNING_PROCESS_QUEUES, DEFAULT_MAX_RUNNING_PROCESS_QUEUES); + EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt( + KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES, + DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES); MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS); MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS, diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 7d9b4776bf37..0f9c775751af 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -584,6 +584,14 @@ class BroadcastProcessQueue { } /** + * Report whether this queue is currently handling an urgent broadcast. + */ + public boolean isPendingUrgent() { + BroadcastRecord next = peekNextBroadcastRecord(); + return (next != null) ? next.isUrgent() : false; + } + + /** * Quickly determine if this queue has broadcasts that are still waiting to * be delivered at some point in the future. */ diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index fb7e0be69d78..8ece1807a2ae 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -42,9 +42,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; +import android.app.ApplicationExitInfo; import android.app.BroadcastOptions; import android.app.IApplicationThread; -import android.app.RemoteServiceException.CannotDeliverBroadcastException; import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.ContentResolver; @@ -728,19 +728,14 @@ public class BroadcastQueueImpl extends BroadcastQueue { thread.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras, ordered, sticky, sendingUser, app.mState.getReportedProcState()); - // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting - // DeadObjectException when the process isn't actually dead. - //} catch (DeadObjectException ex) { - // Failed to call into the process. It's dying so just let it die and move on. - // throw ex; } catch (RemoteException ex) { // Failed to call into the process. It's either dying or wedged. Kill it gently. synchronized (mService) { final String msg = "Failed to schedule " + intent + " to " + receiver + " via " + app + ": " + ex; Slog.w(TAG, msg); - app.scheduleCrashLocked(msg, - CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null); + app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, + true); } throw ex; } @@ -1393,8 +1388,7 @@ public class BroadcastQueueImpl extends BroadcastQueue { final String msg = "Failed to schedule " + r.intent + " to " + info + " via " + app + ": " + e; Slog.w(TAG, msg); - app.scheduleCrashLocked(msg, - CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null); + app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true); } catch (RuntimeException e) { Slog.wtf(TAG, "Failed sending broadcast to " + r.curComponent + " with " + r.intent, e); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index a7d843361762..ad5aa87b6a50 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -42,9 +42,9 @@ import android.annotation.Nullable; import android.annotation.UptimeMillisLong; import android.app.Activity; import android.app.ActivityManager; +import android.app.ApplicationExitInfo; import android.app.BroadcastOptions; import android.app.IApplicationThread; -import android.app.RemoteServiceException.CannotDeliverBroadcastException; import android.app.UidObserver; import android.app.usage.UsageEvents.Event; import android.content.ComponentName; @@ -138,7 +138,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // We configure runnable size only once at boot; it'd be too complex to // try resizing dynamically at runtime - mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES]; + mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()]; } /** @@ -293,6 +293,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } /** + * Return the number of active queues that are delivering "urgent" broadcasts + */ + private int getRunningUrgentCount() { + int count = 0; + for (int i = 0; i < mRunning.length; i++) { + if (mRunning[i] != null && mRunning[i].getActive().isUrgent()) { + count++; + } + } + return count; + } + + /** * Return the first index of the given value contained inside * {@link #mRunning}, otherwise {@code -1}. */ @@ -356,7 +369,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ @GuardedBy("mService") private void updateRunningListLocked() { - int avail = mRunning.length - getRunningSize(); + // Allocated size here implicitly includes the extra reservation for urgent + // dispatches beyond the MAX_RUNNING_QUEUES soft limit for normal + // parallelism. If we're already dispatching some urgent broadcasts, + // count that against the extra first - its role is to permit progress of + // urgent broadcast traffic when the normal reservation is fully occupied + // with less-urgent dispatches, not to generally expand parallelism. + final int usedExtra = Math.min(getRunningUrgentCount(), + mConstants.EXTRA_RUNNING_URGENT_PROCESS_QUEUES); + int avail = mRunning.length - getRunningSize() - usedExtra; if (avail == 0) return; final int cookie = traceBegin("updateRunningList"); @@ -382,6 +403,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue { continue; } + // If we've hit the soft limit for non-urgent dispatch parallelism, + // only consider delivering from queues whose ready broadcast is urgent + if (getRunningSize() >= mConstants.MAX_RUNNING_PROCESS_QUEUES) { + if (!queue.isPendingUrgent()) { + queue = nextQueue; + continue; + } + } + // If queues beyond this point aren't ready to run yet, schedule // another pass when they'll be runnable if (runnableAt > now && !waitingFor) { @@ -801,8 +831,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final String msg = "Failed to schedule " + r + " to " + receiver + " via " + app + ": " + e; logw(msg); - app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null); - app.setKilled(true); + app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true); enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app"); } } else { @@ -829,7 +858,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } catch (RemoteException e) { final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e; logw(msg); - app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null); + app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, true); } } // Clear so both local and remote references can be GC'ed diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 6ea2dee5b578..84d744270e82 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -397,7 +397,7 @@ final class BroadcastRecord extends Binder { alarm = options != null && options.isAlarmBroadcast(); pushMessage = options != null && options.isPushMessagingBroadcast(); pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast(); - interactive = options != null && options.isInteractiveBroadcast(); + interactive = options != null && options.isInteractive(); this.filterExtrasForReceiver = filterExtrasForReceiver; } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index b98639e31d00..4c10d58bef2c 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -2004,6 +2004,7 @@ public final class CachedAppOptimizer { opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis()); opt.setFrozen(true); + opt.setHasCollectedFrozenPSS(false); mFrozenProcesses.put(pid, proc); } catch (Exception e) { Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name); diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index fb41a39fa2bd..24cc5337b86f 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -73,6 +73,12 @@ final class ProcessCachedOptimizerRecord { private boolean mFrozen; /** + * Set to false after the process has been frozen. + * Set to true after we have collected PSS for the frozen process. + */ + private boolean mHasCollectedFrozenPSS; + + /** * An override on the freeze state is in progress. */ @GuardedBy("mProcLock") @@ -188,6 +194,25 @@ final class ProcessCachedOptimizerRecord { mFrozen = frozen; } + boolean skipPSSCollectionBecauseFrozen() { + boolean collected = mHasCollectedFrozenPSS; + + // This check is racy but it isn't critical to PSS collection that we have the most up to + // date idea of whether a task is frozen. + if (!mFrozen) { + // not frozen == always ask to collect PSS + return false; + } + + // We don't want to count PSS for a frozen process more than once. + mHasCollectedFrozenPSS = true; + return collected; + } + + void setHasCollectedFrozenPSS(boolean collected) { + mHasCollectedFrozenPSS = collected; + } + @GuardedBy("mProcLock") boolean hasFreezerOverride() { return mFreezerOverride; diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index f8d4b8fffd03..42fe9d88825b 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -18,11 +18,11 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; -import static android.view.Display.DEFAULT_DISPLAY; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; +import android.hardware.devicestate.DeviceStateManager; import com.android.internal.util.Preconditions; @@ -55,6 +55,15 @@ public final class DeviceState { */ public static final int FLAG_APP_INACCESSIBLE = 1 << 1; + /** + * Some device states can be both entered through a physical configuration as well as emulation + * through {@link DeviceStateManager#requestState}, while some states can only be entered + * through emulation and have no physical configuration to match. + * + * This flag indicates that the corresponding state can only be entered through emulation. + */ + public static final int FLAG_EMULATED_ONLY = 1 << 2; + /** @hide */ @IntDef(prefix = {"FLAG_"}, flag = true, value = { FLAG_CANCEL_OVERRIDE_REQUESTS, diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 44c8e18a22cf..925fc21737e5 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -17,6 +17,11 @@ package com.android.server.devicestate; import static android.Manifest.permission.CONTROL_DEVICE_STATE; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.hardware.devicestate.DeviceStateManager.ACTION_SHOW_REAR_DISPLAY_OVERLAY; +import static android.hardware.devicestate.DeviceStateManager.EXTRA_ORIGINAL_DEVICE_BASE_STATE; +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE; import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE; @@ -31,7 +36,10 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityOptions; +import android.app.WindowConfiguration; import android.content.Context; +import android.content.Intent; import android.hardware.devicestate.DeviceStateInfo; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManagerInternal; @@ -157,6 +165,15 @@ public final class DeviceStateManagerService extends SystemService { private Set<Integer> mDeviceStatesAvailableForAppRequests; + private Set<Integer> mFoldedDeviceStates; + + @Nullable + private DeviceState mRearDisplayState; + + // TODO(259328837) Generalize for all pending feature requests in the future + @Nullable + private OverrideRequest mRearDisplayPendingOverrideRequest; + @VisibleForTesting interface SystemPropertySetter { void setDebugTracingDeviceStateProperty(String value); @@ -201,6 +218,7 @@ public final class DeviceStateManagerService extends SystemService { synchronized (mLock) { readStatesAvailableForRequestFromApps(); + mFoldedDeviceStates = readFoldedStates(); } } @@ -350,6 +368,8 @@ public final class DeviceStateManagerService extends SystemService { mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers); updatePendingStateLocked(); + setRearDisplayStateLocked(); + if (!mPendingState.isPresent()) { // If the change in the supported states didn't result in a change of the pending // state commitPendingState() will never be called and the callbacks will never be @@ -361,6 +381,15 @@ public final class DeviceStateManagerService extends SystemService { } } + @GuardedBy("mLock") + private void setRearDisplayStateLocked() { + int rearDisplayIdentifier = getContext().getResources().getInteger( + R.integer.config_deviceStateRearDisplay); + if (rearDisplayIdentifier != INVALID_DEVICE_STATE) { + mRearDisplayState = mDeviceStates.get(rearDisplayIdentifier); + } + } + /** * Returns {@code true} if the provided state is supported. Requires that * {@link #mDeviceStates} is sorted prior to calling. @@ -398,6 +427,10 @@ public final class DeviceStateManagerService extends SystemService { // Base state hasn't changed. Nothing to do. return; } + // There is a pending rear display request, so we check if the overlay should be closed + if (mRearDisplayPendingOverrideRequest != null) { + handleRearDisplayBaseStateChangedLocked(identifier); + } mBaseState = Optional.of(baseState); if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) { @@ -663,7 +696,7 @@ public final class DeviceStateManagerService extends SystemService { } private void requestStateInternal(int state, int flags, int callingPid, - @NonNull IBinder token) { + @NonNull IBinder token, boolean hasControlDeviceStatePermission) { synchronized (mLock) { final ProcessRecord processRecord = mProcessRecords.get(callingPid); if (processRecord == null) { @@ -685,10 +718,34 @@ public final class DeviceStateManagerService extends SystemService { OverrideRequest request = new OverrideRequest(token, callingPid, state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); - mOverrideRequestController.addRequest(request); + + // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay + if (!hasControlDeviceStatePermission && mRearDisplayState != null + && state == mRearDisplayState.getIdentifier()) { + showRearDisplayEducationalOverlayLocked(request); + } else { + mOverrideRequestController.addRequest(request); + } } } + /** + * If we get a request to enter rear display mode, we need to display an educational + * overlay to let the user know what will happen. This creates the pending request and then + * launches the {@link RearDisplayEducationActivity} + */ + @GuardedBy("mLock") + private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) { + mRearDisplayPendingOverrideRequest = request; + + Intent intent = new Intent(ACTION_SHOW_REAR_DISPLAY_OVERLAY); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRA_ORIGINAL_DEVICE_BASE_STATE, mBaseState.get().getIdentifier()); + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + getUiContext().startActivity(intent, options.toBundle()); + } + private void cancelStateRequestInternal(int callingPid) { synchronized (mLock) { final ProcessRecord processRecord = mProcessRecords.get(callingPid); @@ -738,6 +795,27 @@ public final class DeviceStateManagerService extends SystemService { } } + /** + * Adds the rear display state request to the {@link OverrideRequestController} if the + * educational overlay was closed in a way that should enable the feature, and cancels the + * request if it was dismissed in a way that should cancel the feature. + */ + private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) { + if (mRearDisplayPendingOverrideRequest != null) { + synchronized (mLock) { + if (shouldCancelRequest) { + ProcessRecord processRecord = mProcessRecords.get( + mRearDisplayPendingOverrideRequest.getPid()); + processRecord.notifyRequestCanceledAsync( + mRearDisplayPendingOverrideRequest.getToken()); + } else { + mOverrideRequestController.addRequest(mRearDisplayPendingOverrideRequest); + } + mRearDisplayPendingOverrideRequest = null; + } + } + } + private void dumpInternal(PrintWriter pw) { pw.println("DEVICE STATE MANAGER (dumpsys device_state)"); @@ -823,6 +901,16 @@ public final class DeviceStateManagerService extends SystemService { } } + private Set<Integer> readFoldedStates() { + Set<Integer> foldedStates = new HashSet(); + int[] mFoldedStatesArray = getContext().getResources().getIntArray( + com.android.internal.R.array.config_foldedDeviceStates); + for (int i = 0; i < mFoldedStatesArray.length; i++) { + foldedStates.add(mFoldedStatesArray[i]); + } + return foldedStates; + } + @GuardedBy("mLock") private boolean isValidState(int state) { for (int i = 0; i < mDeviceStates.size(); i++) { @@ -833,6 +921,28 @@ public final class DeviceStateManagerService extends SystemService { return false; } + /** + * If the device is being opened, in response to the rear display educational overlay, we should + * dismiss the overlay and enter the mode. + */ + @GuardedBy("mLock") + private void handleRearDisplayBaseStateChangedLocked(int newBaseState) { + if (isDeviceOpeningLocked(newBaseState)) { + onStateRequestOverlayDismissedInternal(false); + } + } + + /** + * Determines if the device is being opened and if we are going from a folded state to a + * non-folded state. + */ + @GuardedBy("mLock") + private boolean isDeviceOpeningLocked(int newBaseState) { + return mBaseState.filter( + deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier()) + && !mFoldedDeviceStates.contains(newBaseState)).isPresent(); + } + private final class DeviceStateProviderListener implements DeviceStateProvider.Listener { @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState; @@ -850,6 +960,7 @@ public final class DeviceStateManagerService extends SystemService { if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) { throw new IllegalArgumentException("Invalid identifier: " + identifier); } + mCurrentBaseState = identifier; setBaseState(identifier); } @@ -977,9 +1088,12 @@ public final class DeviceStateManagerService extends SystemService { throw new IllegalArgumentException("Request token must not be null."); } + boolean hasControlStatePermission = getContext().checkCallingOrSelfPermission( + CONTROL_DEVICE_STATE) == PERMISSION_GRANTED; + final long callingIdentity = Binder.clearCallingIdentity(); try { - requestStateInternal(state, flags, callingPid, token); + requestStateInternal(state, flags, callingPid, token, hasControlStatePermission); } finally { Binder.restoreCallingIdentity(callingIdentity); } @@ -1034,6 +1148,21 @@ public final class DeviceStateManagerService extends SystemService { } @Override // Binder call + public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) { + + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "CONTROL_DEVICE_STATE permission required to control the state request " + + "overlay"); + + final long callingIdentity = Binder.clearCallingIdentity(); + try { + onStateRequestOverlayDismissedInternal(shouldCancelRequest); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override // Binder call public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result) { new DeviceStateManagerShellCommand(DeviceStateManagerService.this) diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 300b5895ca98..990569cb04c2 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -68,6 +68,7 @@ import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.DisplayBrightnessController; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; import com.android.server.display.utils.SensorUtils; @@ -209,9 +210,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // True if auto-brightness should be used. private boolean mUseSoftwareAutoBrightnessConfig; - // True if should use light sensor to automatically determine doze screen brightness. - private final boolean mAllowAutoBrightnessWhileDozingConfig; - // Whether or not the color fade on screen on / off is enabled. private final boolean mColorFadeEnabled; @@ -348,6 +346,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private final BrightnessEvent mLastBrightnessEvent; private final BrightnessEvent mTempBrightnessEvent; + private final DisplayBrightnessController mDisplayBrightnessController; + // Keeps a record of brightness changes for dumpsys. private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer; @@ -499,9 +499,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness( pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR)); - mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean( - R.bool.config_allowAutoBrightnessWhileDozing); - loadBrightnessRampRates(); mSkipScreenOnBrightnessRamp = resources.getBoolean( R.bool.config_skipScreenOnBrightnessRamp); @@ -565,6 +562,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mBrightnessBucketsInDozeConfig = resources.getBoolean( R.bool.config_displayBrightnessBucketsInDoze); + mDisplayBrightnessController = + new DisplayBrightnessController(context, null, mDisplayId); mCurrentScreenBrightnessSetting = getScreenBrightnessSetting(); mScreenBrightnessForVr = getScreenBrightnessForVrSetting(); mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); @@ -1113,7 +1112,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal final int previousPolicy; boolean mustInitialize = false; int brightnessAdjustmentFlags = 0; - mBrightnessReasonTemp.set(null); mTempBrightnessEvent.reset(); synchronized (mLock) { if (mStopped) { @@ -1149,7 +1147,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // We might override this below based on other factors. // Initialise brightness as invalid. int state; - float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT; boolean performScreenOffTransition = false; switch (mPowerRequest.policy) { case DisplayPowerRequest.POLICY_OFF: @@ -1162,10 +1159,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } else { state = Display.STATE_DOZE; } - if (!mAllowAutoBrightnessWhileDozingConfig) { - brightnessState = mPowerRequest.dozeScreenBrightness; - mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE); - } break; case DisplayPowerRequest.POLICY_VR: state = Display.STATE_VR; @@ -1198,10 +1191,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal animateScreenStateChange(state, performScreenOffTransition); state = mPowerState.getScreenState(); - if (state == Display.STATE_OFF) { - brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT; - mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF); - } + DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController + .updateBrightness(mPowerRequest, state); + float brightnessState = displayBrightnessState.getBrightness(); + mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason()); // Always use the VR brightness when in the VR state. if (state == Display.STATE_VR) { @@ -1219,7 +1212,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } final boolean autoBrightnessEnabledInDoze = - mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state); + mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig() + && Display.isDozeState(state); final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) && Float.isNaN(brightnessState) @@ -2277,8 +2271,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum); pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault); pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig); - pw.println(" mAllowAutoBrightnessWhileDozingConfig=" - + mAllowAutoBrightnessWhileDozingConfig); pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp); pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig); pw.println(" mColorFadeEnabled=" + mColorFadeEnabled); @@ -2378,6 +2370,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mWakelockController.dumpLocal(pw); } + pw.println(); + if (mDisplayBrightnessController != null) { + mDisplayBrightnessController.dump(pw); + } + if (mDisplayPowerProximityStateController != null) { mDisplayPowerProximityStateController.dumpLocal(pw); } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 2c2075d33bb9..5a714f59485c 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -209,7 +209,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private int mUserPreferredModeId = INVALID_MODE_ID; // This is used only for the purpose of testing, to verify if the mode was correct when the // device started or booted. - private int mActiveDisplayModeAtStartId = INVALID_MODE_ID; + private int mActiveSfDisplayModeAtStartId = INVALID_MODE_ID; private Display.Mode mUserPreferredMode; private int mActiveModeId = INVALID_MODE_ID; private boolean mDisplayModeSpecsInvalid; @@ -241,7 +241,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mSidekickInternal = LocalServices.getService(SidekickInternal.class); mBacklightAdapter = new BacklightAdapter(displayToken, isFirstDisplay, mSurfaceControlProxy); - mActiveDisplayModeAtStartId = dynamicInfo.activeDisplayModeId; + mActiveSfDisplayModeAtStartId = dynamicInfo.activeDisplayModeId; } @Override @@ -255,7 +255,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { */ @Override public Display.Mode getActiveDisplayModeAtStartLocked() { - return findMode(mActiveDisplayModeAtStartId); + return findMode(findMatchingModeIdLocked(mActiveSfDisplayModeAtStartId)); } /** diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java new file mode 100644 index 000000000000..fe4c101eed72 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -0,0 +1,96 @@ +/* + * 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.brightness; + +import android.content.Context; +import android.hardware.display.DisplayManagerInternal; +import android.util.IndentingPrintWriter; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; + +import java.io.PrintWriter; + +/** + * Deploys different DozeBrightnessStrategy to choose the current brightness for a specified + * display. Applies the chosen brightness. + */ +public final class DisplayBrightnessController { + private final int mDisplayId; + // Selects an appropriate strategy based on the request provided by the clients. + private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector; + + /** + * The constructor of DisplayBrightnessController. + */ + public DisplayBrightnessController(Context context, Injector injector, int displayId) { + if (injector == null) { + injector = new Injector(); + } + mDisplayId = displayId; + mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context, + displayId); + } + + /** + * Updates the display brightness. This delegates the responsibility of selecting an appropriate + * strategy to DisplayBrightnessStrategySelector, which is then applied to evaluate the + * DisplayBrightnessState. In the future, + * 1. This will account for clamping the brightness if needed. + * 2. This will notify the system about the updated brightness + * + * @param displayPowerRequest The request to update the brightness + * @param targetDisplayState The target display state of the system + */ + public DisplayBrightnessState updateBrightness( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, + int targetDisplayState) { + DisplayBrightnessStrategy displayBrightnessStrategy = + mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, + targetDisplayState); + return displayBrightnessStrategy.updateBrightness(displayPowerRequest); + } + + /** + * Returns a boolean flag indicating if the light sensor is to be used to decide the screen + * brightness when dozing + */ + public boolean isAllowAutoBrightnessWhileDozingConfig() { + return mDisplayBrightnessStrategySelector.isAllowAutoBrightnessWhileDozingConfig(); + } + + /** + * Used to dump the state. + * + * @param writer The PrintWriter used to dump the state. + */ + public void dump(PrintWriter writer) { + writer.println(); + writer.println("DisplayBrightnessController:"); + IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " "); + mDisplayBrightnessStrategySelector.dump(ipw); + } + + @VisibleForTesting + static class Injector { + DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(Context context, + int displayId) { + return new DisplayBrightnessStrategySelector(context, /* injector= */ null, displayId); + } + } +} diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java new file mode 100644 index 000000000000..88707f0e0f88 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -0,0 +1,145 @@ +/* + * 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.brightness; + +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.display.DisplayManagerInternal; +import android.util.Slog; +import android.view.Display; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; +import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; +import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; +import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy; + +import java.io.PrintWriter; + +/** + * This maintains the logic needed to decide the eligible display brightness strategy. + */ +public class DisplayBrightnessStrategySelector { + private static final String TAG = "DisplayBrightnessStrategySelector"; + // True if light sensor is to be used to automatically determine doze screen brightness. + private final boolean mAllowAutoBrightnessWhileDozingConfig; + + // The brightness strategy used to manage the brightness state when the display is dozing. + private final DozeBrightnessStrategy mDozeBrightnessStrategy; + // The brightness strategy used to manage the brightness state when the display is in + // screen off state. + private final ScreenOffBrightnessStrategy mScreenOffBrightnessStrategy; + // The brightness strategy used to manage the brightness state when the request state is + // invalid. + private final InvalidBrightnessStrategy mInvalidBrightnessStrategy; + + // We take note of the old brightness strategy so that we can know when the strategy changes. + private String mOldBrightnessStrategyName; + + private final int mDisplayId; + + /** + * The constructor of DozeBrightnessStrategy. + */ + public DisplayBrightnessStrategySelector(Context context, Injector injector, int displayId) { + if (injector == null) { + injector = new Injector(); + } + mDisplayId = displayId; + mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy(); + mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy(); + mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy(); + mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean( + R.bool.config_allowAutoBrightnessWhileDozing); + mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName(); + } + + /** + * Selects the appropriate DisplayBrightnessStrategy based on the request and the display state + * to which the display is transitioning + */ + @NonNull + public DisplayBrightnessStrategy selectStrategy( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, + int targetDisplayState) { + DisplayBrightnessStrategy displayBrightnessStrategy = mInvalidBrightnessStrategy; + if (targetDisplayState == Display.STATE_OFF) { + displayBrightnessStrategy = mScreenOffBrightnessStrategy; + } else if (shouldUseDozeBrightnessStrategy(displayPowerRequest)) { + displayBrightnessStrategy = mDozeBrightnessStrategy; + } + + if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) { + Slog.i(TAG, + "Changing the DisplayBrightnessStrategy from " + mOldBrightnessStrategyName + + " to" + displayBrightnessStrategy.getName() + " for display " + + mDisplayId); + mOldBrightnessStrategyName = displayBrightnessStrategy.getName(); + } + return displayBrightnessStrategy; + } + + /** + * Returns a boolean flag indicating if the light sensor is to be used to decide the screen + * brightness when dozing + */ + public boolean isAllowAutoBrightnessWhileDozingConfig() { + return mAllowAutoBrightnessWhileDozingConfig; + } + + /** + * Dumps the state of this class. + */ + public void dump(PrintWriter writer) { + writer.println(); + writer.println("DisplayBrightnessStrategySelector:"); + writer.println( + " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig); + } + + /** + * Validates if the conditions are met to qualify for the DozeBrightnessStrategy. + */ + private boolean shouldUseDozeBrightnessStrategy( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { + // We are not checking the targetDisplayState, but rather relying on the policy because + // a user can define a different display state(displayPowerRequest.dozeScreenState) too + // in the request with the Doze policy + if (displayPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE) { + if (!mAllowAutoBrightnessWhileDozingConfig) { + return true; + } + } + return false; + } + + @VisibleForTesting + static class Injector { + ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() { + return new ScreenOffBrightnessStrategy(); + } + + DozeBrightnessStrategy getDozeBrightnessStrategy() { + return new DozeBrightnessStrategy(); + } + + InvalidBrightnessStrategy getInvalidBrightnessStrategy() { + return new InvalidBrightnessStrategy(); + } + } +} diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java deleted file mode 100644 index 3be5933cd3f1..000000000000 --- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessModeStrategy.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.display.brightness.strategy; - -import android.hardware.display.DisplayManagerInternal; - -import com.android.server.display.DisplayBrightnessState; - -import java.io.PrintWriter; - -/** - * An interface to define the general skeleton of how a BrightnessModeStrategy should look like - * This is responsible for deciding the DisplayBrightnessState that the display should change to, - * not taking into account clamping that might be needed - */ -public interface DisplayBrightnessModeStrategy { - /** - * Decides the DisplayBrightnessState that the system should change to. - * - * @param displayPowerRequest The request to evaluate the updated brightness - * @param displayState The target displayState to which the system should - * change to after processing the request - * @param displayBrightnessStateBuilder The DisplayBrightnessStateBuilder, consisting of - * DisplayBrightnessState that have been constructed so far - */ - DisplayBrightnessState.Builder updateBrightness( - DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int displayState, - DisplayBrightnessState.Builder displayBrightnessStateBuilder); - - /** - * Used to dump the state. - * - * @param writer The PrintWriter used to dump the state. - */ - void dump(PrintWriter writer); -} diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java new file mode 100644 index 000000000000..27d04fd7f743 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java @@ -0,0 +1,43 @@ +/* + * 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.brightness.strategy; + +import android.annotation.NonNull; +import android.hardware.display.DisplayManagerInternal; + +import com.android.server.display.DisplayBrightnessState; + +/** + * Decides the DisplayBrighntessState that the display should change to based on strategy-specific + * logic within each implementation. Clamping should be done outside of DisplayBrightnessStrategy if + * not an integral part of the strategy. + */ +public interface DisplayBrightnessStrategy { + /** + * Decides the DisplayBrightnessState that the system should change to. + * + * @param displayPowerRequest The request to evaluate the updated brightness + */ + DisplayBrightnessState updateBrightness( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest); + + /** + * Returns the name of the Strategy + */ + @NonNull + String getName(); +} diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java new file mode 100644 index 000000000000..c8b2c8339821 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java @@ -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.server.display.brightness.strategy; + +import android.hardware.display.DisplayManagerInternal; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +/** + * Manages the brightness of the display when the system is in the doze state. + */ +public class DozeBrightnessStrategy implements DisplayBrightnessStrategy { + + @Override + public DisplayBrightnessState updateBrightness( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { + // Todo(brup): Introduce a validator class and add validations before setting the brightness + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_DOZE); + return new DisplayBrightnessState.Builder() + .setBrightness(displayPowerRequest.dozeScreenBrightness) + .setSdrBrightness(displayPowerRequest.dozeScreenBrightness) + .setBrightnessReason(brightnessReason) + .build(); + } + + @Override + public String getName() { + return "DozeBrightnessStrategy"; + } + +} diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java new file mode 100644 index 000000000000..f6ddf4f870d2 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java @@ -0,0 +1,45 @@ +/* + * 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.brightness.strategy; + +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +/** + * Manages the brightness of the display when the system is in the invalid state. + */ +public class InvalidBrightnessStrategy implements DisplayBrightnessStrategy { + @Override + public DisplayBrightnessState updateBrightness( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.set(null); + return new DisplayBrightnessState.Builder() + .setBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT) + .setSdrBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT) + .setBrightnessReason(brightnessReason) + .build(); + } + + @Override + public String getName() { + return "InvalidBrightnessStrategy"; + } +} diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java new file mode 100644 index 000000000000..41385138607f --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java @@ -0,0 +1,46 @@ +/* + * 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.brightness.strategy; + +import android.hardware.display.DisplayManagerInternal; +import android.os.PowerManager; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +/** + * Manages the brightness of the display when the system is in the ScreenOff state. + */ +public class ScreenOffBrightnessStrategy implements DisplayBrightnessStrategy { + @Override + public DisplayBrightnessState updateBrightness( + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { + // Todo(brup): Introduce a validator class and add validations before setting the brightness + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF); + return new DisplayBrightnessState.Builder() + .setBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT) + .setSdrBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT) + .setBrightnessReason(brightnessReason) + .build(); + } + + @Override + public String getName() { + return "ScreenOffBrightnessStrategy"; + } +} diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 4a0ba2221ae7..ea096299cf48 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -81,6 +81,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; /** * Service api for managing dreams. @@ -120,6 +121,10 @@ public final class DreamManagerService extends SystemService { private final boolean mDreamsEnabledByDefaultConfig; private final boolean mDreamsActivatedOnChargeByDefault; private final boolean mDreamsActivatedOnDockByDefault; + private final boolean mKeepDreamingWhenUndockedDefault; + + private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener> + mDreamManagerStateListeners = new CopyOnWriteArrayList<>(); @GuardedBy("mLock") private DreamRecord mCurrentDream; @@ -226,6 +231,8 @@ public final class DreamManagerService extends SystemService { mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); mSettingsObserver = new SettingsObserver(mHandler); + mKeepDreamingWhenUndockedDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_keepDreamingWhenUndocking); } @Override @@ -294,12 +301,12 @@ public final class DreamManagerService extends SystemService { pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled); pw.println("mDreamsOnlyEnabledForDockUser=" + mDreamsOnlyEnabledForDockUser); pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting); - pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled); pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault); pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault); pw.println("mIsDocked=" + mIsDocked); pw.println("mIsCharging=" + mIsCharging); pw.println("mWhenToDream=" + mWhenToDream); + pw.println("mKeepDreamingWhenUndockedDefault=" + mKeepDreamingWhenUndockedDefault); pw.println("getDozeComponent()=" + getDozeComponent()); pw.println(); @@ -328,7 +335,16 @@ public final class DreamManagerService extends SystemService { } } - /** Whether a real dream is occurring. */ + private void reportKeepDreamingWhenUndockedChanged(boolean keepDreaming) { + mHandler.post(() -> { + for (DreamManagerInternal.DreamManagerStateListener listener + : mDreamManagerStateListeners) { + listener.onKeepDreamingWhenUndockedChanged(keepDreaming); + } + }); + } + + /** Whether a real dream is occurring. */ private boolean isDreamingInternal() { synchronized (mLock) { return mCurrentDream != null && !mCurrentDream.isPreview @@ -571,6 +587,7 @@ public final class DreamManagerService extends SystemService { } mSystemDreamComponent = componentName; + reportKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked()); // Switch dream if currently dreaming and not dozing. if (isDreamingInternal() && !isDozingInternal()) { @@ -580,6 +597,10 @@ public final class DreamManagerService extends SystemService { } } + private boolean shouldKeepDreamingWhenUndocked() { + return mKeepDreamingWhenUndockedDefault && mSystemDreamComponent == null; + } + private ComponentName getDefaultDreamComponentForUser(int userId) { String name = Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, @@ -1012,6 +1033,18 @@ public final class DreamManagerService extends SystemService { public void requestDream() { requestDreamInternal(); } + + @Override + public void registerDreamManagerStateListener(DreamManagerStateListener listener) { + mDreamManagerStateListeners.add(listener); + // Initialize the listener's state. + listener.onKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked()); + } + + @Override + public void unregisterDreamManagerStateListener(DreamManagerStateListener listener) { + mDreamManagerStateListeners.remove(listener); + } } private static final class DreamRecord { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 9bce471fd0cb..8a22ab968165 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -837,7 +837,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { void enableAudioReturnChannel(boolean enabled) { assertRunOnServiceThread(); HdmiDeviceInfo avr = getAvrDeviceInfo(); - if (avr != null) { + if (avr != null && avr.getPortId() != Constants.INVALID_PORT_ID) { mService.enableAudioReturnChannel(avr.getPortId(), enabled); } } @@ -1336,19 +1336,31 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @ServiceThreadOnly + private void forceDisableArcOnAllPins() { + List<HdmiPortInfo> ports = mService.getPortInfo(); + for (HdmiPortInfo port : ports) { + if (isArcFeatureEnabled(port.getId())) { + mService.enableAudioReturnChannel(port.getId(), false); + } + } + } + + @ServiceThreadOnly private void disableArcIfExist() { assertRunOnServiceThread(); HdmiDeviceInfo avr = getAvrDeviceInfo(); if (avr == null) { return; } - disableArc(); // Seq #44. removeAllRunningArcAction(); if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) { addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); } + + // Disable ARC Pin earlier, prevent the case where AVR doesn't send <Terminate ARC> in time + forceDisableArcOnAllPins(); } @ServiceThreadOnly diff --git a/services/core/java/com/android/server/locales/OWNERS b/services/core/java/com/android/server/locales/OWNERS index 4d93bff6f7d3..e1e946b1c51d 100644 --- a/services/core/java/com/android/server/locales/OWNERS +++ b/services/core/java/com/android/server/locales/OWNERS @@ -2,3 +2,6 @@ roosa@google.com pratyushmore@google.com goldmanj@google.com ankitavyas@google.com +allenwtsu@google.com +calvinpan@google.com +joshhou@google.com diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index 12324bff0c7a..e6fd7ec06cdc 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -261,9 +261,11 @@ public final class PermissionHelper { private boolean packageRequestsNotificationPermission(String packageName, @UserIdInt int userId) { try { - String[] permissions = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, - userId).requestedPermissions; - return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION); + PackageInfo pi = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS, userId); + if (pi != null) { + String[] permissions = pi.requestedPermissions; + return ArrayUtils.contains(permissions, NOTIFICATION_PERMISSION); + } } catch (RemoteException e) { Slog.e(TAG, "Could not reach system server", e); } diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index c97711b3aa80..5b837f1daa6f 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -81,11 +81,8 @@ import com.android.server.utils.Watcher; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Objects; -import java.util.Set; /** * Implementation of the methods that update the internal structures of AppsFilter. Because of the @@ -113,7 +110,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, */ @GuardedBy("mQueryableViaUsesPermissionLock") @NonNull - private HashMap<String, Set<Integer>> mPermissionToUids; + private final ArrayMap<String, ArraySet<Integer>> mPermissionToUids; /** * A cache that maps parsed {@link android.R.styleable#AndroidManifestUsesPermission @@ -123,7 +120,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, */ @GuardedBy("mQueryableViaUsesPermissionLock") @NonNull - private HashMap<String, Set<Integer>> mUsesPermissionToUids; + private final ArrayMap<String, ArraySet<Integer>> mUsesPermissionToUids; /** * Ensures an observer is in the list, exactly once. The observer cannot be null. The @@ -225,8 +222,8 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, mProtectedBroadcasts = new WatchedArraySet<>(); mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>( mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts"); - mPermissionToUids = new HashMap<>(); - mUsesPermissionToUids = new HashMap<>(); + mPermissionToUids = new ArrayMap<>(); + mUsesPermissionToUids = new ArrayMap<>(); mSnapshot = new SnapshotCache<AppsFilterSnapshot>(this, this) { @Override @@ -609,7 +606,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, // Lookup in the mPermissionToUids cache if installed packages have // defined this permission. if (mPermissionToUids.containsKey(usesPermissionName)) { - for (int targetAppId : mPermissionToUids.get(usesPermissionName)) { + final ArraySet<Integer> permissionDefiners = + mPermissionToUids.get(usesPermissionName); + for (int j = 0; j < permissionDefiners.size(); j++) { + final int targetAppId = permissionDefiners.valueAt(j); if (targetAppId != newPkgSetting.getAppId()) { mQueryableViaUsesPermission.add(newPkgSetting.getAppId(), targetAppId); @@ -619,7 +619,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, // Record in mUsesPermissionToUids that a permission was requested // by a new package if (!mUsesPermissionToUids.containsKey(usesPermissionName)) { - mUsesPermissionToUids.put(usesPermissionName, new HashSet<>()); + mUsesPermissionToUids.put(usesPermissionName, new ArraySet<>()); } mUsesPermissionToUids.get(usesPermissionName).add(newPkgSetting.getAppId()); } @@ -633,7 +633,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, // Lookup in the mUsesPermissionToUids cache if installed packages have // requested this permission. if (mUsesPermissionToUids.containsKey(permissionName)) { - for (int queryingAppId : mUsesPermissionToUids.get(permissionName)) { + final ArraySet<Integer> permissionUsers = mUsesPermissionToUids.get( + permissionName); + for (int j = 0; j < permissionUsers.size(); j++) { + final int queryingAppId = permissionUsers.valueAt(j); if (queryingAppId != newPkgSetting.getAppId()) { mQueryableViaUsesPermission.add(queryingAppId, newPkgSetting.getAppId()); @@ -642,7 +645,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, } // Record in mPermissionToUids that a permission was defined by a new package if (!mPermissionToUids.containsKey(permissionName)) { - mPermissionToUids.put(permissionName, new HashSet<>()); + mPermissionToUids.put(permissionName, new ArraySet<>()); } mPermissionToUids.get(permissionName).add(newPkgSetting.getAppId()); } diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 01a8bd0a4225..4e5a6f9fa2b5 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED; import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN; import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; @@ -114,6 +115,8 @@ final class InstallRequest { @Nullable private final PackageMetrics mPackageMetrics; + private final int mSessionId; + private final int mRequireUserAction; // New install InstallRequest(InstallingSession params) { @@ -128,6 +131,8 @@ final class InstallRequest { params.mDataLoaderType, params.mPackageSource); mPackageMetrics = new PackageMetrics(this); mIsInstallInherit = params.mIsInherit; + mSessionId = params.mSessionId; + mRequireUserAction = params.mRequireUserAction; } // Install existing package as user @@ -141,6 +146,8 @@ final class InstallRequest { mPostInstallRunnable = runnable; mPackageMetrics = new PackageMetrics(this); mIsInstallForUsers = true; + mSessionId = -1; + mRequireUserAction = USER_ACTION_UNSPECIFIED; } // addForInit @@ -158,6 +165,8 @@ final class InstallRequest { mScanFlags = scanFlags; mScanResult = scanResult; mPackageMetrics = null; // No logging from this code path + mSessionId = -1; + mRequireUserAction = USER_ACTION_UNSPECIFIED; } @Nullable @@ -565,6 +574,14 @@ final class InstallRequest { } } + public int getSessionId() { + return mSessionId; + } + + public int getRequireUserAction() { + return mRequireUserAction; + } + public void setScanFlags(int scanFlags) { mScanFlags = scanFlags; } diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index e4a0a3ab8dfa..d8494dbaa827 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING; +import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; import static android.content.pm.PackageManager.INSTALL_STAGED; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; @@ -95,7 +96,10 @@ class InstallingSession { final InstallPackageHelper mInstallPackageHelper; final RemovePackageHelper mRemovePackageHelper; final boolean mIsInherit; + final int mSessionId; + final int mRequireUserAction; + // For move install InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer, int installFlags, InstallSource installSource, String volumeUuid, UserHandle user, String packageAbiOverride, int packageSource, @@ -124,9 +128,11 @@ class InstallingSession { mPackageSource = packageSource; mPackageLite = packageLite; mIsInherit = false; + mSessionId = -1; + mRequireUserAction = USER_ACTION_UNSPECIFIED; } - InstallingSession(File stagedDir, IPackageInstallObserver2 observer, + InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, InstallSource installSource, UserHandle user, SigningDetails signingDetails, int installerUid, PackageLite packageLite, PackageManagerService pm) { @@ -155,6 +161,8 @@ class InstallingSession { mPackageSource = sessionParams.packageSource; mPackageLite = packageLite; mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING; + mSessionId = sessionId; + mRequireUserAction = sessionParams.requireUserAction; } @Override diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 3cc19071a213..67b948f72d84 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -380,14 +380,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private boolean mStageDirInUse = false; - /** - * True if the installation is already in progress. This is used to prevent the caller - * from {@link #commit(IntentSender, boolean) committing} the session again while the - * installation is still in progress. - */ - @GuardedBy("mLock") - private boolean mInstallationInProgress = false; - /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */ @GuardedBy("mLock") private boolean mPermissionsManuallyAccepted = false; @@ -1700,14 +1692,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - synchronized (mLock) { - if (mInstallationInProgress) { - throw new IllegalStateException("Installation is already in progress. Don't " - + "commit session=" + sessionId + " again."); - } - mInstallationInProgress = true; - } - dispatchSessionSealed(); } @@ -2566,8 +2550,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } synchronized (mLock) { - return new InstallingSession(stageDir, localObserver, params, mInstallSource, user, - mSigningDetails, mInstallerUid, mPackageLite, mPm); + return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource, + user, mSigningDetails, mInstallerUid, mPackageLite, mPm); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8089af381a95..139c6ea28b7d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1724,8 +1724,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService } public PackageManagerService(PackageManagerServiceInjector injector, boolean factoryTest, - final String buildFingerprint, final boolean isEngBuild, final boolean isUserDebugBuild, - final int sdkVersion, final String incrementalVersion) { + final String partitionsFingerprint, final boolean isEngBuild, + final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) { mIsEngBuild = isEngBuild; mIsUserDebugBuild = isUserDebugBuild; mSdkVersion = sdkVersion; @@ -1971,10 +1971,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService final VersionInfo ver = mSettings.getInternalVersion(); mIsUpgrade = - !buildFingerprint.equals(ver.fingerprint); + !partitionsFingerprint.equals(ver.fingerprint); if (mIsUpgrade) { - PackageManagerServiceUtils.logCriticalInfo(Log.INFO, "Upgrading from " - + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT); + PackageManagerServiceUtils.logCriticalInfo(Log.INFO, + "Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to " + + PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")"); } mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper, @@ -2081,14 +2082,14 @@ public class PackageManagerService implements PackageSender, TestUtilityService + ((SystemClock.uptimeMillis() - startTime) / 1000f) + " seconds"); - // If the build fingerprint has changed since the last time we booted, + // If the partitions fingerprint has changed since the last time we booted, // we need to re-grant app permission to catch any new ones that // appear. This is really a hack, and means that apps can in some // cases get permissions that the user didn't initially explicitly // allow... it would be nice to have some better way to handle // this situation. if (mIsUpgrade) { - Slog.i(TAG, "Build fingerprint changed from " + ver.fingerprint + " to " + Slog.i(TAG, "Partitions fingerprint changed from " + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT + "; regranting permissions for internal storage"); } @@ -2120,6 +2121,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES); } } + ver.buildFingerprint = Build.FINGERPRINT; ver.fingerprint = PackagePartitions.FINGERPRINT; } diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java index cb87ff5467a8..0574f737a54b 100644 --- a/services/core/java/com/android/server/pm/PackageMetrics.java +++ b/services/core/java/com/android/server/pm/PackageMetrics.java @@ -92,7 +92,7 @@ final class PackageMetrics { final long apksSize = getApksSize(ps.getPath()); FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED, - 0 /* session_id */, + mInstallRequest.getSessionId() /* session_id */, success ? null : packageName /* not report package_name on success */, mInstallRequest.getUid() /* uid */, newUsers /* user_ids */, @@ -110,7 +110,7 @@ final class PackageMetrics { installerUid /* installer_package_uid */, -1 /* original_installer_package_uid */, mInstallRequest.getDataLoaderType() /* data_loader_type */, - 0 /* user_action_required_type */, + mInstallRequest.getRequireUserAction() /* user_action_required_type */, mInstallRequest.isInstantInstall() /* is_instant */, mInstallRequest.isInstallReplace() /* is_replace */, mInstallRequest.isInstallSystem() /* is_system */, diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 45c0d6ea6c11..e33cc9ff0983 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -353,6 +353,7 @@ public final class Settings implements Watchable, Snappable { private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme"; private static final String ATTR_PACKAGE_NAME = "packageName"; + private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint"; private static final String ATTR_FINGERPRINT = "fingerprint"; private static final String ATTR_VOLUME_UUID = "volumeUuid"; private static final String ATTR_SDK_VERSION = "sdkVersion"; @@ -432,7 +433,12 @@ public final class Settings implements Watchable, Snappable { int databaseVersion; /** - * Last known value of {@link Build#FINGERPRINT}. Used to determine when + * Last known value of {@link Build#FINGERPRINT}. Stored for debug purposes. + */ + String buildFingerprint; + + /** + * Last known value of {@link PackagePartitions#FINGERPRINT}. Used to determine when * an system update has occurred, meaning we need to clear code caches. */ String fingerprint; @@ -444,6 +450,7 @@ public final class Settings implements Watchable, Snappable { public void forceCurrent() { sdkVersion = Build.VERSION.SDK_INT; databaseVersion = CURRENT_DATABASE_VERSION; + buildFingerprint = Build.FINGERPRINT; fingerprint = PackagePartitions.FINGERPRINT; } } @@ -2495,6 +2502,8 @@ public final class Settings implements Watchable, Snappable { XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid); serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion); serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion); + XmlUtils.writeStringAttribute(serializer, ATTR_BUILD_FINGERPRINT, + ver.buildFingerprint); XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint); serializer.endTag(null, TAG_VERSION); } @@ -3105,6 +3114,8 @@ public final class Settings implements Watchable, Snappable { internal.sdkVersion = parser.getAttributeInt(null, "internal", 0); external.sdkVersion = parser.getAttributeInt(null, "external", 0); + internal.buildFingerprint = external.buildFingerprint = + XmlUtils.readStringAttribute(parser, "buildFingerprint"); internal.fingerprint = external.fingerprint = XmlUtils.readStringAttribute(parser, "fingerprint"); @@ -3136,6 +3147,8 @@ public final class Settings implements Watchable, Snappable { final VersionInfo ver = findOrCreateVersion(volumeUuid); ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION); ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION); + ver.buildFingerprint = XmlUtils.readStringAttribute(parser, + ATTR_BUILD_FINGERPRINT); ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT); } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) { mDomainVerificationManager.readSettings(computer, parser); @@ -4514,6 +4527,7 @@ public final class Settings implements Watchable, Snappable { pw.printPair("sdkVersion", ver.sdkVersion); pw.printPair("databaseVersion", ver.databaseVersion); pw.println(); + pw.printPair("buildFingerprint", ver.buildFingerprint); pw.printPair("fingerprint", ver.fingerprint); pw.println(); pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index fbfc84abf4fc..4f7c2bdf3057 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -208,7 +208,7 @@ public final class StorageEventHelper extends StorageEventListener { synchronized (mPm.mLock) { final boolean isUpgrade = !PackagePartitions.FINGERPRINT.equals(ver.fingerprint); if (isUpgrade) { - logCriticalInfo(Log.INFO, "Build fingerprint changed from " + ver.fingerprint + logCriticalInfo(Log.INFO, "Partitions fingerprint changed from " + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT + "; regranting permissions for " + volumeUuid); } diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 1420cbf3ae45..1027f4c03127 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -51,7 +51,7 @@ public abstract class UserManagerInternal { public static final int USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE = 2; public static final int USER_ASSIGNMENT_RESULT_FAILURE = -1; - private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT"; + private static final String PREFIX_USER_ASSIGNMENT_RESULT = "USER_ASSIGNMENT_RESULT_"; @IntDef(flag = false, prefix = {PREFIX_USER_ASSIGNMENT_RESULT}, value = { USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE, diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 9f84ab030cef..c5212a16c3ed 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1645,8 +1645,7 @@ public class UserManagerService extends IUserManager.Stub { return isProfileUnchecked(userId); } - // TODO(b/244644281): make it private once UserVisibilityMediator don't use it anymore - boolean isProfileUnchecked(@UserIdInt int userId) { + private boolean isProfileUnchecked(@UserIdInt int userId) { synchronized (mUsersLock) { UserInfo userInfo = getUserInfoLU(userId); return userInfo != null && userInfo.isProfile(); diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java index cbf7dfe77ca6..2cc7fca45426 100644 --- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java +++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java @@ -25,6 +25,7 @@ import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_S import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE; import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.os.UserHandle; @@ -36,26 +37,50 @@ import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import com.android.server.pm.UserManagerInternal.UserAssignmentResult; import com.android.server.utils.Slogf; import java.io.PrintWriter; -import java.util.LinkedHashMap; -import java.util.Map; /** * Class responsible for deciding whether a user is visible (or visible for a given display). * + * <p>Currently, it has 2 "modes" (set on constructor), which defines the class behavior (i.e, the + * logic that dictates the result of methods such as {@link #isUserVisible(int)} and + * {@link #isUserVisible(int, int)}): + * + * <ul> + * <li>default: this is the most common mode (used by phones, tablets, foldables, automotives with + * just cluster and driver displayes, etc...), where the logic is based solely on the current + * foreground user (and its started profiles) + * <li>{@code MUMD}: mode for "(concurrent) Multiple Users on Multiple Displays", which is used on + * automotives with passenger display. In this mode, users started in background on the secondary + * display are stored in map. + * </ul> + * * <p>This class is thread safe. */ -// TODO(b/244644281): improve javadoc (for example, explain all cases / modes) public final class UserVisibilityMediator implements Dumpable { private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE private static final String TAG = UserVisibilityMediator.class.getSimpleName(); + public static final int SECONDARY_DISPLAY_MAPPING_NEEDED = 1; + public static final int SECONDARY_DISPLAY_MAPPING_NOT_NEEDED = 2; + public static final int SECONDARY_DISPLAY_MAPPING_FAILED = -1; + + /** + * Whether a user / display assignment requires adding an entry to the + * {@code mUsersOnSecondaryDisplays} map. + */ + @IntDef(flag = false, prefix = {"SECONDARY_DISPLAY_MAPPING_"}, value = { + SECONDARY_DISPLAY_MAPPING_NEEDED, + SECONDARY_DISPLAY_MAPPING_NOT_NEEDED, + SECONDARY_DISPLAY_MAPPING_FAILED + }) + public @interface SecondaryDisplayMappingStatus {} + // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices @VisibleForTesting static final int INITIAL_CURRENT_USER_ID = USER_SYSTEM; @@ -68,9 +93,14 @@ public final class UserVisibilityMediator implements Dumpable { @GuardedBy("mLock") private int mCurrentUserId = INITIAL_CURRENT_USER_ID; + /** + * Map of background users started on secondary displays. + * + * <p>Only set when {@code mUsersOnSecondaryDisplaysEnabled} is {@code true}. + */ @Nullable @GuardedBy("mLock") - private final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray(); + private final SparseIntArray mUsersOnSecondaryDisplays; /** * Mapping from each started user to its profile group. @@ -85,171 +115,199 @@ public final class UserVisibilityMediator implements Dumpable { @VisibleForTesting UserVisibilityMediator(boolean usersOnSecondaryDisplaysEnabled) { mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled; + mUsersOnSecondaryDisplays = mUsersOnSecondaryDisplaysEnabled ? new SparseIntArray() : null; } /** * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, boolean, int)}. */ - public @UserAssignmentResult int startUser(@UserIdInt int userId, @UserIdInt int profileGroupId, - boolean foreground, int displayId) { - // TODO(b/244644281): this method need to perform 4 actions: + public @UserAssignmentResult int startUser(@UserIdInt int userId, + @UserIdInt int unResolvedProfileGroupId, boolean foreground, int displayId) { + // This method needs to perform 4 actions: // // 1. Check if the user can be started given the provided arguments // 2. If it can, decide whether it's visible or not (which is the return value) // 3. Update the current user / profiles state // 4. Update the users on secondary display state (if applicable) // - // Ideally, they should be done "atomically" (i.e, only changing state while holding the - // mLock), but the initial implementation is just calling the existing methods, as the - // focus is to change the UserController startUser() workflow (so it relies on this class - // for the logic above). - // - // The next CL will refactor it (and the unit tests) to achieve that atomicity. - int result = startOnly(userId, profileGroupId, foreground, displayId); - if (result != USER_ASSIGNMENT_RESULT_FAILURE) { - assignUserToDisplay(userId, profileGroupId, displayId); - } - return result; - } + // Notice that steps 3 and 4 should be done atomically (i.e., while holding mLock), so the + // previous steps are delegated to other methods (canAssignUserToDisplayLocked() and + // getUserVisibilityOnStartLocked() respectively). - /** - * @deprecated - see comment inside {@link #startUser(int, int, boolean, int)} - */ - @Deprecated - @VisibleForTesting - @UserAssignmentResult int startOnly(@UserIdInt int userId, - @UserIdInt int profileGroupId, boolean foreground, int displayId) { - int actualProfileGroupId = profileGroupId == NO_PROFILE_GROUP_ID + + int profileGroupId = unResolvedProfileGroupId == NO_PROFILE_GROUP_ID ? userId - : profileGroupId; + : unResolvedProfileGroupId; if (DBG) { Slogf.d(TAG, "startUser(%d, %d, %b, %d): actualProfileGroupId=%d", - userId, profileGroupId, foreground, displayId, actualProfileGroupId); - } - if (foreground && displayId != DEFAULT_DISPLAY) { - Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start foreground user on " - + "secondary display", userId, actualProfileGroupId, foreground, displayId); - return USER_ASSIGNMENT_RESULT_FAILURE; + userId, unResolvedProfileGroupId, foreground, displayId, profileGroupId); } - int visibility; + int result; synchronized (mLock) { - if (isProfile(userId, actualProfileGroupId)) { - if (displayId != DEFAULT_DISPLAY) { - Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user on " - + "secondary display", userId, actualProfileGroupId, foreground, - displayId); - return USER_ASSIGNMENT_RESULT_FAILURE; - } - if (foreground) { - Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in " - + "foreground", userId, actualProfileGroupId, foreground, displayId); - return USER_ASSIGNMENT_RESULT_FAILURE; - } else { - boolean isParentRunning = mStartedProfileGroupIds - .get(actualProfileGroupId) == actualProfileGroupId; - if (DBG) { - Slogf.d(TAG, "profile parent running: %b", isParentRunning); - } - visibility = isParentRunning - ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE - : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; - } - } else if (foreground) { + result = getUserVisibilityOnStartLocked(userId, profileGroupId, foreground, displayId); + if (DBG) { + Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)", + userAssignmentResultToString(result)); + } + if (result == USER_ASSIGNMENT_RESULT_FAILURE) { + return result; + } + + int mappingResult = canAssignUserToDisplayLocked(userId, profileGroupId, displayId); + if (mappingResult == SECONDARY_DISPLAY_MAPPING_FAILED) { + return USER_ASSIGNMENT_RESULT_FAILURE; + } + + // Set current user / profiles state + if (foreground) { mCurrentUserId = userId; - visibility = USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE; - } else { - visibility = USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; } if (DBG) { - Slogf.d(TAG, "adding user / profile mapping (%d -> %d) and returning %s", - userId, actualProfileGroupId, userAssignmentResultToString(visibility)); + Slogf.d(TAG, "adding user / profile mapping (%d -> %d)", userId, profileGroupId); + } + mStartedProfileGroupIds.put(userId, profileGroupId); + + // Set user / display state + switch (mappingResult) { + case SECONDARY_DISPLAY_MAPPING_NEEDED: + if (DBG) { + Slogf.d(TAG, "adding user / display mapping (%d -> %d)", userId, displayId); + } + mUsersOnSecondaryDisplays.put(userId, displayId); + break; + case SECONDARY_DISPLAY_MAPPING_NOT_NEEDED: + if (DBG) { + // Don't need to do set state because methods (such as isUserVisible()) + // already know that the current user (and their profiles) is assigned to + // the default display. + Slogf.d(TAG, "Don't need to update mUsersOnSecondaryDisplays"); + } + break; + default: + Slogf.wtf(TAG, "Invalid resut from canAssignUserToDisplayLocked: %d", + mappingResult); } - mStartedProfileGroupIds.put(userId, actualProfileGroupId); } - return visibility; - } - /** - * @deprecated - see comment inside {@link #startUser(int, int, boolean, int)} - */ - @Deprecated - @VisibleForTesting - void assignUserToDisplay(int userId, int profileGroupId, int displayId) { if (DBG) { - Slogf.d(TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplaysEnabled=%b", - userId, displayId, mUsersOnSecondaryDisplaysEnabled); + Slogf.d(TAG, "returning %s", userAssignmentResultToString(result)); } + return result; + } + + @GuardedBy("mLock") + @UserAssignmentResult + private int getUserVisibilityOnStartLocked(@UserIdInt int userId, + @UserIdInt int profileGroupId, boolean foreground, int displayId) { + if (displayId != DEFAULT_DISPLAY) { + if (foreground) { + Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start " + + "foreground user on secondary display", userId, profileGroupId, + foreground, displayId); + return USER_ASSIGNMENT_RESULT_FAILURE; + } + if (!mUsersOnSecondaryDisplaysEnabled) { + Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on " + + "device that doesn't support multiple users on multiple displays", + userId, profileGroupId, foreground, displayId); + return USER_ASSIGNMENT_RESULT_FAILURE; + } + } + + if (isProfile(userId, profileGroupId)) { + if (displayId != DEFAULT_DISPLAY) { + Slogf.w(TAG, "canStartUserLocked(%d, %d, %b, %d) failed: cannot start profile user " + + "on secondary display", userId, profileGroupId, foreground, + displayId); + return USER_ASSIGNMENT_RESULT_FAILURE; + } + if (foreground) { + Slogf.w(TAG, "startUser(%d, %d, %b, %d) failed: cannot start profile user in " + + "foreground", userId, profileGroupId, foreground, displayId); + return USER_ASSIGNMENT_RESULT_FAILURE; + } else { + boolean isParentVisibleOnDisplay = isUserVisible(profileGroupId, displayId); + if (DBG) { + Slogf.d(TAG, "parent visible on display: %b", isParentVisibleOnDisplay); + } + return isParentVisibleOnDisplay + ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE + : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; + } + } + + return foreground || displayId != DEFAULT_DISPLAY + ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE + : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; + } + + @GuardedBy("mLock") + @SecondaryDisplayMappingStatus + private int canAssignUserToDisplayLocked(@UserIdInt int userId, + @UserIdInt int profileGroupId, int displayId) { if (displayId == DEFAULT_DISPLAY && (!mUsersOnSecondaryDisplaysEnabled || !isProfile(userId, profileGroupId))) { // Don't need to do anything because methods (such as isUserVisible()) already - // know that the current user (and their profiles) is assigned to the default display. - // But on MUMD devices, it profiles are only supported in the default display, so it + // know that the current user (and its profiles) is assigned to the default display. + // But on MUMD devices, profiles are only supported in the default display, so it // cannot return yet as it needs to check if the parent is also assigned to the // DEFAULT_DISPLAY (this is done indirectly below when it checks that the profile parent // is the current user, as the current user is always assigned to the DEFAULT_DISPLAY). if (DBG) { - Slogf.d(TAG, "ignoring on default display"); + Slogf.d(TAG, "ignoring mapping for default display"); } - return; + return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED; } - if (!mUsersOnSecondaryDisplaysEnabled) { - throw new UnsupportedOperationException("assignUserToDisplay(" + userId + ", " - + displayId + ") called on device that doesn't support multiple " - + "users on multiple displays"); + if (userId == UserHandle.USER_SYSTEM) { + Slogf.w(TAG, "Cannot assign system user to secondary display (%d)", displayId); + return SECONDARY_DISPLAY_MAPPING_FAILED; + } + if (displayId == Display.INVALID_DISPLAY) { + Slogf.w(TAG, "Cannot assign to INVALID_DISPLAY (%d)", displayId); + return SECONDARY_DISPLAY_MAPPING_FAILED; + } + if (userId == mCurrentUserId) { + Slogf.w(TAG, "Cannot assign current user (%d) to other displays", userId); + return SECONDARY_DISPLAY_MAPPING_FAILED; } - - Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot assign system " - + "user to secondary display (%d)", displayId); - Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY, - "Cannot assign to INVALID_DISPLAY (%d)", displayId); - - int currentUserId = getCurrentUserId(); - Preconditions.checkArgument(userId != currentUserId, - "Cannot assign current user (%d) to other displays", currentUserId); if (isProfile(userId, profileGroupId)) { // Profile can only start in the same display as parent. And for simplicity, // that display must be the DEFAULT_DISPLAY. - Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY, - "Profile user can only be started in the default display"); - int parentUserId = getStartedProfileGroupId(userId); - Preconditions.checkArgument(parentUserId == currentUserId, - "Only profile of current user can be assigned to a display"); + if (displayId != Display.DEFAULT_DISPLAY) { + Slogf.w(TAG, "Profile user can only be started in the default display"); + return SECONDARY_DISPLAY_MAPPING_FAILED; + + } if (DBG) { - Slogf.d(TAG, "Ignoring profile user %d on default display", userId); + Slogf.d(TAG, "Don't need to map profile user %d to default display", userId); } - return; + return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED; } - synchronized (mLock) { - // Check if display is available - for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) { - int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i); - int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i); - if (DBG) { - Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d", - i, assignedUserId, assignedDisplayId); - } - if (displayId == assignedDisplayId) { - throw new IllegalStateException("Cannot assign user " + userId + " to " - + "display " + displayId + " because such display is already " - + "assigned to user " + assignedUserId); - } - if (userId == assignedUserId) { - throw new IllegalStateException("Cannot assign user " + userId + " to " - + "display " + displayId + " because such user is as already " - + "assigned to display " + assignedDisplayId); - } - } - + // Check if display is available + for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) { + int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i); + int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i); if (DBG) { - Slogf.d(TAG, "Adding full user %d -> display %d", userId, displayId); + Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d", + i, assignedUserId, assignedDisplayId); + } + if (displayId == assignedDisplayId) { + Slogf.w(TAG, "Cannot assign user %d to display %d because such display is already " + + "assigned to user %d", userId, displayId, assignedUserId); + return SECONDARY_DISPLAY_MAPPING_FAILED; + } + if (userId == assignedUserId) { + Slogf.w(TAG, "Cannot assign user %d to display %d because such user is as already " + + "assigned to display %d", userId, displayId, assignedUserId); + return SECONDARY_DISPLAY_MAPPING_FAILED; } - mUsersOnSecondaryDisplays.put(userId, displayId); } + return SECONDARY_DISPLAY_MAPPING_NEEDED; } /** @@ -311,12 +369,12 @@ public final class UserVisibilityMediator implements Dumpable { return isCurrentUserOrRunningProfileOfCurrentUser(userId); } - // TODO(b/244644281): temporary workaround to let WM use this API without breaking current + // TODO(b/256242848): temporary workaround to let WM use this API without breaking current // behavior - return true for current user / profile for any display (other than those // explicitly assigned to another users), otherwise they wouldn't be able to launch - // activities on other non-passenger displays, like cluster, display, or virtual displays). + // activities on other non-passenger displays, like cluster). // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which - // would be updated by DisplayManagerService when displays are created / initialized. + // would be updated by CarService to allow additional mappings. if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) { synchronized (mLock) { boolean assignedToUser = false; @@ -410,7 +468,7 @@ public final class UserVisibilityMediator implements Dumpable { ipw.print("Supports background users on secondary displays: "); ipw.println(mUsersOnSecondaryDisplaysEnabled); - if (mUsersOnSecondaryDisplaysEnabled) { + if (mUsersOnSecondaryDisplays != null) { dumpIntArray(ipw, mUsersOnSecondaryDisplays, "background user / secondary display", "u", "d"); } @@ -448,23 +506,13 @@ public final class UserVisibilityMediator implements Dumpable { dump(new IndentingPrintWriter(pw)); } - @VisibleForTesting - Map<Integer, Integer> getUsersOnSecondaryDisplays() { - Map<Integer, Integer> map; - synchronized (mLock) { - int size = mUsersOnSecondaryDisplays.size(); - map = new LinkedHashMap<>(size); - for (int i = 0; i < size; i++) { - map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i)); - } - } - Slogf.v(TAG, "getUsersOnSecondaryDisplays(): returning %s", map); - return map; + private static boolean isProfile(@UserIdInt int userId, @UserIdInt int profileGroupId) { + return profileGroupId != NO_PROFILE_GROUP_ID && profileGroupId != userId; } - // TODO(b/244644281): methods below are needed because some APIs use the current users (full and - // profiles) state to decide whether a user is visible or not. If we decide to always store that - // info into intermediate maps, we should remove them. + // NOTE: methods below are needed because some APIs use the current users (full and profiles) + // state to decide whether a user is visible or not. If we decide to always store that info into + // mUsersOnSecondaryDisplays, we should remove them. @VisibleForTesting @UserIdInt int getCurrentUserId() { @@ -487,10 +535,6 @@ public final class UserVisibilityMediator implements Dumpable { } } - private static boolean isProfile(@UserIdInt int userId, @UserIdInt int profileGroupId) { - return profileGroupId != NO_PROFILE_GROUP_ID && profileGroupId != userId; - } - @VisibleForTesting boolean isStartedUser(@UserIdInt int userId) { synchronized (mLock) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index ab223ef3cbeb..5ffbbdcfbb0b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -231,6 +231,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO); READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES); READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION); + READ_MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED); NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE); NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT); NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN); diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java index f8fcaff354ce..03d563d6ec4a 100644 --- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java +++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java @@ -96,6 +96,7 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, private static final String CONFIG_FILE_NAME = "device_state_configuration.xml"; private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS"; private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE"; + private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY"; /** Interface that allows reading the device state configuration. */ interface ReadableConfig { @@ -149,6 +150,8 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, case FLAG_APP_INACCESSIBLE: flags |= DeviceState.FLAG_APP_INACCESSIBLE; break; + case FLAG_EMULATED_ONLY: + flags |= DeviceState.FLAG_EMULATED_ONLY; default: Slog.w(TAG, "Parsed unknown flag with name: " + configFlagString); @@ -225,7 +228,13 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider, } final Conditions conditions = stateConditions.get(i); if (conditions == null) { - mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER); + // If this state has the FLAG_EMULATED_ONLY flag on it, it should never be triggered + // by a physical hardware change, and should always return false for it's conditions + if (deviceStates.get(i).hasFlag(DeviceState.FLAG_EMULATED_ONLY)) { + mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER); + } else { + mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER); + } continue; } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index cc84c85c7518..20235eb2c464 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -472,9 +472,6 @@ public final class PowerManagerService extends SystemService // True if the device should wake up when plugged or unplugged. private boolean mWakeUpWhenPluggedOrUnpluggedConfig; - // True if the device should keep dreaming when undocked. - private boolean mKeepDreamingWhenUndockingConfig; - // True if the device should wake up when plugged or unplugged in theater mode. private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig; @@ -681,6 +678,19 @@ public final class PowerManagerService extends SystemService // but the DreamService has not yet been told to start (it's an async process). private boolean mDozeStartInProgress; + // Whether to keep dreaming when the device is undocked. + private boolean mKeepDreamingWhenUndocked; + + private final class DreamManagerStateListener implements + DreamManagerInternal.DreamManagerStateListener { + @Override + public void onKeepDreamingWhenUndockedChanged(boolean keepDreaming) { + synchronized (mLock) { + mKeepDreamingWhenUndocked = keepDreaming; + } + } + } + private final class PowerGroupWakefulnessChangeListener implements PowerGroup.PowerGroupListener { @GuardedBy("mLock") @@ -1285,6 +1295,9 @@ public final class PowerManagerService extends SystemService new DisplayGroupPowerChangeListener(); mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener); + // This DreamManager method does not acquire a lock, so it should be safe to call. + mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener()); + mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager, mInjector.createSuspendBlocker( this, "PowerManagerService.WirelessChargerDetector"), @@ -1402,8 +1415,6 @@ public final class PowerManagerService extends SystemService com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay); mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean( com.android.internal.R.bool.config_unplugTurnsOnScreen); - mKeepDreamingWhenUndockingConfig = resources.getBoolean( - com.android.internal.R.bool.config_keepDreamingWhenUndocking); mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean( com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug); mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean( @@ -2518,7 +2529,7 @@ public final class PowerManagerService extends SystemService } // Don't wake when undocking while dreaming if configured not to. - if (mKeepDreamingWhenUndockingConfig + if (mKeepDreamingWhenUndocked && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING && wasPowered && !mIsPowered && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) { @@ -4472,8 +4483,7 @@ public final class PowerManagerService extends SystemService + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig); pw.println(" mTheaterModeEnabled=" + mTheaterModeEnabled); - pw.println(" mKeepDreamingWhenUndockingConfig=" - + mKeepDreamingWhenUndockingConfig); + pw.println(" mKeepDreamingWhenUndocked=" + mKeepDreamingWhenUndocked); pw.println(" mSuspendWhenScreenOffDueToProximityConfig=" + mSuspendWhenScreenOffDueToProximityConfig); pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 12efe0dc074f..e65ea53ac459 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -311,6 +311,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private SurfaceControl mOverlayLayer; + /** A surfaceControl specifically for accessibility overlays. */ + private SurfaceControl mA11yOverlayLayer; + /** * The direct child layer of the display to put all non-overlay windows. This is also used for * screen rotation animation so that there is a parent layer to put the animation leash. @@ -1269,12 +1272,21 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp transaction.reparent(mOverlayLayer, mSurfaceControl); } + if (mA11yOverlayLayer == null) { + mA11yOverlayLayer = + b.setName("Accessibility Overlays").setParent(mSurfaceControl).build(); + } else { + transaction.reparent(mA11yOverlayLayer, mSurfaceControl); + } + transaction .setLayer(mSurfaceControl, 0) .setLayerStack(mSurfaceControl, mDisplayId) .show(mSurfaceControl) .setLayer(mOverlayLayer, Integer.MAX_VALUE) - .show(mOverlayLayer); + .show(mOverlayLayer) + .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 1) + .show(mA11yOverlayLayer); } boolean isReady() { @@ -3250,6 +3262,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp setRemoteInsetsController(null); mWmService.mAnimator.removeDisplayLocked(mDisplayId); mOverlayLayer.release(); + mA11yOverlayLayer.release(); mWindowingLayer.release(); mInputMonitor.onDisplayRemoved(); mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this); @@ -5502,6 +5515,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mOverlayLayer; } + SurfaceControl getA11yOverlayLayer() { + return mA11yOverlayLayer; + } + SurfaceControl[] findRoundedCornerOverlays() { List<SurfaceControl> roundedCornerOverlays = new ArrayList<>(); for (WindowToken token : mTokenMap.values()) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 51d4bd220bc1..b290bec8c4e0 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1588,6 +1588,11 @@ class Task extends TaskFragment { removeChild(r, reason); }); } else { + // Finish or destroy apps from the bottom to ensure that all the other activity have + // been finished and the top task in another task gets resumed when a top activity is + // removed. Otherwise, shell transitions wouldn't run because there would be no event + // that sets the transition ready. + final boolean traverseTopToBottom = !mTransitionController.isShellTransitionsEnabled(); forAllActivities((r) -> { if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) { return; @@ -1601,7 +1606,7 @@ class Task extends TaskFragment { } else { r.destroyIfPossible(reason); } - }); + }, traverseTopToBottom); } } diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 509b1e6f41ca..2e716ae23f20 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -34,6 +34,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -322,9 +323,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr + " is not in a task belong to the organizer app."); return null; } - if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) { + if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED + || !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) { Slog.d(TAG, "Reparent activity=" + activity.token - + " is not allowed to be embedded."); + + " is not allowed to be embedded in trusted mode."); return null; } @@ -350,7 +352,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr activity.token, task.mTaskId); return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK) .setTaskId(task.mTaskId) - .setActivityIntent(activity.intent) + .setActivityIntent(trimIntent(activity.intent)) .setActivityToken(activityToken); } @@ -1095,4 +1097,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return false; } } + + /** + * Trims the given Intent to only those that are needed to for embedding rules. This helps to + * make it safer for cross-uid embedding even if we only send the Intent for trusted embedding. + */ + private static Intent trimIntent(@NonNull Intent intent) { + return new Intent() + .setComponent(intent.getComponent()) + .setPackage(intent.getPackage()) + .setAction(intent.getAction()); + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index bab3a0553e63..1282acbc9e5a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -903,4 +903,7 @@ public abstract class WindowManagerInternal { * could not be prepared and the session needs to be torn down. */ public abstract boolean setContentRecordingSession(ContentRecordingSession incomingSession); + + /** Returns the SurfaceControl accessibility services should use for accessibility overlays. */ + public abstract SurfaceControl getA11yOverlayLayer(int displayId); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index df343db8b3bf..1289634d0a1d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -736,7 +736,6 @@ public class WindowManagerService extends IWindowManager.Stub @VisibleForTesting final ContentRecordingController mContentRecordingController = new ContentRecordingController(); - @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mDisplayInversionEnabledUri = @@ -8301,6 +8300,17 @@ public class WindowManagerService extends IWindowManager.Stub return true; } } + + @Override + public SurfaceControl getA11yOverlayLayer(int displayId) { + synchronized (mGlobalLock) { + DisplayContent dc = mRoot.getDisplayContent(displayId); + if (dc != null) { + return dc.getA11yOverlayLayer(); + } + } + return null; + } } void registerAppFreezeListener(AppFreezeListener listener) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 89cbf5324ed4..c58e8d500eb2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -23,6 +23,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; +import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY; import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; @@ -46,6 +47,7 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER; +import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE; @@ -330,6 +332,7 @@ import android.util.ArraySet; import android.util.AtomicFile; import android.util.DebugUtils; import android.util.IndentingPrintWriter; +import android.util.IntArray; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -671,6 +674,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private @interface CopyAccountStatus {} /** + * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to + * corresponding app-ops. + */ + private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS = + new ArrayMap<>(); + static { + APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put( + EXEMPT_FROM_APP_STANDBY, OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY); + } + + /** * Admin apps targeting Android S+ may not use * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality * on the {@code DevicePolicyManager} instance obtained by calling @@ -17016,6 +17030,88 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); } + @Override + public void setApplicationExemptions(String packageName, int[] exemptions) { + if (!mHasFeature) { + return; + } + Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty."); + Objects.requireNonNull(exemptions, "Application exemptions must not be null."); + Preconditions.checkArgument(areApplicationExemptionsValid(exemptions), + "Invalid application exemption constant found in application exemptions set."); + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)); + + final CallerIdentity caller = getCallerIdentity(); + final ApplicationInfo packageInfo; + packageInfo = getPackageInfoWithNullCheck(packageName, caller); + + for (Map.Entry<Integer, String> entry : + APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) { + int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow( + entry.getValue(), packageInfo.uid, packageInfo.packageName); + int newMode = ArrayUtils.contains(exemptions, entry.getKey()) + ? MODE_ALLOWED : MODE_DEFAULT; + mInjector.binderWithCleanCallingIdentity(() -> { + if (currentMode != newMode) { + mInjector.getAppOpsManager() + .setMode(entry.getValue(), + packageInfo.uid, + packageName, + newMode); + } + }); + } + } + + @Override + public int[] getApplicationExemptions(String packageName) { + if (!mHasFeature) { + return new int[0]; + } + Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty."); + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)); + + final CallerIdentity caller = getCallerIdentity(); + final ApplicationInfo packageInfo; + packageInfo = getPackageInfoWithNullCheck(packageName, caller); + + IntArray appliedExemptions = new IntArray(0); + for (Map.Entry<Integer, String> entry : + APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) { + if (mInjector.getAppOpsManager().unsafeCheckOpNoThrow( + entry.getValue(), packageInfo.uid, packageInfo.packageName) == MODE_ALLOWED) { + appliedExemptions.add(entry.getKey()); + } + } + return appliedExemptions.toArray(); + } + + private ApplicationInfo getPackageInfoWithNullCheck(String packageName, CallerIdentity caller) { + final ApplicationInfo packageInfo = + mInjector.getPackageManagerInternal().getApplicationInfo( + packageName, + /* flags= */ 0, + caller.getUid(), + caller.getUserId()); + if (packageInfo == null) { + throw new ServiceSpecificException( + DevicePolicyManager.ERROR_PACKAGE_NAME_NOT_FOUND, + "Package name not found."); + } + return packageInfo; + } + + private boolean areApplicationExemptionsValid(int[] exemptions) { + for (int exemption : exemptions) { + if (!APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.containsKey(exemption)) { + return false; + } + } + return true; + } + private boolean isCallingFromPackage(String packageName, int callingUid) { return mInjector.binderWithCleanCallingIdentity(() -> { try { diff --git a/services/permission/Android.bp b/services/permission/Android.bp new file mode 100644 index 000000000000..b03f17b1bef7 --- /dev/null +++ b/services/permission/Android.bp @@ -0,0 +1,40 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "services.permission-sources", + srcs: [ + "java/**/*.java", + "java/**/*.kt", + ], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.permission", + defaults: ["platform_service_defaults"], + srcs: [":services.permission-sources"], + libs: [ + "services.core", + // Soong fails to automatically add this dependency because all the + // *.kt sources are inside a filegroup. + "kotlin-annotations", + ], + static_libs: [ + "kotlin-stdlib", + ], + jarjar_rules: "jarjar-rules.txt", + kotlincflags: [ + "-Xjvm-default=all", + "-Xno-call-assertions", + "-Xno-param-assertions", + "-Xno-receiver-assertions", + ], +} diff --git a/services/permission/OWNERS b/services/permission/OWNERS new file mode 100644 index 000000000000..6c6c9fc10d3b --- /dev/null +++ b/services/permission/OWNERS @@ -0,0 +1,4 @@ +ashfall@google.com +joecastro@google.com +ntmyren@google.com +zhanghai@google.com diff --git a/services/permission/jarjar-rules.txt b/services/permission/jarjar-rules.txt new file mode 100644 index 000000000000..34af3afabc4f --- /dev/null +++ b/services/permission/jarjar-rules.txt @@ -0,0 +1 @@ +rule kotlin.** com.android.server.permission.jarjar.@0 diff --git a/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt b/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt new file mode 100644 index 000000000000..21ec1593fdeb --- /dev/null +++ b/services/permission/java/com/android/server/permission/ModernPermissionManagerServiceImpl.kt @@ -0,0 +1,26 @@ +/* + * 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.permission + +import com.android.internal.annotations.Keep +import com.android.server.pm.permission.PermissionManagerServiceInterface + +/** + * Modern implementation of [PermissionManagerServiceInterface]. + */ +@Keep +class ModernPermissionManagerServiceImpl diff --git a/services/proguard_permission.flags b/services/proguard_permission.flags new file mode 100644 index 000000000000..15edc61c33b2 --- /dev/null +++ b/services/proguard_permission.flags @@ -0,0 +1,9 @@ +# Only shrink services.permission classes. +# Note that while more aggressive services shrinking is enabled by default (see proguard.flags), for +# cases where that's not yet possible, we still need to shrink the permission package to prune out +# unused Kotlin stdlib dependencies. +-keep class !com.android.server.permission.** { *; } + +# CoverageService guards optional jacoco class references with a runtime guard, so we can safely +# suppress build-time warnings. +-dontwarn org.jacoco.agent.rt.* diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index 0f7c0d73ad08..2f6b07bfb6f7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -466,7 +466,7 @@ public class BroadcastQueueModernImplTest { @Test public void testRunnableAt_Cached_Interactive() { final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setInteractiveBroadcast(true); + options.setInteractive(true); doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options, List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_INTERACTIVE); } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index fd605f779625..b9615f6b3771 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -50,7 +50,6 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IApplicationThread; -import android.app.RemoteServiceException.CannotDeliverBroadcastException; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; @@ -964,8 +963,8 @@ public class BroadcastQueueTest { // First broadcast should have already been dead verifyScheduleRegisteredReceiver(receiverApp, airplane); - verify(receiverApp).scheduleCrashLocked(any(), - eq(CannotDeliverBroadcastException.TYPE_ID), any()); + // The old receiverApp should be killed gently + assertTrue(receiverApp.isKilled()); // Second broadcast in new process should work fine final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN, @@ -995,8 +994,8 @@ public class BroadcastQueueTest { // First broadcast should have already been dead verifyScheduleReceiver(receiverApp, airplane); - verify(receiverApp).scheduleCrashLocked(any(), - eq(CannotDeliverBroadcastException.TYPE_ID), any()); + // The old receiverApp should be killed gently + assertTrue(receiverApp.isKilled()); // Second broadcast in new process should work fine final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN, diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java index 9be370fe3045..6b340205ceda 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java @@ -19,11 +19,11 @@ import static android.os.UserHandle.USER_SYSTEM; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.junit.Assert.assertThrows; +import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE; +import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE; +import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE; -import android.util.Log; +import static com.google.common.truth.Truth.assertWithMessage; import org.junit.Test; @@ -36,130 +36,94 @@ import org.junit.Test; */ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase { - private static final String TAG = UserVisibilityMediatorMUMDTest.class.getSimpleName(); - public UserVisibilityMediatorMUMDTest() { super(/* usersOnSecondaryDisplaysEnabled= */ true); } @Test - public void testAssignUserToDisplay_systemUser() { - assertThrows(IllegalArgumentException.class, () -> mMediator - .assignUserToDisplay(USER_SYSTEM, USER_SYSTEM, SECONDARY_DISPLAY_ID)); - } + public void testStartUser_systemUser() { + int result = mMediator.startUser(USER_SYSTEM, USER_SYSTEM, FG, SECONDARY_DISPLAY_ID); - @Test - public void testAssignUserToDisplay_invalidDisplay() { - assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, INVALID_DISPLAY)); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); } @Test - public void testAssignUserToDisplay_currentUser() { - mockCurrentUser(USER_ID); - - assertThrows(IllegalArgumentException.class, - () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID)); + public void testStartUser_invalidDisplay() { + int result = mMediator.startUser(USER_ID, USER_ID, FG, INVALID_DISPLAY); - assertNoUserAssignedToDisplay(); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); } @Test - public void testAssignUserToDisplay_startedProfileOfCurrentUser() { - mockCurrentUser(PARENT_USER_ID); - startDefaultProfile(); + public void testStartUser_displayAvailable() { + int result = mMediator.startUser(USER_ID, USER_ID, BG, SECONDARY_DISPLAY_ID); + + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator - .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); + assertIsNotCurrentUserOrRunningProfileOfCurrentUser(USER_ID); + assertStartedProfileGroupIdOf(USER_ID, USER_ID); - Log.v(TAG, "Exception: " + e); - assertNoUserAssignedToDisplay(); + stopUserAndAssertState(USER_ID); } @Test - public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() { - mockCurrentUser(PARENT_USER_ID); - stopDefaultProfile(); + public void testStartUser_displayAlreadyAssigned() { + startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator - .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); + int result = mMediator.startUser(USER_ID, USER_ID, BG, SECONDARY_DISPLAY_ID); - Log.v(TAG, "Exception: " + e); - assertNoUserAssignedToDisplay(); - } - - @Test - public void testAssignUserToDisplay_displayAvailable() { - mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); - assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + stopUserAndAssertState(PROFILE_USER_ID); } @Test - public void testAssignUserToDisplay_displayAlreadyAssigned() { - mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID); + public void testStartUser_userAlreadyAssigned() { + startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID); - IllegalStateException e = assertThrows(IllegalStateException.class, () -> mMediator - .assignUserToDisplay(OTHER_USER_ID, OTHER_USER_ID, SECONDARY_DISPLAY_ID)); + int result = mMediator.startUser(USER_ID, USER_ID, BG, SECONDARY_DISPLAY_ID); - Log.v(TAG, "Exception: " + e); - assertWithMessage("exception (%s) message", e).that(e).hasMessageThat() - .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*" - + USER_ID + ".*"); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); } @Test - public void testAssignUserToDisplay_userAlreadyAssigned() { - mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID); + public void testStartUser_profileOnSameDisplayAsParent() { + startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID); - IllegalStateException e = assertThrows(IllegalStateException.class, - () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, OTHER_SECONDARY_DISPLAY_ID)); + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID); - Log.v(TAG, "Exception: " + e); - assertWithMessage("exception (%s) message", e).that(e).hasMessageThat() - .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*" - + SECONDARY_DISPLAY_ID + ".*"); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); - assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + stopUserAndAssertState(PROFILE_USER_ID); } @Test - public void testAssignUserToDisplay_profileOnSameDisplayAsParent() { - mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator - .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); + public void testStartUser_profileOnDifferentDisplayAsParent() { + startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID); - Log.v(TAG, "Exception: " + e); - assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); - } + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, + OTHER_SECONDARY_DISPLAY_ID); - @Test - public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() { - mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator - .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID)); + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); - Log.v(TAG, "Exception: " + e); - assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); + stopUserAndAssertState(PROFILE_USER_ID); } @Test - public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() { - mMediator.assignUserToDisplay(PARENT_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> mMediator - .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, DEFAULT_DISPLAY)); + public void testStartUser_profileDefaultDisplayParentOnSecondaryDisplay() { + startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID); - Log.v(TAG, "Exception: " + e); - assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID); - } + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); - // TODO(b/244644281): when start & assign are merged, rename tests above and also call - // stopUserAndAssertState() at the end of them + assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE); + + stopUserAndAssertState(PROFILE_USER_ID); + } @Test public void testIsUserVisible_bgUserOnSecondaryDisplay() { - mockCurrentUser(OTHER_USER_ID); - assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + startForegroundUser(OTHER_USER_ID); + startUserInSecondaryDisplay(USER_ID, SECONDARY_DISPLAY_ID); assertWithMessage("isUserVisible(%s)", USER_ID) .that(mMediator.isUserVisible(USER_ID)).isTrue(); @@ -170,7 +134,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_currentUserUnassignedSecondaryDisplay() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue(); @@ -178,8 +142,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_currentUserSecondaryDisplayAssignedToAnotherUser() { - mockCurrentUser(USER_ID); - assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); + startForegroundUser(USER_ID); + startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isFalse(); @@ -188,8 +152,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() { startDefaultProfile(); - mockCurrentUser(PARENT_USER_ID); - assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); + startForegroundUser(PARENT_USER_ID); + startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse(); @@ -197,9 +161,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() { - stopDefaultProfile(); - mockCurrentUser(PARENT_USER_ID); - assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); + startForegroundUser(PARENT_USER_ID); + startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID); assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse(); @@ -208,7 +171,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserOnUnassignedSecondaryDisplay() { startDefaultProfile(); - mockCurrentUser(PARENT_USER_ID); + startForegroundUser(PARENT_USER_ID); // TODO(b/244644281): change it to isFalse() once isUserVisible() is fixed (see note there) assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID) @@ -217,8 +180,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_bgUserOnSecondaryDisplay() { - mockCurrentUser(OTHER_USER_ID); - assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + startForegroundUser(OTHER_USER_ID); + startUserInSecondaryDisplay(USER_ID, SECONDARY_DISPLAY_ID); assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue(); @@ -226,8 +189,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testIsUserVisibleOnDisplay_bgUserOnAnotherSecondaryDisplay() { - mockCurrentUser(OTHER_USER_ID); - assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + startForegroundUser(OTHER_USER_ID); + startUserInSecondaryDisplay(USER_ID, SECONDARY_DISPLAY_ID); assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(USER_ID, OTHER_SECONDARY_DISPLAY_ID)).isFalse(); @@ -240,8 +203,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testGetDisplayAssignedToUser_bgUserOnSecondaryDisplay() { - mockCurrentUser(OTHER_USER_ID); - assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + startForegroundUser(OTHER_USER_ID); + startUserInSecondaryDisplay(USER_ID, SECONDARY_DISPLAY_ID); assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID) .that(mMediator.getDisplayAssignedToUser(USER_ID)) @@ -253,8 +216,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testGetUserAssignedToDisplay_bgUserOnSecondaryDisplay() { - mockCurrentUser(OTHER_USER_ID); - assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID); + startForegroundUser(OTHER_USER_ID); + startUserInSecondaryDisplay(USER_ID, SECONDARY_DISPLAY_ID); assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID) .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID); @@ -262,7 +225,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator @Test public void testGetUserAssignedToDisplay_noUserOnSecondaryDisplay() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID) .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java index 7abdd9e7bbaf..ef04c282b7ee 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java @@ -15,10 +15,6 @@ */ package com.android.server.pm; -import static org.junit.Assert.assertThrows; - -import org.junit.Test; - /** * Tests for {@link UserVisibilityMediator} tests for devices that DO NOT support concurrent * multiple users on multiple displays (A.K.A {@code SUSD} - Single User on Single Device). @@ -31,33 +27,4 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator public UserVisibilityMediatorSUSDTest() { super(/* usersOnSecondaryDisplaysEnabled= */ false); } - - // TODO(b/244644281): when start & assign are merged, rename tests below and also call - // stopUserAndAssertState() at the end of them - - @Test - public void testAssignUserToDisplay_otherDisplay_currentUser() { - mockCurrentUser(USER_ID); - - assertThrows(UnsupportedOperationException.class, - () -> mMediator.assignUserToDisplay(USER_ID, USER_ID, SECONDARY_DISPLAY_ID)); - } - - @Test - public void testAssignUserToDisplay_otherDisplay_startProfileOfcurrentUser() { - mockCurrentUser(PARENT_USER_ID); - startDefaultProfile(); - - assertThrows(UnsupportedOperationException.class, () -> mMediator - .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); - } - - @Test - public void testAssignUserToDisplay_otherDisplay_stoppedProfileOfcurrentUser() { - mockCurrentUser(PARENT_USER_ID); - stopDefaultProfile(); - - assertThrows(UnsupportedOperationException.class, () -> mMediator - .assignUserToDisplay(PROFILE_USER_ID, PARENT_USER_ID, SECONDARY_DISPLAY_ID)); - } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java index e8be97db717d..9c6cbd9e3b2a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java @@ -31,6 +31,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import android.annotation.UserIdInt; import android.util.Log; +import com.android.internal.util.Preconditions; import com.android.server.ExtendedMockitoTestCase; import org.junit.Before; @@ -81,8 +82,8 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { */ protected static final int OTHER_SECONDARY_DISPLAY_ID = 108; - private static final boolean FG = true; - private static final boolean BG = false; + protected static final boolean FG = true; + protected static final boolean BG = false; private final boolean mUsersOnSecondaryDisplaysEnabled; @@ -100,7 +101,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testStartUser_currentUser() { - int result = mMediator.startOnly(USER_ID, USER_ID, FG, DEFAULT_DISPLAY); + int result = mMediator.startUser(USER_ID, USER_ID, FG, DEFAULT_DISPLAY); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE); assertCurrentUser(USER_ID); @@ -111,8 +112,8 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { } @Test - public final void testStartUser_currentUserSecondaryDisplay() { - int result = mMediator.startOnly(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID); + public final void testStartUser_currentUserOnSecondaryDisplay() { + int result = mMediator.startUser(USER_ID, USER_ID, FG, SECONDARY_DISPLAY_ID); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); assertCurrentUser(INITIAL_CURRENT_USER_ID); @@ -124,9 +125,9 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testStartUser_profileBg_parentStarted() { - mockCurrentUser(PARENT_USER_ID); + startForegroundUser(PARENT_USER_ID); - int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE); assertCurrentUser(PARENT_USER_ID); @@ -139,7 +140,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testStartUser_profileBg_parentNotStarted() { - int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE); assertCurrentUser(INITIAL_CURRENT_USER_ID); @@ -152,7 +153,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testStartUser_profileBg_secondaryDisplay() { - int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID); + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, SECONDARY_DISPLAY_ID); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); assertCurrentUser(INITIAL_CURRENT_USER_ID); @@ -163,7 +164,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testStartUser_profileFg() { - int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY); + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, DEFAULT_DISPLAY); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); assertCurrentUser(INITIAL_CURRENT_USER_ID); @@ -173,8 +174,8 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { } @Test - public final void testStartUser_profileFgSecondaryDisplay() { - int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID); + public final void testStartUser_profileFg_secondaryDisplay() { + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, FG, SECONDARY_DISPLAY_ID); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE); assertCurrentUser(INITIAL_CURRENT_USER_ID); @@ -182,25 +183,19 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { stopUserAndAssertState(USER_ID); } + @Test public final void testGetStartedProfileGroupId_whenStartedWithNoProfileGroupId() { - int result = mMediator.startOnly(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY); + int result = mMediator.startUser(USER_ID, NO_PROFILE_GROUP_ID, FG, DEFAULT_DISPLAY); assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE); - assertWithMessage("shit").that(mMediator.getStartedProfileGroupId(USER_ID)) - .isEqualTo(USER_ID); - } - - @Test - public final void testAssignUserToDisplay_defaultDisplayIgnored() { - mMediator.assignUserToDisplay(USER_ID, USER_ID, DEFAULT_DISPLAY); - - assertNoUserAssignedToDisplay(); + assertWithMessage("getStartedProfileGroupId(%s)", USER_ID) + .that(mMediator.getStartedProfileGroupId(USER_ID)).isEqualTo(USER_ID); } @Test public final void testIsUserVisible_invalidUser() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("isUserVisible(%s)", USER_NULL) .that(mMediator.isUserVisible(USER_NULL)).isFalse(); @@ -208,7 +203,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisible_currentUser() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("isUserVisible(%s)", USER_ID) .that(mMediator.isUserVisible(USER_ID)).isTrue(); @@ -216,7 +211,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisible_nonCurrentUser() { - mockCurrentUser(OTHER_USER_ID); + startForegroundUser(OTHER_USER_ID); assertWithMessage("isUserVisible(%s)", USER_ID) .that(mMediator.isUserVisible(USER_ID)).isFalse(); @@ -224,7 +219,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisible_startedProfileOfcurrentUser() { - mockCurrentUser(PARENT_USER_ID); + startForegroundUser(PARENT_USER_ID); startDefaultProfile(); assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID) .that(mMediator.isUserVisible(PROFILE_USER_ID)).isTrue(); @@ -232,16 +227,14 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisible_stoppedProfileOfcurrentUser() { - mockCurrentUser(PARENT_USER_ID); - stopDefaultProfile(); - + startForegroundUser(PARENT_USER_ID); assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID) .that(mMediator.isUserVisible(PROFILE_USER_ID)).isFalse(); } @Test public final void testIsUserVisibleOnDisplay_invalidUser() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("isUserVisible(%s, %s)", USER_NULL, DEFAULT_DISPLAY) .that(mMediator.isUserVisible(USER_NULL, DEFAULT_DISPLAY)).isFalse(); @@ -249,7 +242,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisibleOnDisplay_currentUserInvalidDisplay() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("isUserVisible(%s, %s)", USER_ID, INVALID_DISPLAY) .that(mMediator.isUserVisible(USER_ID, INVALID_DISPLAY)).isFalse(); @@ -257,7 +250,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisibleOnDisplay_currentUserDefaultDisplay() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY) .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isTrue(); @@ -265,7 +258,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisibleOnDisplay_currentUserSecondaryDisplay() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue(); @@ -273,7 +266,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisibleOnDisplay_nonCurrentUserDefaultDisplay() { - mockCurrentUser(OTHER_USER_ID); + startForegroundUser(OTHER_USER_ID); assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY) .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isFalse(); @@ -281,7 +274,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() { - mockCurrentUser(PARENT_USER_ID); + startForegroundUser(PARENT_USER_ID); startDefaultProfile(); assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY) @@ -290,16 +283,14 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() { - mockCurrentUser(PARENT_USER_ID); - stopDefaultProfile(); - + startForegroundUser(PARENT_USER_ID); assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY) .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse(); } @Test public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() { - mockCurrentUser(PARENT_USER_ID); + startForegroundUser(PARENT_USER_ID); startDefaultProfile(); assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY) .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue(); @@ -307,16 +298,14 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() { - mockCurrentUser(PARENT_USER_ID); - stopDefaultProfile(); - + startForegroundUser(PARENT_USER_ID); assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY) .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse(); } @Test public final void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplay() { - mockCurrentUser(PARENT_USER_ID); + startForegroundUser(PARENT_USER_ID); startDefaultProfile(); assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue(); @@ -324,16 +313,14 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() { - mockCurrentUser(PARENT_USER_ID); - stopDefaultProfile(); - + startForegroundUser(PARENT_USER_ID); assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID) .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse(); } @Test public void testGetDisplayAssignedToUser_invalidUser() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL) .that(mMediator.getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY); @@ -341,7 +328,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public void testGetDisplayAssignedToUser_currentUser() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID) .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY); @@ -349,7 +336,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testGetDisplayAssignedToUser_nonCurrentUser() { - mockCurrentUser(OTHER_USER_ID); + startForegroundUser(OTHER_USER_ID); assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID) .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY); @@ -357,7 +344,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() { - mockCurrentUser(PARENT_USER_ID); + startForegroundUser(PARENT_USER_ID); startDefaultProfile(); assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID) .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID)) @@ -366,9 +353,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() { - mockCurrentUser(PARENT_USER_ID); - stopDefaultProfile(); - + startForegroundUser(PARENT_USER_ID); assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID) .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID)) .isEqualTo(INVALID_DISPLAY); @@ -376,7 +361,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public void testGetUserAssignedToDisplay_invalidDisplay() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("getUserAssignedToDisplay(%s)", INVALID_DISPLAY) .that(mMediator.getUserAssignedToDisplay(INVALID_DISPLAY)).isEqualTo(USER_ID); @@ -384,7 +369,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testGetUserAssignedToDisplay_defaultDisplay() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("getUserAssignedToDisplay(%s)", DEFAULT_DISPLAY) .that(mMediator.getUserAssignedToDisplay(DEFAULT_DISPLAY)).isEqualTo(USER_ID); @@ -392,7 +377,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { @Test public final void testGetUserAssignedToDisplay_secondaryDisplay() { - mockCurrentUser(USER_ID); + startForegroundUser(USER_ID); assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID) .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)) @@ -407,72 +392,61 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { * own methods, but it depends on the user being started at first place, so pragmatically * speaking, it's better to "reuse" such tests for both (start and stop) */ - private void stopUserAndAssertState(@UserIdInt int userId) { + protected void stopUserAndAssertState(@UserIdInt int userId) { mMediator.stopUser(userId); assertUserIsStopped(userId); - assertNoUserAssignedToDisplay(); } - // TODO(b/244644281): remove if start & assign are merged; if they aren't, add a note explaining - // it's not meant to be used to test startUser() itself. - protected void mockCurrentUser(@UserIdInt int userId) { - Log.d(TAG, "mockCurrentUser(" + userId + ")"); - int result = mMediator.startOnly(userId, userId, FG, DEFAULT_DISPLAY); + /** + * Starts a user in foreground on the main display, asserting it was properly started. + * + * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the + * {@link UserVisibilityMediator#startUser(int, int, boolean, int)} method per se. + */ + protected void startForegroundUser(@UserIdInt int userId) { + Log.d(TAG, "startForegroundUSer(" + userId + ")"); + int result = mMediator.startUser(userId, userId, FG, DEFAULT_DISPLAY); if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) { - throw new IllegalStateException("Failed to mock current user " + userId + throw new IllegalStateException("Failed to start foreground user " + userId + ": mediator returned " + userAssignmentResultToString(result)); } } - // TODO(b/244644281): remove when start & assign are merged; or add a note explaining - // it's not meant to be used to test startUser() itself. + /** + * Starts the {@link #PROFILE_USER_ID default profile } in foreground on the main display, + * asserting it was properly started. + * + * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the + * {@link UserVisibilityMediator#startUser(int, int, boolean, int)} method per se. + */ protected void startDefaultProfile() { - mockCurrentUser(PARENT_USER_ID); + startForegroundUser(PARENT_USER_ID); Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting" + " its parent (" + PARENT_USER_ID + ") on foreground"); - int result = mMediator.startOnly(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); + int result = mMediator.startUser(PROFILE_USER_ID, PARENT_USER_ID, BG, DEFAULT_DISPLAY); if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) { throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID + ": mediator returned " + userAssignmentResultToString(result)); } } - // TODO(b/244644281): remove when start & assign are merged; or add a note explaining - // it's not meant to be used to test stopUser() itself. - protected void stopDefaultProfile() { - Log.d(TAG, "stopping default profile"); - mMediator.stopUser(PROFILE_USER_ID); - } - - // TODO(b/244644281): remove when start & assign are merged; or add a note explaining - // it's not meant to be used to test assignUserToDisplay() itself. - protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) { - Log.d(TAG, "assignUserToDisplay(" + userId + ", " + displayId + ")"); - int result = mMediator.startOnly(userId, userId, BG, displayId); - if (result != USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE) { + /** + * Starts a user in background on the secondary display, asserting it was properly started. + * + * <p><b>NOTE: </b>should only be used as a helper method, not to test the behavior of the + * {@link UserVisibilityMediator#startUser(int, int, boolean, int)} method per se. + */ + protected final void startUserInSecondaryDisplay(@UserIdInt int userId, int displayId) { + Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY, + "must pass a secondary display, not %d", displayId); + Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")"); + int result = mMediator.startUser(userId, userId, BG, displayId); + if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) { throw new IllegalStateException("Failed to startuser " + userId + " on background: mediator returned " + userAssignmentResultToString(result)); } - mMediator.assignUserToDisplay(userId, userId, displayId); - - } - - // TODO(b/244644281): remove when start & assign are merged; or rename to - // assertNoUserAssignedToSecondaryDisplays - protected final void assertNoUserAssignedToDisplay() { - assertWithMessage("users on secondary displays") - .that(mMediator.getUsersOnSecondaryDisplays()) - .isEmpty(); - } - - // TODO(b/244644281): remove when start & assign are merged; or rename to - // assertUserAssignedToSecondaryDisplay - protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) { - assertWithMessage("users on secondary displays") - .that(mMediator.getUsersOnSecondaryDisplays()) - .containsExactly(userId, displayId); } private void assertCurrentUser(@UserIdInt int userId) { @@ -500,7 +474,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { assertUserIsStarted(userId); } - private void assertStartedProfileGroupIdOf(@UserIdInt int userId, + protected void assertStartedProfileGroupIdOf(@UserIdInt int userId, @UserIdInt int profileGroupId) { assertWithMessage("mediator.getStartedProfileGroupId(%s)", userId) .that(mMediator.getStartedProfileGroupId(userId)) @@ -518,16 +492,16 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase { } } - private void assertIsNotCurrentUserOrRunningProfileOfCurrentUser(int userId) { + protected void assertIsNotCurrentUserOrRunningProfileOfCurrentUser(int userId) { assertWithMessage("mediator.isCurrentUserOrRunningProfileOfCurrentUser(%s)", userId) .that(mMediator.isCurrentUserOrRunningProfileOfCurrentUser(userId)) .isFalse(); } - private void assertStartUserResult(int actualResult, int expectedResult) { + protected void assertStartUserResult(int actualResult, int expectedResult) { assertWithMessage("startUser() result (where %s=%s and %s=%s)", - actualResult, userAssignmentResultToString(actualResult), - expectedResult, userAssignmentResultToString(expectedResult)) + expectedResult, userAssignmentResultToString(expectedResult), + actualResult, userAssignmentResultToString(actualResult)) .that(actualResult).isEqualTo(expectedResult); } } diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java new file mode 100644 index 000000000000..cbeaf7ba2434 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -0,0 +1,80 @@ +/* + * 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.brightness; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.view.Display; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class DisplayBrightnessControllerTest { + private static final int DISPLAY_ID = 1; + + @Mock + private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector; + @Mock + private Context mContext; + + private DisplayBrightnessController mDisplayBrightnessController; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + DisplayBrightnessController.Injector injector = new DisplayBrightnessController.Injector() { + @Override + DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector( + Context context, int displayId) { + return mDisplayBrightnessStrategySelector; + } + }; + mDisplayBrightnessController = new DisplayBrightnessController(mContext, injector, + DISPLAY_ID); + } + + @Test + public void updateBrightnessWorksAsExpected() { + DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class); + DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class); + int targetDisplayState = Display.STATE_DOZE; + when(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, + targetDisplayState)).thenReturn(displayBrightnessStrategy); + mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState); + verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest); + } + + @Test + public void isAllowAutoBrightnessWhileDozingConfigDelegatesToDozeBrightnessStrategy() { + mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(); + verify(mDisplayBrightnessStrategySelector).isAllowAutoBrightnessWhileDozingConfig(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java new file mode 100644 index 000000000000..ba31e8c4de32 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -0,0 +1,113 @@ +/* + * 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.brightness; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.hardware.display.DisplayManagerInternal; +import android.view.Display; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; +import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; +import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; +import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class DisplayBrightnessStrategySelectorTest { + private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false; + private static final int DISPLAY_ID = 1; + + @Mock + private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy; + @Mock + private DozeBrightnessStrategy mDozeBrightnessModeStrategy; + @Mock + private InvalidBrightnessStrategy mInvalidBrightnessStrategy; + @Mock + private Context mContext; + @Mock + private Resources mResources; + + private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + when(mContext.getResources()).thenReturn(mResources); + DisplayBrightnessStrategySelector.Injector injector = + new DisplayBrightnessStrategySelector.Injector() { + @Override + ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() { + return mScreenOffBrightnessModeStrategy; + } + + @Override + DozeBrightnessStrategy getDozeBrightnessStrategy() { + return mDozeBrightnessModeStrategy; + } + + @Override + InvalidBrightnessStrategy getInvalidBrightnessStrategy() { + return mInvalidBrightnessStrategy; + } + }; + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + injector, DISPLAY_ID); + + } + + @Test + public void selectStrategySelectsDozeStrategyWhenValid() { + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( + DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); + assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, + Display.STATE_DOZE), mDozeBrightnessModeStrategy); + } + + @Test + public void selectStrategySelectsScreenOffStrategyWhenValid() { + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, + Display.STATE_OFF), mScreenOffBrightnessModeStrategy); + } + + @Test + public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() { + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, + Display.STATE_ON), mInvalidBrightnessStrategy); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java new file mode 100644 index 000000000000..29652ff0396b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java @@ -0,0 +1,60 @@ +/* + * 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.brightness.strategy; + +import static org.junit.Assert.assertEquals; + +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DozeBrightnessStrategyTest { + private DozeBrightnessStrategy mDozeBrightnessModeStrategy; + + @Before + public void before() { + mDozeBrightnessModeStrategy = new DozeBrightnessStrategy(); + } + + @Test + public void updateBrightnessWorksAsExpectedWhenScreenDozeStateIsRequested() { + DisplayPowerRequest displayPowerRequest = new DisplayPowerRequest(); + float dozeScreenBrightness = 0.2f; + displayPowerRequest.dozeScreenBrightness = dozeScreenBrightness; + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_DOZE); + DisplayBrightnessState expectedDisplayBrightnessState = + new DisplayBrightnessState.Builder() + .setBrightness(dozeScreenBrightness) + .setBrightnessReason(brightnessReason) + .setSdrBrightness(dozeScreenBrightness) + .build(); + DisplayBrightnessState updatedDisplayBrightnessState = + mDozeBrightnessModeStrategy.updateBrightness(displayPowerRequest); + assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java new file mode 100644 index 000000000000..050547550c95 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java @@ -0,0 +1,60 @@ +/* + * 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.brightness.strategy; + +import static org.junit.Assert.assertEquals; + +import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.os.PowerManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class ScreenOffBrightnessStrategyTest { + + private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy; + + @Before + public void before() { + mScreenOffBrightnessModeStrategy = new ScreenOffBrightnessStrategy(); + } + + @Test + public void updateBrightnessWorksAsExpectedWhenScreenOffDisplayState() { + DisplayPowerRequest displayPowerRequest = new DisplayPowerRequest(); + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF); + DisplayBrightnessState expectedDisplayBrightnessState = + new DisplayBrightnessState.Builder() + .setBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT) + .setSdrBrightness(PowerManager.BRIGHTNESS_OFF_FLOAT) + .setBrightnessReason(brightnessReason) + .build(); + DisplayBrightnessState updatedDisplayBrightnessState = + mScreenOffBrightnessModeStrategy.updateBrightness(displayPowerRequest); + assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 54baf18da92a..82c340145f8e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -849,4 +849,53 @@ public class HdmiCecLocalDeviceTvTest { verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(), anyInt()); } + + @Test + public void tvSendRequestArcTerminationOnSleep() { + // Emulate Audio device on port 0x2000 (supports ARC) + + mNativeWrapper.setPortConnectionStatus(2, true); + HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mNativeWrapper.onCecMessage(hdmiCecMessage); + mTestLooper.dispatchAll(); + + mHdmiCecLocalDeviceTv.startArcAction(true); + mTestLooper.dispatchAll(); + HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc( + ADDR_AUDIO_SYSTEM, + ADDR_TV); + HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated( + ADDR_TV, + ADDR_AUDIO_SYSTEM); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination); + + mNativeWrapper.onCecMessage(initiateArc); + mTestLooper.dispatchAll(); + + // Finish querying SADs + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + // ARC should be established after RequestSadAction is finished + assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated); + + mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination); + } + } diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index db2630e2683c..d0d2b412b1b6 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -658,17 +658,52 @@ public class PowerManagerServiceTest { } /** - * Tests that dreaming continues when undocking and configured to do so. + * Tests that dreaming stops when undocking and not configured to keep dreaming. */ @Test - public void testWakefulnessDream_shouldKeepDreamingWhenUndocked() { + public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenNotConfigured() { + // Make sure "unplug turns on screen" is configured to true. + when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen)) + .thenReturn(true); + createService(); startSystem(); - when(mResourcesSpy.getBoolean( - com.android.internal.R.bool.config_keepDreamingWhenUndocking)) + ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener = + ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class); + verify(mDreamManagerInternalMock).registerDreamManagerStateListener( + dreamManagerStateListener.capture()); + dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false); + + when(mBatteryManagerInternalMock.getPlugType()) + .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK); + setPluggedIn(true); + + forceAwake(); // Needs to be awake first before it can dream. + forceDream(); + when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0); + setPluggedIn(false); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + } + + /** + * Tests that dreaming continues when undocking and configured to do so. + */ + @Test + public void testWakefulnessDream_shouldKeepDreamingWhenUndocked_whenConfigured() { + // Make sure "unplug turns on screen" is configured to true. + when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen)) .thenReturn(true); - mService.readConfigurationLocked(); + + createService(); + startSystem(); + + ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener = + ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class); + verify(mDreamManagerInternalMock).registerDreamManagerStateListener( + dreamManagerStateListener.capture()); + dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true); when(mBatteryManagerInternalMock.getPlugType()) .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK); @@ -682,6 +717,37 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING); } + /** + * Tests that dreaming stops when undocking while showing a dream that prevents it. + */ + @Test + public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenDreamPrevents() { + // Make sure "unplug turns on screen" is configured to true. + when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen)) + .thenReturn(true); + + createService(); + startSystem(); + + ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener = + ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class); + verify(mDreamManagerInternalMock).registerDreamManagerStateListener( + dreamManagerStateListener.capture()); + dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true); + + when(mBatteryManagerInternalMock.getPlugType()) + .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK); + setPluggedIn(true); + + forceAwake(); // Needs to be awake first before it can dream. + forceDream(); + dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false); + when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0); + setPluggedIn(false); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + } + @Test public void testWakefulnessDoze_goToSleep() { createService(); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java index 1246d1ee46e8..1be9de78cd27 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java @@ -35,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting; import junit.framework.Assert; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -72,6 +73,7 @@ public class LetterboxConfigurationPersisterTest { mLetterboxConfigurationPersister.start(); } + @After public void tearDown() throws InterruptedException { deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue); waitForCompletion(mPersisterQueue); 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 4202f46c188c..c53518210af9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -62,10 +62,12 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.annotation.NonNull; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -403,7 +405,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final TaskFragmentTransaction.Change change = changes.get(0); assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType()); assertEquals(task.mTaskId, change.getTaskId()); - assertEquals(activity.intent, change.getActivityIntent()); + assertIntentsEqualForOrganizer(activity.intent, change.getActivityIntent()); assertNotEquals(activity.token, change.getActivityToken()); mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken()); assertApplyTransactionAllowed(mTransaction); @@ -415,6 +417,62 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test + public void testOnActivityReparentedToTask_untrustedEmbed_notReported() { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid, + DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); + mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); + final Task task = createTask(mDisplayContent); + task.addChild(mTaskFragment, POSITION_TOP); + final ActivityRecord activity = createActivityRecord(task); + + // Make sure the activity is embedded in untrusted mode. + activity.info.applicationInfo.uid = uid + 1; + doReturn(pid + 1).when(activity).getPid(); + task.effectiveUid = uid; + doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid); + doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid); + doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity); + + // Notify organizer if it was embedded before entered Pip. + // Create a temporary token since the activity doesn't belong to the same process. + clearInvocations(mOrganizer); + activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer; + mController.onActivityReparentedToTask(activity); + mController.dispatchPendingEvents(); + + // Disallow organizer to reparent activity that is untrusted embedded. + verify(mOrganizer, never()).onTransactionReady(mTransactionCaptor.capture()); + } + + @Test + public void testOnActivityReparentedToTask_trimReportedIntent() { + // Make sure the activity pid/uid is the same as the organizer caller. + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final ActivityRecord activity = createActivityRecord(mDisplayContent); + final Task task = activity.getTask(); + activity.info.applicationInfo.uid = uid; + doReturn(pid).when(activity).getPid(); + task.effectiveUid = uid; + activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer; + + // Test the Intent trim in #assertIntentTrimmed + activity.intent.setComponent(new ComponentName("TestPackage", "TestClass")) + .setPackage("TestPackage") + .setAction("TestAction") + .setData(mock(Uri.class)) + .putExtra("Test", 123) + .setFlags(10); + + mController.onActivityReparentedToTask(activity); + mController.dispatchPendingEvents(); + + assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token); + } + + @Test public void testRegisterRemoteAnimations() { mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition); @@ -1425,7 +1483,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { final TaskFragmentTransaction.Change change = changes.remove(0); assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType()); assertEquals(taskId, change.getTaskId()); - assertEquals(intent, change.getActivityIntent()); + assertIntentsEqualForOrganizer(intent, change.getActivityIntent()); + assertIntentTrimmed(change.getActivityIntent()); assertEquals(activityToken, change.getActivityToken()); } @@ -1452,4 +1511,17 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mockParent.lastActiveTime = 100; doReturn(true).when(mockParent).shouldBeVisible(any()); } + + private static void assertIntentsEqualForOrganizer(@NonNull Intent expected, + @NonNull Intent actual) { + assertEquals(expected.getComponent(), actual.getComponent()); + assertEquals(expected.getPackage(), actual.getPackage()); + assertEquals(expected.getAction(), actual.getAction()); + } + + private static void assertIntentTrimmed(@NonNull Intent intent) { + assertNull(intent.getData()); + assertNull(intent.getExtras()); + assertEquals(0, intent.getFlags()); + } } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index ccec67e8687e..af37ed583438 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1851,7 +1851,7 @@ public class TelecomManager { ITelecomService service = getTelecomService(); if (service != null) { try { - return service.getCallStateUsingPackage(mContext.getPackageName(), + return service.getCallStateUsingPackage(mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { Log.d(TAG, "RemoteException calling getCallState().", e); diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java index d4b912e76b2d..f4336096ed3b 100644 --- a/telephony/java/android/telephony/PreciseCallState.java +++ b/telephony/java/android/telephony/PreciseCallState.java @@ -67,8 +67,8 @@ public final class PreciseCallState implements Parcelable { /** Call state: Disconnecting. */ public static final int PRECISE_CALL_STATE_DISCONNECTING = 8; /** - * Call state: Incoming in pre-alerting state. - * A call will be in this state prior to entering {@link #PRECISE_CALL_STATE_ALERTING}. + * Call state: Incoming in pre-alerting state i.e. prior to entering + * {@link #PRECISE_CALL_STATE_INCOMING}. */ public static final int PRECISE_CALL_STATE_INCOMING_SETUP = 9; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 73aceba266e7..8c3ef675dabc 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -9540,12 +9540,13 @@ public class TelephonyManager { /** * Set the allowed network types of the device and provide the reason triggering the allowed * network change. - * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE or + * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or * that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * - * This can be called for following reasons + * This can be called for following reasons: * <ol> - * <li>Allowed network types control by USER {@link #ALLOWED_NETWORK_TYPES_REASON_USER} + * <li>Allowed network types control by USER + * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER} * <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER} * </ol> * This API will result in allowing an intersection of allowed network types for all reasons, @@ -9555,7 +9556,13 @@ public class TelephonyManager { * @param allowedNetworkTypes The bitmask of allowed network type * @throws IllegalStateException if the Telephony process is not currently available. * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed. - * @throws SecurityException if the caller does not have the required privileges + * @throws SecurityException if the caller does not have the required privileges or if the + * caller tries to use one of the following security-based reasons without + * {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions. + * <ol> + * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li> + * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS}</li> + * </ol> */ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature( diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 340ee7202757..0c14dbaa5a3f 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -543,6 +543,9 @@ public interface RILConstants { int RIL_REQUEST_SEND_ANBR_QUERY = 237; int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238; int RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED = 239; + int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240; + int RIL_REQUEST_SET_N1_MODE_ENABLED = 241; + int RIL_REQUEST_IS_N1_MODE_ENABLED = 242; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index 87b4c6855638..006a02908643 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -54,6 +54,7 @@ LANG_TO_SCRIPT = { 'or': 'Orya', 'pa': 'Guru', 'pt': 'Latn', + 'pl': 'Latn', 'ru': 'Latn', 'sk': 'Latn', 'sl': 'Latn', |