diff options
380 files changed, 7599 insertions, 3481 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 8591a9c4a195..6ecd38f054aa 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -21,6 +21,7 @@ aconfig_declarations_group { // !!! KEEP THIS LIST ALPHABETICAL !!! "aconfig_mediacodec_flags_java_lib", "android.adaptiveauth.flags-aconfig-java", + "android.app.contextualsearch.flags-aconfig-java", "android.app.flags-aconfig-java", "android.app.ondeviceintelligence-aconfig-java", "android.app.smartspace.flags-aconfig-java", @@ -974,6 +975,19 @@ java_aconfig_library { ], } +// Contextual Search +aconfig_declarations { + name: "android.app.contextualsearch.flags-aconfig", + package: "android.app.contextualsearch.flags", + srcs: ["core/java/android/app/contextualsearch/flags.aconfig"], +} + +java_aconfig_library { + name: "android.app.contextualsearch.flags-aconfig-java", + aconfig_declarations: "android.app.contextualsearch.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Smartspace aconfig_declarations { name: "android.app.smartspace.flags-aconfig", diff --git a/core/api/current.txt b/core/api/current.txt index 14783775d762..cc3f52f73806 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12487,33 +12487,33 @@ package android.content.pm { } public class LauncherApps { - method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle); - method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllPackageInstallerSessions(); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle); + method @NonNull @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllPackageInstallerSessions(); method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.IntentSender getAppMarketActivityIntent(@Nullable String, @NonNull android.os.UserHandle); - method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public final android.content.pm.LauncherUserInfo getLauncherUserInfo(@NonNull android.os.UserHandle); method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent); method @FlaggedApi("android.os.allow_private_profile") @NonNull @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<java.lang.String> getPreInstalledSystemPackages(@NonNull android.os.UserHandle); - method public java.util.List<android.os.UserHandle> getProfiles(); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<android.os.UserHandle> getProfiles(); method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int); method @Nullable public android.content.IntentSender getShortcutConfigActivityIntent(@NonNull android.content.pm.LauncherActivityInfo); method public java.util.List<android.content.pm.LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String, @NonNull android.os.UserHandle); method public android.graphics.drawable.Drawable getShortcutIconDrawable(@NonNull android.content.pm.ShortcutInfo, int); method @Nullable public android.app.PendingIntent getShortcutIntent(@NonNull String, @NonNull String, @Nullable android.os.Bundle, @NonNull android.os.UserHandle); method @Nullable public java.util.List<android.content.pm.ShortcutInfo> getShortcuts(@NonNull android.content.pm.LauncherApps.ShortcutQuery, @NonNull android.os.UserHandle); - method @Nullable public android.os.Bundle getSuspendedPackageLauncherExtras(String, android.os.UserHandle); + method @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.os.Bundle getSuspendedPackageLauncherExtras(String, android.os.UserHandle); method public boolean hasShortcutHostPermission(); - method public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); - method public boolean isPackageEnabled(String, android.os.UserHandle); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean isActivityEnabled(android.content.ComponentName, android.os.UserHandle); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean isPackageEnabled(String, android.os.UserHandle); method public void pinShortcuts(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull android.os.UserHandle); - method public void registerCallback(android.content.pm.LauncherApps.Callback); - method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void registerCallback(android.content.pm.LauncherApps.Callback); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler); method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback); - method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle); method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibility(@NonNull android.content.pm.LauncherApps.ArchiveCompatibilityParams); - method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle); - method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); - method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); + method @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle); method public void startPackageInstallerSessionDetailsActivity(@NonNull android.content.pm.PackageInstaller.SessionInfo, @Nullable android.graphics.Rect, @Nullable android.os.Bundle); method public void startShortcut(@NonNull String, @NonNull String, @Nullable android.graphics.Rect, @Nullable android.os.Bundle, @NonNull android.os.UserHandle); method public void startShortcut(@NonNull android.content.pm.ShortcutInfo, @Nullable android.graphics.Rect, @Nullable android.os.Bundle); @@ -20121,9 +20121,14 @@ package android.hardware.camera2.params { ctor public OutputConfiguration(@NonNull android.view.Surface); ctor public OutputConfiguration(int, @NonNull android.view.Surface); ctor public <T> OutputConfiguration(@NonNull android.util.Size, @NonNull Class<T>); + ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, @NonNull android.util.Size); + ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, int, @NonNull android.util.Size); + ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, @NonNull android.util.Size, long); + ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public OutputConfiguration(int, int, @NonNull android.util.Size, long); method public void addSensorPixelModeUsed(int); method public void addSurface(@NonNull android.view.Surface); method @NonNull public static java.util.Collection<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull android.hardware.camera2.MultiResolutionImageReader); + method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") @NonNull public static java.util.List<android.hardware.camera2.params.OutputConfiguration> createInstancesForMultiResolutionOutput(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int); method public int describeContents(); method public void enableSurfaceSharing(); method public long getDynamicRangeProfile(); @@ -20142,6 +20147,7 @@ package android.hardware.camera2.params { method public void setPhysicalCameraId(@Nullable String); method public void setReadoutTimestampEnabled(boolean); method public void setStreamUseCase(long); + method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public static void setSurfacesForMultiResolutionOutput(@NonNull java.util.Collection<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.MultiResolutionImageReader); method public void setTimestampBase(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; @@ -20203,6 +20209,7 @@ package android.hardware.camera2.params { public final class SessionConfiguration implements android.os.Parcelable { ctor public SessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback); + ctor @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public SessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>); method public void clearColorSpace(); method public int describeContents(); method @Nullable public android.graphics.ColorSpace getColorSpace(); @@ -20212,6 +20219,7 @@ package android.hardware.camera2.params { method public android.hardware.camera2.CaptureRequest getSessionParameters(); method public int getSessionType(); method public android.hardware.camera2.CameraCaptureSession.StateCallback getStateCallback(); + method @FlaggedApi("com.android.internal.camera.flags.camera_device_setup") public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback); method public void setColorSpace(@NonNull android.graphics.ColorSpace.Named); method public void setInputConfiguration(@NonNull android.hardware.camera2.params.InputConfiguration); method public void setSessionParameters(android.hardware.camera2.CaptureRequest); @@ -33960,6 +33968,7 @@ package android.os { method public static boolean supportsMultipleUsers(); field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking"; field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; + field @FlaggedApi("android.os.allow_private_profile") public static final String DISALLOW_ADD_PRIVATE_PROFILE = "no_add_private_profile"; field public static final String DISALLOW_ADD_USER = "no_add_user"; field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config"; field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; @@ -52193,7 +52202,7 @@ package android.view { method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction addTransactionCompletedListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.SurfaceControl.TransactionStats>); method public void apply(); method @NonNull public android.view.SurfaceControl.Transaction clearFrameRate(@NonNull android.view.SurfaceControl); - method @NonNull public android.view.SurfaceControl.Transaction clearTrustedPresentationCallback(@NonNull android.view.SurfaceControl); + method @Deprecated @NonNull public android.view.SurfaceControl.Transaction clearTrustedPresentationCallback(@NonNull android.view.SurfaceControl); method public void close(); method public int describeContents(); method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction); @@ -52218,7 +52227,7 @@ package android.view { method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean); method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float); method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float); - method @NonNull public android.view.SurfaceControl.Transaction setTrustedPresentationCallback(@NonNull android.view.SurfaceControl, @NonNull android.view.SurfaceControl.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setTrustedPresentationCallback(@NonNull android.view.SurfaceControl, @NonNull android.view.SurfaceControl.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR; @@ -52233,8 +52242,8 @@ package android.view { method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.hardware.SyncFence getPresentFence(); } - public static final class SurfaceControl.TrustedPresentationThresholds { - ctor public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int); + @Deprecated public static final class SurfaceControl.TrustedPresentationThresholds { + ctor @Deprecated public SurfaceControl.TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int); } @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public interface SurfaceControlInputReceiver { @@ -52249,7 +52258,7 @@ package android.view { method public void relayout(int, int); method public void release(); method public void setView(@NonNull android.view.View, int, int); - method public boolean transferTouchGestureToHost(); + method @Deprecated public boolean transferTouchGestureToHost(); } public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable { @@ -52308,7 +52317,7 @@ package android.view { ctor public SurfaceView(android.content.Context, android.util.AttributeSet, int, int); method public void applyTransactionToFrame(@NonNull android.view.SurfaceControl.Transaction); method public android.view.SurfaceHolder getHolder(); - method @Nullable public android.os.IBinder getHostToken(); + method @Deprecated @Nullable public android.os.IBinder getHostToken(); method public android.view.SurfaceControl getSurfaceControl(); method public void setChildSurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage); method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 78ac7749fc89..9039bf169f8b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -7,6 +7,7 @@ package android { field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO"; field public static final String ACCESS_BROADCAST_RESPONSE_STATS = "android.permission.ACCESS_BROADCAST_RESPONSE_STATS"; field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM"; + field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String ACCESS_CONTEXTUAL_SEARCH = "android.permission.ACCESS_CONTEXTUAL_SEARCH"; field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB"; field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES"; field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO"; @@ -198,7 +199,6 @@ package android { field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"; field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"; - field @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") public static final String MANAGE_DEVICE_POLICY_THEFT_DETECTION = "android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION"; field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"; field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; @@ -282,6 +282,7 @@ package android { field @FlaggedApi("android.content.pm.quarantined_enabled") public static final String QUARANTINE_APPS = "android.permission.QUARANTINE_APPS"; field public static final String QUERY_ADMIN_POLICY = "android.permission.QUERY_ADMIN_POLICY"; field public static final String QUERY_CLONED_APPS = "android.permission.QUERY_CLONED_APPS"; + field @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") public static final String QUERY_DEVICE_STOLEN_STATE = "android.permission.QUERY_DEVICE_STOLEN_STATE"; field @Deprecated public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES"; field public static final String QUERY_USERS = "android.permission.QUERY_USERS"; field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION"; @@ -1328,12 +1329,12 @@ package android.app.admin { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState(); method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled(); method public boolean isDeviceManaged(); + method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE) public boolean isDevicePotentiallyStolen(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean isDpcDownloaded(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isManagedKiosk(); method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle); - method @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION) public boolean isTheftDetectionTriggered(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isUnattendedManagedKiosk(); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); @@ -2176,6 +2177,39 @@ package android.app.contentsuggestions { } +package android.app.contextualsearch { + + @FlaggedApi("android.app.contextualsearch.flags.enable_service") public class ContextualSearchManager { + method public void getContextualSearchState(@NonNull android.os.IBinder, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>); + method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int); + field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH"; + field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2 + field public static final int ENTRYPOINT_LONG_PRESS_META = 10; // 0xa + field public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1; // 0x1 + field public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3; // 0x3 + field public static final int ENTRYPOINT_OVERVIEW_ACTION = 4; // 0x4 + field public static final int ENTRYPOINT_OVERVIEW_MENU = 5; // 0x5 + field public static final int ENTRYPOINT_SYSTEM_ACTION = 9; // 0x9 + field public static final String EXTRA_ENTRYPOINT = "android.app.contextualsearch.extra.ENTRYPOINT"; + field public static final String EXTRA_FLAG_SECURE_FOUND = "android.app.contextualsearch.extra.FLAG_SECURE_FOUND"; + field public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE = "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE"; + field public static final String EXTRA_SCREENSHOT = "android.app.contextualsearch.extra.SCREENSHOT"; + field public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN"; + field public static final String EXTRA_VISIBLE_PACKAGE_NAMES = "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES"; + } + + @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchState implements android.os.Parcelable { + ctor public ContextualSearchState(@Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent, @NonNull android.os.Bundle); + method public int describeContents(); + method @Nullable public android.app.assist.AssistContent getContent(); + method @NonNull public android.os.Bundle getExtras(); + method @Nullable public android.app.assist.AssistStructure getStructure(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.ContextualSearchState> CREATOR; + } + +} + package android.app.job { public abstract class JobScheduler { @@ -3747,6 +3781,7 @@ package android.content { field public static final String CLOUDSEARCH_SERVICE = "cloudsearch"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; + field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search"; field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation"; field public static final String ETHERNET_SERVICE = "ethernet"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; @@ -5006,7 +5041,7 @@ package android.hardware.devicestate { } public static interface DeviceStateManager.DeviceStateCallback { - method public default void onDeviceStateChanged(@NonNull android.hardware.devicestate.DeviceState); + method public void onDeviceStateChanged(@NonNull android.hardware.devicestate.DeviceState); method public default void onSupportedStatesChanged(@NonNull java.util.List<android.hardware.devicestate.DeviceState>); } @@ -12979,11 +13014,11 @@ package android.service.persistentdata { method public long getMaximumDataBlockSize(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName(); - method public byte[] read(); + method @Nullable public byte[] read(); method @FlaggedApi("android.security.frp_enforcement") public boolean setFactoryResetProtectionSecret(@NonNull byte[]); method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean); method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe(); - method public int write(byte[]); + method public int write(@Nullable byte[]); field public static final int FLASH_LOCK_LOCKED = 1; // 0x1 field public static final int FLASH_LOCK_UNKNOWN = -1; // 0xffffffff field public static final int FLASH_LOCK_UNLOCKED = 0; // 0x0 @@ -13936,9 +13971,8 @@ package android.telecom { } @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class DisconnectCause.Builder { - ctor public DisconnectCause.Builder(); + ctor public DisconnectCause.Builder(int); method @NonNull public android.telecom.DisconnectCause build(); - method @NonNull public android.telecom.DisconnectCause.Builder setCode(int); method @NonNull public android.telecom.DisconnectCause.Builder setDescription(@Nullable CharSequence); method @NonNull public android.telecom.DisconnectCause.Builder setImsReasonInfo(@Nullable android.telephony.ims.ImsReasonInfo); method @NonNull public android.telecom.DisconnectCause.Builder setLabel(@Nullable CharSequence); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index bc45a76d861b..c1af719e91c9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1625,25 +1625,37 @@ package android.hardware.camera2.params { package android.hardware.devicestate { @FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceState { + ctor public DeviceState(@NonNull android.hardware.devicestate.DeviceState.Configuration); field public static final int PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST = 8; // 0x8 } + public static final class DeviceState.Configuration implements android.os.Parcelable { + method public int describeContents(); + method public int getIdentifier(); + method @NonNull public String getName(); + method @NonNull public java.util.Set<java.lang.Integer> getPhysicalProperties(); + method @NonNull public java.util.Set<java.lang.Integer> getSystemProperties(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.devicestate.DeviceState.Configuration> CREATOR; + } + + public static final class DeviceState.Configuration.Builder { + ctor public DeviceState.Configuration.Builder(int, @NonNull String); + method @NonNull public android.hardware.devicestate.DeviceState.Configuration build(); + method @NonNull public android.hardware.devicestate.DeviceState.Configuration.Builder setPhysicalProperties(@NonNull java.util.Set<java.lang.Integer>); + method @NonNull public android.hardware.devicestate.DeviceState.Configuration.Builder setSystemProperties(@NonNull java.util.Set<java.lang.Integer>); + } + @FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceStateManager { method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride(); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest(); - method @Deprecated @NonNull public int[] getSupportedStates(); method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestBaseStateOverride(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); + field public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1; // 0xffffffff field public static final int MAXIMUM_DEVICE_STATE_IDENTIFIER = 10000; // 0x2710 field public static final int MINIMUM_DEVICE_STATE_IDENTIFIER = 0; // 0x0 } - public static interface DeviceStateManager.DeviceStateCallback { - method @Deprecated public default void onBaseStateChanged(int); - method @Deprecated public void onStateChanged(int); - method @Deprecated public default void onSupportedStatesChanged(@NonNull int[]); - } - public final class DeviceStateRequest { method public int getFlags(); method public int getState(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2afc78cb90e6..a8352fad8a90 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2951,7 +2951,9 @@ public class AppOpsManager { new AppOpInfo.Builder(OP_ESTABLISH_VPN_MANAGER, OPSTR_ESTABLISH_VPN_MANAGER, "ESTABLISH_VPN_MANAGER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS, - "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED) + "ACCESS_RESTRICTED_SETTINGS").setDefaultMode( + android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + ? MODE_DEFAULT : MODE_ALLOWED) .setDisableReset(true).setRestrictRead(true).build(), new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED) diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index df566db5ba97..6f6e0911fa4b 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -272,10 +272,15 @@ class ContextImpl extends Context { @UnsupportedAppUsage private Context mOuterContext; + + private final Object mThemeLock = new Object(); @UnsupportedAppUsage + @GuardedBy("mThemeLock") private int mThemeResource = 0; @UnsupportedAppUsage + @GuardedBy("mThemeLock") private Resources.Theme mTheme = null; + @UnsupportedAppUsage private PackageManager mPackageManager; private Context mReceiverRestrictedContext = null; @@ -288,7 +293,6 @@ class ContextImpl extends Context { private ContentCaptureOptions mContentCaptureOptions = null; - private final Object mSync = new Object(); /** * Indicates this {@link Context} can not handle UI components properly and is not associated * with a {@link Display} instance. @@ -340,20 +344,21 @@ class ContextImpl extends Context { */ private boolean mOwnsToken = false; - @GuardedBy("mSync") + private final Object mDirsLock = new Object(); + @GuardedBy("mDirsLock") private File mDatabasesDir; - @GuardedBy("mSync") + @GuardedBy("mDirsLock") @UnsupportedAppUsage private File mPreferencesDir; - @GuardedBy("mSync") + @GuardedBy("mDirsLock") private File mFilesDir; - @GuardedBy("mSync") + @GuardedBy("mDirsLock") private File mCratesDir; - @GuardedBy("mSync") + @GuardedBy("mDirsLock") private File mNoBackupFilesDir; - @GuardedBy("mSync") + @GuardedBy("mDirsLock") private File mCacheDir; - @GuardedBy("mSync") + @GuardedBy("mDirsLock") private File mCodeCacheDir; // The system service cache for the system services that are cached per-ContextImpl. @@ -458,7 +463,7 @@ class ContextImpl extends Context { @Override public void setTheme(int resId) { - synchronized (mSync) { + synchronized (mThemeLock) { if (mThemeResource != resId) { mThemeResource = resId; initializeTheme(); @@ -468,14 +473,14 @@ class ContextImpl extends Context { @Override public int getThemeResId() { - synchronized (mSync) { + synchronized (mThemeLock) { return mThemeResource; } } @Override public Resources.Theme getTheme() { - synchronized (mSync) { + synchronized (mThemeLock) { if (mTheme != null) { return mTheme; } @@ -737,7 +742,7 @@ class ContextImpl extends Context { @UnsupportedAppUsage private File getPreferencesDir() { - synchronized (mSync) { + synchronized (mDirsLock) { if (mPreferencesDir == null) { mPreferencesDir = new File(getDataDir(), "shared_prefs"); } @@ -826,7 +831,7 @@ class ContextImpl extends Context { @Override public File getFilesDir() { - synchronized (mSync) { + synchronized (mDirsLock) { if (mFilesDir == null) { mFilesDir = new File(getDataDir(), "files"); } @@ -841,7 +846,7 @@ class ContextImpl extends Context { final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId) .toAbsolutePath().normalize(); - synchronized (mSync) { + synchronized (mDirsLock) { if (mCratesDir == null) { mCratesDir = cratesRootPath.toFile(); } @@ -854,7 +859,7 @@ class ContextImpl extends Context { @Override public File getNoBackupFilesDir() { - synchronized (mSync) { + synchronized (mDirsLock) { if (mNoBackupFilesDir == null) { mNoBackupFilesDir = new File(getDataDir(), "no_backup"); } @@ -871,7 +876,7 @@ class ContextImpl extends Context { @Override public File[] getExternalFilesDirs(String type) { - synchronized (mSync) { + synchronized (mDirsLock) { File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName()); if (type != null) { dirs = Environment.buildPaths(dirs, type); @@ -889,7 +894,7 @@ class ContextImpl extends Context { @Override public File[] getObbDirs() { - synchronized (mSync) { + synchronized (mDirsLock) { File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName()); return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */); } @@ -897,7 +902,7 @@ class ContextImpl extends Context { @Override public File getCacheDir() { - synchronized (mSync) { + synchronized (mDirsLock) { if (mCacheDir == null) { mCacheDir = new File(getDataDir(), "cache"); } @@ -907,7 +912,7 @@ class ContextImpl extends Context { @Override public File getCodeCacheDir() { - synchronized (mSync) { + synchronized (mDirsLock) { if (mCodeCacheDir == null) { mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir()); } @@ -933,7 +938,7 @@ class ContextImpl extends Context { @Override public File[] getExternalCacheDirs() { - synchronized (mSync) { + synchronized (mDirsLock) { File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName()); // We don't try to create cache directories in-process, because they need special // setup for accurate quota tracking. This ensures the cache dirs are always @@ -944,7 +949,7 @@ class ContextImpl extends Context { @Override public File[] getExternalMediaDirs() { - synchronized (mSync) { + synchronized (mDirsLock) { File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName()); return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */); } @@ -1046,7 +1051,7 @@ class ContextImpl extends Context { } private File getDatabasesDir() { - synchronized (mSync) { + synchronized (mDirsLock) { if (mDatabasesDir == null) { if ("android".equals(getPackageName())) { mDatabasesDir = new File("/data/system"); diff --git a/core/java/android/app/ForegroundServiceDelegationOptions.java b/core/java/android/app/ForegroundServiceDelegationOptions.java index 875e01f32f54..d6b6a5844ff1 100644 --- a/core/java/android/app/ForegroundServiceDelegationOptions.java +++ b/core/java/android/app/ForegroundServiceDelegationOptions.java @@ -92,6 +92,16 @@ public class ForegroundServiceDelegationOptions { */ public final @DelegationService int mDelegationService; + /** + * The optional notification Id of the foreground service delegation. + */ + public final int mClientNotificationId; + + /** + * The optional notification of the foreground service delegation. + */ + public final @Nullable Notification mClientNotification; + public ForegroundServiceDelegationOptions(int clientPid, int clientUid, @NonNull String clientPackageName, @@ -100,6 +110,21 @@ public class ForegroundServiceDelegationOptions { @NonNull String clientInstanceName, int foregroundServiceTypes, @DelegationService int delegationService) { + this(clientPid, clientUid, clientPackageName, clientAppThread, isSticky, + clientInstanceName, foregroundServiceTypes, delegationService, + 0 /* notificationId */, null /* notification */); + } + + public ForegroundServiceDelegationOptions(int clientPid, + int clientUid, + @NonNull String clientPackageName, + @NonNull IApplicationThread clientAppThread, + boolean isSticky, + @NonNull String clientInstanceName, + int foregroundServiceTypes, + @DelegationService int delegationService, + int clientNotificationId, + @Nullable Notification clientNotification) { mClientPid = clientPid; mClientUid = clientUid; mClientPackageName = clientPackageName; @@ -108,6 +133,8 @@ public class ForegroundServiceDelegationOptions { mClientInstanceName = clientInstanceName; mForegroundServiceTypes = foregroundServiceTypes; mDelegationService = delegationService; + mClientNotificationId = clientNotificationId; + mClientNotification = clientNotification; } /** @@ -201,7 +228,8 @@ public class ForegroundServiceDelegationOptions { int mClientPid; // The actual app PID int mClientUid; // The actual app UID String mClientPackageName; // The actual app's package name - int mClientNotificationId; // The actual app's notification + int mClientNotificationId; // The actual app's notification id + Notification mClientNotification; // The actual app's notification IApplicationThread mClientAppThread; // The actual app's app thread boolean mSticky; // Is it a sticky service String mClientInstanceName; // The delegation service instance name @@ -233,10 +261,12 @@ public class ForegroundServiceDelegationOptions { } /** - * Set the notification ID from the client app. + * Set the notification from the client app. */ - public Builder setClientNotificationId(int clientNotificationId) { + public Builder setClientNotification(int clientNotificationId, + @Nullable Notification clientNotification) { mClientNotificationId = clientNotificationId; + mClientNotification = clientNotification; return this; } @@ -291,7 +321,9 @@ public class ForegroundServiceDelegationOptions { mSticky, mClientInstanceName, mForegroundServiceTypes, - mDelegationService + mDelegationService, + mClientNotificationId, + mClientNotification ); } } diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 9f2e4739e8e6..7c803ebde384 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -776,7 +776,8 @@ public final class NotificationChannel implements Parcelable { /** * Whether or not notifications posted to this channel can bypass the Do Not Disturb - * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode. + * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode when the active policy allows + * priority channels to bypass notification filtering. */ public boolean canBypassDnd() { return mBypassDnd; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index fa4a4009ebc9..66269a52f0c7 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -32,6 +32,7 @@ import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IContentSuggestionsManager; +import android.app.contextualsearch.ContextualSearchManager; import android.app.ecm.EnhancedConfirmationFrameworkInitializer; import android.app.job.JobSchedulerFrameworkInitializer; import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager; @@ -1282,6 +1283,16 @@ public final class SystemServiceRegistry { } }); + registerService(Context.CONTEXTUAL_SEARCH_SERVICE, ContextualSearchManager.class, + new CachedServiceFetcher<>() { + @Override + public ContextualSearchManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE); + return b == null ? null : new ContextualSearchManager(); + } + }); + registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class, new CachedServiceFetcher<AppPredictionManager>() { @Override diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index cb4ed058af33..5b28f50b5152 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -45,11 +45,11 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SECURITY_LOGGING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STATUS_BAR; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES; -import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA; import static android.Manifest.permission.QUERY_ADMIN_POLICY; +import static android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; @@ -5952,7 +5952,8 @@ public class DevicePolicyManager { * <p> * This method can be called on the {@link DevicePolicyManager} instance returned by * {@link #getParentProfileInstance(ComponentName)} in order to set a value on the parent - * profile. + * profile. This allows a profile wipe after too many incorrect device-unlock password have + * been entered on the parent profile even if each profile has a separate challenge. * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the * password is always empty and this method has no effect - i.e. the policy is not set. * @@ -9979,17 +9980,22 @@ public class DevicePolicyManager { /** * Called by a profile owner or device owner or holder of the permission * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_TASK}. to set a default activity - * that the system selects to handle intents that match the given {@link IntentFilter}. + * that the system selects to handle intents that match the given {@link IntentFilter} instead + * of showing the default disambiguation mechanism. * This activity will remain the default intent handler even if the set of potential event * handlers for the intent filter changes and if the intent preferences are reset. * <p> - * Note that the caller should still declare the activity in the manifest, the API just sets - * the activity to be the default one to handle the given intent filter. + * Note that the target application should still declare the activity in the manifest, the API + * just sets the activity to be the default one to handle the given intent filter. * <p> * The default disambiguation mechanism takes over if the activity is not installed (anymore). * When the activity is (re)installed, it is automatically reset as default intent handler for * the filter. * <p> + * Note that calling this API to set a default intent handler, only allow to avoid the default + * disambiguation mechanism. Implicit intents that do not trigger this mechanism (like invoking + * the browser) cannot be configured as they are controlled by other configurations. + * <p> * The calling device admin must be a profile owner or device owner. If it is not, a security * exception will be thrown. * <p> @@ -17152,15 +17158,15 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(value = MANAGE_DEVICE_POLICY_THEFT_DETECTION) + @RequiresPermission(value = QUERY_DEVICE_STOLEN_STATE) @FlaggedApi(FLAG_DEVICE_THEFT_API_ENABLED) - public boolean isTheftDetectionTriggered() { - throwIfParentInstance("isTheftDetectionTriggered"); + public boolean isDevicePotentiallyStolen() { + throwIfParentInstance("isDevicePotentiallyStolen"); if (mService == null) { return false; } try { - return mService.isTheftDetectionTriggered(mContext.getPackageName()); + return mService.isDevicePotentiallyStolen(mContext.getPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 03d0b0f88bc0..aea0246e15c8 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -581,7 +581,7 @@ interface IDevicePolicyManager { void setWifiSsidPolicy(String callerPackageName, in WifiSsidPolicy policy); WifiSsidPolicy getWifiSsidPolicy(String callerPackageName); - boolean isTheftDetectionTriggered(String callerPackageName); + boolean isDevicePotentiallyStolen(String callerPackageName); List<UserHandle> listForegroundAffiliatedUsers(); void setDrawables(in List<DevicePolicyDrawableResource> drawables); diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java new file mode 100644 index 000000000000..693de219e7d7 --- /dev/null +++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2024 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.contextualsearch; + +import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH; + +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.app.contextualsearch.flags.Flags; +import android.content.Context; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.OutcomeReceiver; +import android.os.ParcelableException; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * {@link ContextualSearchManager} is a system service to facilitate contextual search experience on + * configured Android devices. + * <p> + * This class lets + * <ul> + * <li> a caller start contextual search by calling {@link #startContextualSearch} method. + * <li> a handler request {@link ContextualSearchState} by calling the + * {@link #getContextualSearchState} method. + * </ul> + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) +public class ContextualSearchManager { + + /** + * Key to get the entrypoint from the extras of the activity launched by contextual search. + * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + */ + public static final String EXTRA_ENTRYPOINT = + "android.app.contextualsearch.extra.ENTRYPOINT"; + /** + * Key to get the flag_secure value from the extras of the activity launched by contextual + * search. The value will be true if flag_secure is found in any of the visible activities. + * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + */ + public static final String EXTRA_FLAG_SECURE_FOUND = + "android.app.contextualsearch.extra.FLAG_SECURE_FOUND"; + /** + * Key to get the screenshot from the extras of the activity launched by contextual search. + * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + */ + public static final String EXTRA_SCREENSHOT = + "android.app.contextualsearch.extra.SCREENSHOT"; + /** + * Key to check whether managed profile is visible from the extras of the activity launched by + * contextual search. The value will be true if any one of the visible apps is managed. + * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + */ + public static final String EXTRA_IS_MANAGED_PROFILE_VISIBLE = + "android.app.contextualsearch.extra.IS_MANAGED_PROFILE_VISIBLE"; + /** + * Key to get the list of visible packages from the extras of the activity launched by + * contextual search. + * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + */ + public static final String EXTRA_VISIBLE_PACKAGE_NAMES = + "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES"; + + /** + * Key to get the binder token from the extras of the activity launched by contextual search. + * This token is needed to invoke {@link #getContextualSearchState} method. + * Only supposed to be used with ACTON_LAUNCH_CONTEXTUAL_SEARCH. + */ + public static final String EXTRA_TOKEN = "android.app.contextualsearch.extra.TOKEN"; + /** + * Intent action for contextual search invocation. The app providing the contextual search + * experience must add this intent filter action to the activity it wants to be launched. + * <br> + * <b>Note</b> This activity must not be exported. + */ + public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = + "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH"; + + /** Entrypoint to be used when a user long presses on the nav handle. */ + public static final int ENTRYPOINT_LONG_PRESS_NAV_HANDLE = 1; + /** Entrypoint to be used when a user long presses on the home button. */ + public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; + /** Entrypoint to be used when a user long presses on the overview button. */ + public static final int ENTRYPOINT_LONG_PRESS_OVERVIEW = 3; + /** Entrypoint to be used when a user presses the action button in overview. */ + public static final int ENTRYPOINT_OVERVIEW_ACTION = 4; + /** Entrypoint to be used when a user presses the context menu button in overview. */ + public static final int ENTRYPOINT_OVERVIEW_MENU = 5; + /** Entrypoint to be used by system actions like TalkBack, Accessibility etc. */ + public static final int ENTRYPOINT_SYSTEM_ACTION = 9; + /** Entrypoint to be used when a user long presses on the meta key. */ + public static final int ENTRYPOINT_LONG_PRESS_META = 10; + /** + * The {@link Entrypoint} annotation is used to standardize the entrypoints supported by + * {@link #startContextualSearch} method. + * + * @hide + */ + @IntDef(prefix = {"ENTRYPOINT_"}, value = { + ENTRYPOINT_LONG_PRESS_NAV_HANDLE, + ENTRYPOINT_LONG_PRESS_HOME, + ENTRYPOINT_LONG_PRESS_OVERVIEW, + ENTRYPOINT_OVERVIEW_ACTION, + ENTRYPOINT_OVERVIEW_MENU, + ENTRYPOINT_SYSTEM_ACTION, + ENTRYPOINT_LONG_PRESS_META + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Entrypoint { + } + private static final String TAG = ContextualSearchManager.class.getSimpleName(); + private static final boolean DEBUG = false; + + private final IContextualSearchManager mService; + + /** @hide */ + public ContextualSearchManager() { + if (DEBUG) Log.d(TAG, "ContextualSearchManager created"); + IBinder b = ServiceManager.getService(Context.CONTEXTUAL_SEARCH_SERVICE); + mService = IContextualSearchManager.Stub.asInterface(b); + } + + /** + * Used to start contextual search. + * <p> + * When {@link #startContextualSearch} is called, the system server does the following: + * <ul> + * <li>Resolves the activity using the package name and intent filter. The package name + * is fetched from the config specified in ContextualSearchManagerService. + * The activity must have ACTION_LAUNCH_CONTEXTUAL_SEARCH specified in its manifest. + * <li>Puts the required extras in the launch intent. + * <li>Launches the activity. + * </ul> + * </p> + * + * @param entrypoint the invocation entrypoint + */ + @RequiresPermission(ACCESS_CONTEXTUAL_SEARCH) + public void startContextualSearch(@Entrypoint int entrypoint) { + if (DEBUG) Log.d(TAG, "startContextualSearch for entrypoint: " + entrypoint); + try { + mService.startContextualSearch(entrypoint); + } catch (RemoteException e) { + if (DEBUG) Log.d(TAG, "Failed to startContextualSearch", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Returns the {@link ContextualSearchState} to the handler via the provided callback. + * + * @param token The caller is expected to get the token from the launch extras of the handling + * activity using {@link Bundle#getIBinder} with {@link #EXTRA_TOKEN} key. + * <br> + * <b>Note</b> This token is for one time use only. Subsequent uses will invoke + * callback's {@link OutcomeReceiver#onError}. + * @param executor The executor which will be used to invoke the callback. + * @param callback The callback which will be used to return {@link ContextualSearchState} + * if/when it is available via {@link OutcomeReceiver#onResult}. It will also be + * used to return errors via {@link OutcomeReceiver#onError}. + */ + public void getContextualSearchState(@NonNull IBinder token, + @NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) { + if (DEBUG) Log.d(TAG, "getContextualSearchState for token:" + token); + try { + final CallbackWrapper wrapper = new CallbackWrapper(executor, callback); + mService.getContextualSearchState(token, wrapper); + } catch (RemoteException e) { + if (DEBUG) Log.d(TAG, "Failed to getContextualSearchState", e); + e.rethrowFromSystemServer(); + } + } + + private static class CallbackWrapper extends IContextualSearchCallback.Stub { + private final OutcomeReceiver<ContextualSearchState, Throwable> mCallback; + private final Executor mExecutor; + + CallbackWrapper(@NonNull Executor callbackExecutor, + @NonNull OutcomeReceiver<ContextualSearchState, Throwable> callback) { + mCallback = callback; + mExecutor = callbackExecutor; + } + + @Override + public void onResult(ContextualSearchState state) { + Binder.withCleanCallingIdentity(() -> { + if (DEBUG) Log.d(TAG, "onResult state:" + state); + mExecutor.execute(() -> mCallback.onResult(state)); + }); + } + + @Override + public void onError(ParcelableException error) { + Binder.withCleanCallingIdentity(() -> { + if (DEBUG) Log.w(TAG, "onError", error); + mExecutor.execute(() -> mCallback.onError(error)); + }); + } + } +} diff --git a/core/java/android/app/contextualsearch/ContextualSearchState.aidl b/core/java/android/app/contextualsearch/ContextualSearchState.aidl new file mode 100644 index 000000000000..7f64484b0f38 --- /dev/null +++ b/core/java/android/app/contextualsearch/ContextualSearchState.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contextualsearch; + +parcelable ContextualSearchState;
\ No newline at end of file diff --git a/core/java/android/app/contextualsearch/ContextualSearchState.java b/core/java/android/app/contextualsearch/ContextualSearchState.java new file mode 100644 index 000000000000..5c04bc8933c0 --- /dev/null +++ b/core/java/android/app/contextualsearch/ContextualSearchState.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 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.contextualsearch; + +import android.annotation.FlaggedApi; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; +import android.app.contextualsearch.flags.Flags; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +/** + * {@link ContextualSearchState} contains additional data a contextual search handler can request + * via {@link ContextualSearchManager#getContextualSearchState} method. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) +@SystemApi +public final class ContextualSearchState implements Parcelable { + private final @NonNull Bundle mExtras; + private final @Nullable AssistStructure mStructure; + private final @Nullable AssistContent mContent; + + public ContextualSearchState(@Nullable AssistStructure structure, + @Nullable AssistContent content, @NonNull Bundle extras) { + mStructure = structure; + mContent = content; + mExtras = extras; + } + + private ContextualSearchState(Parcel source) { + this.mStructure = source.readTypedObject(AssistStructure.CREATOR); + this.mContent = source.readTypedObject(AssistContent.CREATOR); + Bundle extras = source.readBundle(getClass().getClassLoader()); + this.mExtras = extras != null ? extras : Bundle.EMPTY; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(this.mStructure, flags); + dest.writeTypedObject(this.mContent, flags); + dest.writeBundle(this.mExtras); + } + + /** Gets an instance of {@link AssistContent}. */ + @Nullable + public AssistContent getContent() { + return mContent; + } + + /** Gets an instance of {@link AssistStructure}. */ + @Nullable + public AssistStructure getStructure() { + return mStructure; + } + + /** + * Gets an instance of {@link Bundle} containing the extras added by the system server. + * The contents of this bundle vary by usecase. When Contextual is invoked via Launcher, this + * bundle is empty. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + @NonNull + public static final Creator<ContextualSearchState> CREATOR = new Creator<>() { + @Override + public ContextualSearchState createFromParcel(Parcel source) { + return new ContextualSearchState(source); + } + + @Override + public ContextualSearchState[] newArray(int size) { + return new ContextualSearchState[size]; + } + }; +} diff --git a/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl b/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl new file mode 100644 index 000000000000..5fe5fd23c501 --- /dev/null +++ b/core/java/android/app/contextualsearch/IContextualSearchCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.contextualsearch; + +import android.os.ParcelableException; +import android.app.contextualsearch.ContextualSearchState; +/** + * @hide + */ +oneway interface IContextualSearchCallback { + void onResult(in ContextualSearchState state); + void onError(in ParcelableException error); +} diff --git a/core/java/android/app/contextualsearch/IContextualSearchManager.aidl b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl new file mode 100644 index 000000000000..1735a7139e6f --- /dev/null +++ b/core/java/android/app/contextualsearch/IContextualSearchManager.aidl @@ -0,0 +1,11 @@ +package android.app.contextualsearch; + + +import android.app.contextualsearch.IContextualSearchCallback; +/** + * @hide + */ +oneway interface IContextualSearchManager { + void startContextualSearch(int entrypoint); + void getContextualSearchState(in IBinder token, in IContextualSearchCallback callback); +} diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig new file mode 100644 index 000000000000..5ab07620bc81 --- /dev/null +++ b/core/java/android/app/contextualsearch/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.app.contextualsearch.flags" + +flag { + name: "enable_service" + namespace: "machine_learning" + description: "Flag to enable the service" + bug: "309689654" +} diff --git a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java index 3e543d27143a..c275cc786007 100644 --- a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java +++ b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java @@ -75,7 +75,11 @@ public final class ProcessingSignal { /** - * Sends a custom signal with the provided parameters. It also signals the remote callback + * Sends a custom signal with the provided parameters. If there are multiple concurrent + * requests to this method, the actionParams are queued in a blocking fashion, in the order they + * are received. + * + * It also signals the remote callback * with the same params if already configured, if not the action is queued to be sent when a * remote is configured. Similarly, on the receiver side, the callback will be invoked if * already set, if not all actions are queued to be sent to callback when it is set. @@ -159,9 +163,9 @@ public final class ProcessingSignal { * Sets the remote transport. * * If there are actions queued from {@link ProcessingSignal#sendSignal}, they are also - * sequentially sent to the remote. + * sequentially sent to the configured remote. * - * This method is guaranteed that the remote transport will not be called after it + * This method guarantees that the remote transport will not be called after it * has been removed. * * @param remote The remote transport, or null to remove. diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java index 193b03ced0b0..fedffe134ce1 100644 --- a/core/java/android/app/servertransaction/WindowStateResizeItem.java +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -28,6 +28,7 @@ import android.content.Context; import android.os.Parcel; import android.os.RemoteException; import android.os.Trace; +import android.util.Log; import android.util.MergedConfiguration; import android.view.IWindow; import android.view.InsetsState; @@ -41,6 +42,8 @@ import java.util.Objects; */ public class WindowStateResizeItem extends ClientTransactionItem { + private static final String TAG = "WindowStateResizeItem"; + private IWindow mWindow; private ClientWindowFrames mFrames; private boolean mReportDraw; @@ -65,7 +68,9 @@ public class WindowStateResizeItem extends ClientTransactionItem { mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing); } catch (RemoteException e) { // Should be a local call. - throw new RuntimeException(e); + // An exception could happen if the process is restarted. It is safe to ignore since + // the window should no longer exist. + Log.w(TAG, "The original window no longer exists in the new process", e); } Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 7f2ec53ea41a..1653bf538435 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5396,6 +5396,19 @@ public abstract class Context { public static final String SMARTSPACE_SERVICE = "smartspace"; /** + * Used for getting the contextual search service. + * + * <p><b>NOTE: </b> this service is optional; callers of + * {@code Context.getSystemServiceName(CONTEXTUAL_SEARCH_SERVICE)} must check for {@code null}. + * + * @hide + * @see #getSystemService(String) + */ + @FlaggedApi(android.app.contextualsearch.flags.Flags.FLAG_ENABLE_SERVICE) + @SystemApi + public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search"; + + /** * Used for getting the cloudsearch service. * * <p><b>NOTE: </b> this service is optional; callers of diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 443aadd505bd..8bc5e8c1ef34 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6129,10 +6129,14 @@ public class Intent implements Parcelable, Cloneable { * the selection changes made by the user. * Applications may implement this method to change any of the following Chooser arguments by * returning new values in the result bundle: - * {@link #EXTRA_CHOOSER_TARGETS}, {@link #EXTRA_ALTERNATE_INTENTS}, + * {@link #EXTRA_CHOOSER_TARGETS}, + * {@link #EXTRA_ALTERNATE_INTENTS}, * {@link #EXTRA_CHOOSER_CUSTOM_ACTIONS}, * {@link #EXTRA_CHOOSER_MODIFY_SHARE_ACTION}, - * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p> + * {@link #EXTRA_METADATA_TEXT}, + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}, + * {@link #EXTRA_CHOOSER_RESULT_INTENT_SENDER}. + * </p> */ @FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING) public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI = diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 3a5383d9537b..39b914975362 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -693,7 +693,12 @@ public class LauncherApps { * * <p>If the caller is running on a managed profile, it'll return only the current profile. * Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would. + * + * <p> To get hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public List<UserHandle> getProfiles() { if (mUserManager.isManagedProfile() || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks() @@ -751,11 +756,17 @@ public class LauncherApps { * list.</li> * </ul> * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @param packageName The specific package to query. If null, it checks all installed packages * in the profile. * @param user The UserHandle of the profile. * @return List of launchable activities. Can be an empty list but will not be null. */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) { logErrorForInvalidProfileAccess(user); try { @@ -925,10 +936,16 @@ public class LauncherApps { * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it * returns null. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @param intent The intent to find a match for. * @param user The profile to look in for a match. * @return An activity info object if there is a match. */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) { logErrorForInvalidProfileAccess(user); try { @@ -978,11 +995,17 @@ public class LauncherApps { /** * Starts a Main activity in the specified profile. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @param component The ComponentName of the activity to launch * @param user The UserHandle of the profile * @param sourceBounds The Rect containing the source bounds of the clicked icon * @param opts Options to pass to startActivity */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public void startMainActivity(ComponentName component, UserHandle user, Rect sourceBounds, Bundle opts) { logErrorForInvalidProfileAccess(user); @@ -1020,11 +1043,17 @@ public class LauncherApps { * Starts the settings activity to show the application details for a * package in the specified profile. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @param component The ComponentName of the package to launch settings for. * @param user The UserHandle of the profile * @param sourceBounds The Rect containing the source bounds of the clicked icon * @param opts Options to pass to startActivity */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public void startAppDetailsActivity(ComponentName component, UserHandle user, Rect sourceBounds, Bundle opts) { logErrorForInvalidProfileAccess(user); @@ -1135,11 +1164,17 @@ public class LauncherApps { /** * Checks if the package is installed and enabled for a profile. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @param packageName The package to check. * @param user The UserHandle of the profile. * * @return true if the package exists and is enabled. */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public boolean isPackageEnabled(String packageName, UserHandle user) { logErrorForInvalidProfileAccess(user); try { @@ -1157,6 +1192,10 @@ public class LauncherApps { * <p>The contents of this {@link Bundle} are supposed to be a contract between the suspending * app and the launcher. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * <p>Note: This just returns whatever extras were provided to the system, <em>which might * even be {@code null}.</em> * @@ -1168,6 +1207,8 @@ public class LauncherApps { * @see Callback#onPackagesSuspended(String[], UserHandle, Bundle) * @see PackageManager#isPackageSuspended() */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public @Nullable Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user) { logErrorForInvalidProfileAccess(user); try { @@ -1182,10 +1223,16 @@ public class LauncherApps { * could be done because the package was marked as distracting to the user via * {@code PackageManager.setDistractingPackageRestrictions(String[], int)}. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @param packageName The package for which to check. * @param user the {@link UserHandle} of the profile. * @return */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public boolean shouldHideFromSuggestions(@NonNull String packageName, @NonNull UserHandle user) { Objects.requireNonNull(packageName, "packageName"); @@ -1200,6 +1247,10 @@ public class LauncherApps { /** * Returns {@link ApplicationInfo} about an application installed for a specific user profile. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @param packageName The package name of the application * @param flags Additional option flags {@link PackageManager#getApplicationInfo} * @param user The UserHandle of the profile. @@ -1208,6 +1259,8 @@ public class LauncherApps { * {@code null} if the package isn't installed for the given profile, or the profile * isn't enabled. */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public ApplicationInfo getApplicationInfo(@NonNull String packageName, @ApplicationInfoFlagsBits int flags, @NonNull UserHandle user) throws PackageManager.NameNotFoundException { @@ -1257,11 +1310,17 @@ public class LauncherApps { * <p>The activity may still not be exported, in which case {@link #startMainActivity} will * throw a {@link SecurityException} unless the caller has the same UID as the target app's. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @param component The activity to check. * @param user The UserHandle of the profile. * * @return true if the activity exists and is enabled. */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public boolean isActivityEnabled(ComponentName component, UserHandle user) { logErrorForInvalidProfileAccess(user); try { @@ -1816,8 +1875,14 @@ public class LauncherApps { /** * Registers a callback for changes to packages in this user and managed profiles. * + * <p>To receive callbacks for hidden profile{@link UserManager.USER_TYPE_PROFILE_PRIVATE}, + * caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the + * permissions required. + * * @param callback The callback to register. */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public void registerCallback(Callback callback) { registerCallback(callback, null); } @@ -1825,9 +1890,15 @@ public class LauncherApps { /** * Registers a callback for changes to packages in this user and managed profiles. * + * <p>To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, + * caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the + * permissions required. + * * @param callback The callback to register. * @param handler that should be used to post callbacks on, may be null. */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public void registerCallback(Callback callback, Handler handler) { synchronized (this) { if (callback != null && findCallbackLocked(callback) < 0) { @@ -2275,8 +2346,14 @@ public class LauncherApps { * package name in the app's manifest, have the android.permission.QUERY_ALL_PACKAGES, or be * the session owner to retrieve these details. * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * * @see PackageInstaller#getAllSessions() */ + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public @NonNull List<SessionInfo> getAllPackageInstallerSessions() { try { return mService.getAllSessions(mContext.getPackageName()).getList(); diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 48a7cc920f1d..2d32aed5a1ad 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -108,6 +108,7 @@ flag { is_fixed_read_only: true } +# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile. flag { name: "enable_private_space_features" namespace: "profile_experiences" @@ -192,3 +193,10 @@ flag { bug: "290333800" is_fixed_read_only: true } + +flag { + name: "delete_private_space_from_reset" + namespace: "profile_experiences" + description: "Add entrypoint in Settings Reset options for deleting private space when lock is forgotten" + bug: "329601751" +} diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index dfb77c046c9a..faa2c7018d6f 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -34,22 +34,17 @@ import android.util.Log; import android.util.LruCache; import android.util.Pair; import android.util.Printer; -import com.android.internal.util.RingBuffer; import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; - import java.io.File; import java.io.IOException; import java.lang.ref.Reference; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; -import java.util.Locale; import java.util.Map; import java.util.function.BinaryOperator; import java.util.function.UnaryOperator; @@ -190,7 +185,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection) { mPool = pool; - mRecentOperations = new OperationLog(); + mRecentOperations = new OperationLog(mPool); mConfiguration = new SQLiteDatabaseConfiguration(configuration); mConnectionId = connectionId; mIsPrimaryConnection = primaryConnection; @@ -312,16 +307,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - /** Record the start of a transaction for logging and debugging. */ - void recordBeginTransaction(String mode) { - mRecentOperations.beginTransaction(mode); - } - - /** Record the end of a transaction for logging and debugging. */ - void recordEndTransaction(boolean successful) { - mRecentOperations.endTransaction(successful); - } - private void setPageSize() { if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { final long newValue = SQLiteGlobal.getDefaultPageSize(); @@ -1352,7 +1337,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } printer.println(" isPrimaryConnection: " + mIsPrimaryConnection); printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations); - printer.println(" totalLongOperations: " + mRecentOperations.getTotalLongOperations()); mRecentOperations.dump(printer); @@ -1611,39 +1595,51 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - private final class OperationLog { + private static final class OperationLog { private static final int MAX_RECENT_OPERATIONS = 20; private static final int COOKIE_GENERATION_SHIFT = 8; private static final int COOKIE_INDEX_MASK = 0xff; - // Operations over 2s are long. Save the last ten. - private static final long LONG_OPERATION_THRESHOLD_MS = 2_000; - private static final int MAX_LONG_OPERATIONS = 10; - private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS]; - private int mIndex = -1; - private int mGeneration = 0; - private final Operation mTransaction = new Operation(); + private int mIndex; + private int mGeneration; + private final SQLiteConnectionPool mPool; private long mResultLong = Long.MIN_VALUE; private String mResultString; - private final RingBuffer<Operation> mLongOperations = - new RingBuffer<>(()->{return new Operation();}, - (n) ->{return new Operation[n];}, - MAX_LONG_OPERATIONS); - private int mTotalLongOperations = 0; + OperationLog(SQLiteConnectionPool pool) { + mPool = pool; + } public int beginOperation(String kind, String sql, Object[] bindArgs) { mResultLong = Long.MIN_VALUE; mResultString = null; synchronized (mOperations) { - Operation operation = newOperationLocked(); + final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS; + Operation operation = mOperations[index]; + if (operation == null) { + operation = new Operation(); + mOperations[index] = operation; + } else { + operation.mFinished = false; + operation.mException = null; + if (operation.mBindArgs != null) { + operation.mBindArgs.clear(); + } + } + operation.mStartWallTime = System.currentTimeMillis(); + operation.mStartTime = SystemClock.uptimeMillis(); operation.mKind = kind; operation.mSql = sql; + operation.mPath = mPool.getPath(); + operation.mResultLong = Long.MIN_VALUE; + operation.mResultString = null; if (bindArgs != null) { if (operation.mBindArgs == null) { operation.mBindArgs = new ArrayList<Object>(); + } else { + operation.mBindArgs.clear(); } for (int i = 0; i < bindArgs.length; i++) { final Object arg = bindArgs[i]; @@ -1655,44 +1651,16 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } } + operation.mCookie = newOperationCookieLocked(index); if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) { Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(), operation.mCookie); } + mIndex = index; return operation.mCookie; } } - public void beginTransaction(String kind) { - synchronized (mOperations) { - Operation operation = newOperationLocked(); - operation.mKind = kind; - mTransaction.copyFrom(operation); - - if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) { - Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(), - operation.mCookie); - } - } - } - - /** - * Fetch a new operation from the ring buffer. The operation is properly initialized. - * This advances mIndex to point to the next element. - */ - private Operation newOperationLocked() { - final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS; - Operation operation = mOperations[index]; - if (operation == null) { - mOperations[index] = new Operation(); - operation = mOperations[index]; - } - operation.start(); - operation.mCookie = newOperationCookieLocked(index); - mIndex = index; - return operation; - } - public void failOperation(int cookie, Exception ex) { synchronized (mOperations) { final Operation operation = getOperationLocked(cookie); @@ -1716,20 +1684,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - public boolean endTransaction(boolean success) { - synchronized (mOperations) { - mTransaction.mResultLong = success ? 1 : 0; - final long execTime = finishOperationLocked(mTransaction); - final Operation operation = getOperationLocked(mTransaction.mCookie); - if (operation != null) { - operation.copyFrom(mTransaction); - } - mTransaction.setEmpty(); - return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES - && SQLiteDebug.shouldLogSlowQuery(execTime); - } - } - public void logOperation(int cookie, String detail) { synchronized (mOperations) { logOperationLocked(cookie, detail); @@ -1751,7 +1705,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(), operation.mCookie); } - final long execTime = finishOperationLocked(operation); + operation.mEndTime = SystemClock.uptimeMillis(); + operation.mFinished = true; + final long execTime = operation.mEndTime - operation.mStartTime; mPool.onStatementExecuted(execTime); return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( execTime); @@ -1776,22 +1732,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen return generation << COOKIE_GENERATION_SHIFT | index; } - /** Close out the operation and return the elapsed time. */ - private long finishOperationLocked(Operation operation) { - operation.mEndTime = SystemClock.uptimeMillis(); - operation.mFinished = true; - final long elapsed = operation.mEndTime - operation.mStartTime; - if (elapsed > LONG_OPERATION_THRESHOLD_MS) { - mLongOperations.getNextSlot().copyFrom(operation); - mTotalLongOperations++; - } - return elapsed; - } - private Operation getOperationLocked(int cookie) { final int index = cookie & COOKIE_INDEX_MASK; final Operation operation = mOperations[index]; - return (operation != null && operation.mCookie == cookie) ? operation : null; + return operation.mCookie == cookie ? operation : null; } public String describeCurrentOperation() { @@ -1806,87 +1750,48 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - /** - * Dump an Operation if it is not in the recent operations list. Return 1 if the - * operation was dumped and 0 if not. - */ - private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) { - if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) { - return 0; - } - pw.println(op.describe(counter)); - return 1; - } - - private void dumpRecentLocked(Printer printer) { + public void dump(Printer printer) { synchronized (mOperations) { printer.println(" Most recently executed operations:"); int index = mIndex; - if (index == 0) { - printer.println(" <none>"); - return; - } - - // Operations are dumped in order of most recent first. - int counter = 0; - int n = 0; Operation operation = mOperations[index]; - do { - printer.println(operation.describe(counter)); - - if (index > 0) { - index -= 1; - } else { - index = MAX_RECENT_OPERATIONS - 1; - } - n++; - counter++; - operation = mOperations[index]; - } while (operation != null && n < MAX_RECENT_OPERATIONS); - counter += dumpIfNotRecentLocked(printer, mTransaction, counter); - } - } - - private void dumpLongLocked(Printer printer) { - printer.println(" Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:"); - if (mLongOperations.isEmpty()) { - printer.println(" <none>"); - return; - } - Operation[] longOps = mLongOperations.toArray(); - for (int i = 0; i < longOps.length; i++) { - if (longOps[i] != null) { - printer.println(longOps[i].describe(i)); + if (operation != null) { + // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created, + // and is relatively expensive to create during preloading. This method is only + // used when dumping a connection, which is a rare (mainly error) case. + SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + int n = 0; + do { + StringBuilder msg = new StringBuilder(); + msg.append(" ").append(n).append(": ["); + String formattedStartTime = opDF.format(new Date(operation.mStartWallTime)); + msg.append(formattedStartTime); + msg.append("] "); + operation.describe(msg, false); // Never dump bingargs in a bugreport + printer.println(msg.toString()); + + if (index > 0) { + index -= 1; + } else { + index = MAX_RECENT_OPERATIONS - 1; + } + n += 1; + operation = mOperations[index]; + } while (operation != null && n < MAX_RECENT_OPERATIONS); + } else { + printer.println(" <none>"); } } } - - public long getTotalLongOperations() { - return mTotalLongOperations; - } - - public void dump(Printer printer) { - synchronized (mOperations) { - dumpRecentLocked(printer); - dumpLongLocked(printer); - } - } } - private final class Operation { + private static final class Operation { // Trim all SQL statements to 256 characters inside the trace marker. // This limit gives plenty of context while leaving space for other // entries in the trace buffer (and ensures atrace doesn't truncate the // marker for us, potentially losing metadata in the process). private static final int MAX_TRACE_METHOD_NAME_LEN = 256; - // The reserved start time that indicates the Operation is empty. - private static final long EMPTY_OPERATION = -1; - - // The formatter for the timestamp. - private static final DateTimeFormatter sDateTime = - DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US); - public long mStartWallTime; // in System.currentTimeMillis() public long mStartTime; // in SystemClock.uptimeMillis(); public long mEndTime; // in SystemClock.uptimeMillis(); @@ -1896,58 +1801,16 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen public boolean mFinished; public Exception mException; public int mCookie; + public String mPath; public long mResultLong; // MIN_VALUE means "value not set". public String mResultString; - /** Reset the object to begin a new operation. */ - void start() { - mStartWallTime = System.currentTimeMillis(); - mStartTime = SystemClock.uptimeMillis(); - mEndTime = Long.MIN_VALUE; - mKind = null; - mSql = null; - if (mBindArgs != null) mBindArgs.clear(); - mFinished = false; - mException = null; - mCookie = -1; - mResultLong = Long.MIN_VALUE; - mResultString = null; - } - - /** - * Initialize from the source object. This is meant to clone the object for use in a - * transaction operation. To that end, the local bind args are set to null. - */ - void copyFrom(Operation r) { - mStartWallTime = r.mStartWallTime; - mStartTime = r.mStartTime; - mEndTime = r.mEndTime; - mKind = r.mKind; - mSql = r.mSql; - mBindArgs = null; - mFinished = r.mFinished; - mException = r.mException; - mCookie = r.mCookie; - mResultLong = r.mResultLong; - mResultString = r.mResultString; - } - - /** Mark the operation empty. */ - void setEmpty() { - mStartWallTime = EMPTY_OPERATION; - } - - /** Return true if the operation is empty. */ - boolean isEmpty() { - return mStartWallTime == EMPTY_OPERATION; - } - public void describe(StringBuilder msg, boolean allowDetailedLog) { msg.append(mKind); if (mFinished) { msg.append(" took ").append(mEndTime - mStartTime).append("ms"); } else { - msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime) + msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime) .append("ms ago"); } msg.append(" - ").append(getStatus()); @@ -1976,7 +1839,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } msg.append("]"); } - msg.append(", path=").append(mPool.getPath()); + msg.append(", path=").append(mPath); if (mException != null) { msg.append(", exception=\"").append(mException.getMessage()).append("\""); } @@ -1988,21 +1851,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - /** - * Convert a wall-clock time in milliseconds to logcat format. - */ - private String timeString(long millis) { - return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis)); - } - - public String describe(int n) { - final StringBuilder msg = new StringBuilder(); - final String start = timeString(mStartWallTime); - msg.append(" ").append(n).append(": [").append(start).append("] "); - describe(msg, false); // Never dump bingargs in a bugreport - return msg.toString(); - } - private String getStatus() { if (!mFinished) { return "running"; @@ -2016,6 +1864,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN); return methodName; } + } /** diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index 15d7d6675c8e..ad335b62f05e 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -1175,7 +1175,7 @@ public final class SQLiteConnectionPool implements Closeable { + ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode()) + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode())); - printer.println(" IsReadOnlyDatabase: " + mConfiguration.isReadOnlyDatabase()); + printer.println(" IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase()); if (isCompatibilityWalEnabled) { printer.println(" Compatibility WAL enabled: wal_syncmode=" diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java index 3b14d9d71b3e..7d9f02d6a96d 100644 --- a/core/java/android/database/sqlite/SQLiteSession.java +++ b/core/java/android/database/sqlite/SQLiteSession.java @@ -312,15 +312,6 @@ public final class SQLiteSession { cancellationSignal); } - private String modeString(int transactionMode) { - switch (transactionMode) { - case TRANSACTION_MODE_IMMEDIATE: return "TRANSACTION-IMMEDIATE"; - case TRANSACTION_MODE_EXCLUSIVE: return "TRANSACTION-EXCLUSIVE"; - case TRANSACTION_MODE_DEFERRED: return "TRANSACTION-DEFERRED"; - default: return "TRANSACTION"; - } - } - private void beginTransactionUnchecked(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags, CancellationSignal cancellationSignal) { @@ -330,7 +321,6 @@ public final class SQLiteSession { if (mTransactionStack == null) { acquireConnection(null, connectionFlags, cancellationSignal); // might throw - mConnection.recordBeginTransaction(modeString(transactionMode)); } try { // Set up the transaction such that we can back out safely @@ -475,7 +465,6 @@ public final class SQLiteSession { mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw } } finally { - mConnection.recordEndTransaction(successful); releaseConnection(); // might throw } } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 0208fed6040f..be9f0a0c8d13 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -295,7 +295,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * called. * * @param view The customized view information. - * @return This builder.re + * @return This builder. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) @NonNull diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 991bade09a25..ec9b013c34cc 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -585,6 +585,11 @@ public abstract class CameraDevice implements AutoCloseable { * <p>Configuring a session with an empty or null list will close the current session, if * any. This can be used to release the current session's target surfaces for another use.</p> * + * <p>This function throws an {@code IllegalArgumentException} if called with a + * SessionConfiguration lacking state callbacks or valid output surfaces. The only exceptions + * are deferred SurfaceView or SurfaceTexture outputs. See {@link + * OutputConfiguration#OutputConfiguration(Size, Class)} for details.</p> + * * <h3>Regular capture</h3> * * <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when @@ -1675,19 +1680,32 @@ public abstract class CameraDevice implements AutoCloseable { * * <p><b>IMPORTANT:</b></p> * <ul> - * <li>If a feature support can be queried via + * <li>If feature support can be queried via * {@link CameraCharacteristics#SCALER_MANDATORY_STREAM_COMBINATIONS} or * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP}, applications should - * directly use it rather than calling this function as: (1) using - * {@code CameraCharacteristics} is more efficient, and (2) calling this function with on - * non-supported devices will throw a {@link UnsupportedOperationException}. + * directly use that route rather than calling this function as: (1) using + * {@code CameraCharacteristics} is more efficient, and (2) calling this function with + * certain non-supported features will throw a {@link IllegalArgumentException}.</li> * - * <li>To minimize latency of {@link SessionConfiguration} creation, applications can - * use deferred surfaces for SurfaceView and SurfaceTexture to avoid waiting for UI - * creation before setting up the camera. For {@link android.media.MediaRecorder} and - * {@link android.media.MediaCodec} uses, applications can use {@code ImageReader} with - * {@link android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE}. The lightweight nature of - * {@code ImageReader} helps minimize the latency cost. + * <li>To minimize {@link SessionConfiguration} creation latency due to its dependency on + * output surfaces, the application can call this method before acquiring valid + * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture}, + * {@link android.media.MediaRecorder}, {@link android.media.MediaCodec}, or {@link + * android.media.ImageReader} surfaces. For {@link android.view.SurfaceView}, + * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, and + * {@link android.media.MediaCodec}, the application can call + * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. For {@link + * android.media.ImageReader}, the application can call {@link + * OutputConfiguration#OutputConfiguration(int, Size)}, {@link + * OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link + * OutputConfiguration#OutputConfiguration(int, Size, long)}, or {@link + * OutputConfiguration#OutputConfiguration(int, int, Size, long)}. The {@link + * SessionConfiguration} can then be created using the OutputConfiguration objects and + * be used to query whether it's supported by the camera device. To create the + * CameraCaptureSession, the application still needs to make sure all output surfaces + * are added via {@link OutputConfiguration#addSurfaces} with the exception of deferred + * surfaces for {@link android.view.SurfaceView} and + * {@link android.graphics.SurfaceTexture}.</li> * </ul> * * @return {@code true} if the given session configuration is supported by the camera @@ -1706,8 +1724,8 @@ public abstract class CameraDevice implements AutoCloseable { @NonNull SessionConfiguration config) throws CameraAccessException; /** - * <p>Get camera characteristics for a particular session configuration for this camera - * device</p> + * Get camera characteristics for a particular session configuration for this camera + * device. * * <p>The camera characteristics returned by this method are different from those returned * from {@link CameraManager#getCameraCharacteristics}. The characteristics returned here @@ -1718,6 +1736,24 @@ public abstract class CameraDevice implements AutoCloseable { * <p>Other than that, the characteristics returned here can be used in the same way as * those returned from {@link CameraManager#getCameraCharacteristics}.</p> * + * <p>To optimize latency, the application can call this method before acquiring valid + * {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture}, + * {@link android.media.MediaRecorder}, {@link android.media.MediaCodec}, or {@link + * android.media.ImageReader} surfaces. For {@link android.view.SurfaceView}, + * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, and + * {@link android.media.MediaCodec}, the application can call + * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. For {@link + * android.media.ImageReader}, the application can call {@link + * OutputConfiguration#OutputConfiguration(int, Size)}, {@link + * OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link + * OutputConfiguration#OutputConfiguration(int, Size, long)}, or {@link + * OutputConfiguration#OutputConfiguration(int, int, Size, long)}. The {@link + * SessionConfiguration} can then be created using the OutputConfiguration objects and + * be used for this function. To create the CameraCaptureSession, the application still + * needs to make sure all output surfaces are added via {@link + * OutputConfiguration#addSurfaces} with the exception of deferred surfaces for {@link + * android.view.SurfaceView} and {@link android.graphics.SurfaceTexture}.</p> + * * @param sessionConfig The session configuration for which characteristics are fetched. * @return CameraCharacteristics specific to a given session configuration. * diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 6962811ab860..eb644e8cfa01 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -229,9 +229,10 @@ public final class CameraExtensionCharacteristics { StreamConfigurationMap streamMap) { ArrayList<Size> ret = getSupportedSizes(sizesList, format); - if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888) { + if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888 || + format == ImageFormat.PRIVATE) { // Per API contract it is assumed that the extension is able to support all - // camera advertised sizes for JPEG and YUV_420_888 in case it doesn't return + // camera advertised sizes for JPEG, YUV_420_888 and PRIVATE in case it doesn't return // a valid non-empty size list. Size[] supportedSizes = streamMap.getOutputSizes(format); if ((ret.isEmpty()) && (supportedSizes != null)) { diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index d2e4a614202f..b43a900bec80 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -47,6 +47,7 @@ import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.utils.CameraIdAndSessionConfiguration; import android.hardware.camera2.utils.ConcurrentCameraIdCombination; import android.hardware.camera2.utils.ExceptionUtils; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.os.Binder; @@ -204,14 +205,12 @@ public final class CameraManager { mDeviceStateListeners.add(new WeakReference<>(listener)); } + @SuppressWarnings("FlaggedApi") @Override - public final void onBaseStateChanged(int state) { - handleStateChange(state); - } - - @Override - public final void onStateChanged(int state) { - handleStateChange(state); + public void onDeviceStateChanged(DeviceState state) { + // Suppressing the FlaggedAPI warning as this specific API isn't new, just moved to + // system API which requires it to be flagged. + handleStateChange(state.getIdentifier()); } } diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java index 8a18a0d2d367..116928b4283a 100644 --- a/core/java/android/hardware/camera2/MultiResolutionImageReader.java +++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java @@ -16,6 +16,8 @@ package android.hardware.camera2; +import static com.android.internal.util.Preconditions.checkNotNull; + import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntRange; @@ -26,20 +28,15 @@ import android.graphics.ImageFormat; import android.graphics.ImageFormat.Format; import android.hardware.HardwareBuffer; import android.hardware.HardwareBuffer.Usage; +import android.hardware.camera2.params.MultiResolutionStreamInfo; import android.media.Image; import android.media.ImageReader; -import android.hardware.camera2.params.MultiResolutionStreamInfo; -import android.os.Looper; -import android.os.Message; -import android.util.Log; +import android.util.Size; import android.view.Surface; import com.android.internal.camera.flags.Flags; -import java.nio.NioUtils; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.concurrent.Executor; /** @@ -351,6 +348,52 @@ public class MultiResolutionImageReader implements AutoCloseable { } /** + * Get the internal ImageReader surface based on configured size and physical camera Id. + * + * <p>The {@code configuredSize} and {@code physicalCameraId} parameters must match one of the + * MultiResolutionStreamInfo used to create this {@link MultiResolutionImageReader}.</p> + * + * <p>The Surface returned from this function isn't meant to be used directly as part of a + * {@link CaptureRequest}. It should instead be used for creating an OutputConfiguration + * before session creation. See {@link OutputConfiguration#setSurfacesForMultiResolutionOutput} + * for details. For {@link CaptureRequest}, use {@link #getSurface()} instead.</p> + * + * <p>Please note that holding on to the Surface objects returned by this method is not enough + * to keep their parent MultiResolutionImageReaders from being reclaimed. In that sense, a + * Surface acts like a {@link java.lang.ref.WeakReference weak reference} to the + * MultiResolutionImageReader that provides it.</p> + * + * @param configuredSize The configured size corresponding to one of the internal ImageReader. + * @param physicalCameraId The physical camera Id the internal ImageReader targets for. If + * the ImageReader is not targeting a physical camera of a logical + * multi-camera, this parameter is set to "". + * + * @return The {@link Surface} of the internal ImageReader corresponding to the provided + * configured size and physical camera Id. + * + * @throws IllegalArgumentException If {@code configuredSize} is {@code null}, or the ({@code + * configuredSize} and {@code physicalCameraId}) combo is not + * part of this {@code MultiResolutionImageReader}. + * @hide + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public @NonNull Surface getSurface(@NonNull Size configuredSize, + @NonNull String physicalCameraId) { + checkNotNull(configuredSize, "configuredSize must not be null"); + checkNotNull(physicalCameraId, "physicalCameraId must not be null"); + + for (int i = 0; i < mStreamInfo.length; i++) { + if (mStreamInfo[i].getWidth() == configuredSize.getWidth() + && mStreamInfo[i].getHeight() == configuredSize.getHeight() + && physicalCameraId.equals(mStreamInfo[i].getPhysicalCameraId())) { + return mReaders[i].getSurface(); + } + } + throw new IllegalArgumentException("configuredSize and physicalCameraId don't match with " + + "this MultiResolutionImageReader"); + } + + /** * Get the surface that is used as a target for {@link CaptureRequest} * * <p>The application must use the surface returned by this function as a target for diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java index 0f199b15af6c..9e014386a6e5 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java @@ -90,7 +90,6 @@ public class CameraDeviceSetupImpl extends CameraDevice.CameraDeviceSetup { @Override public boolean isSessionConfigurationSupported(@NonNull SessionConfiguration config) throws CameraAccessException { - // TODO(b/298033056): restructure the OutputConfiguration API for better usability synchronized (mInterfaceLock) { if (mCameraManager.isCameraServiceDisabled()) { throw new IllegalArgumentException("No cameras available on device"); diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 8aacd5e3908f..dda52dd5e8cc 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -19,6 +19,7 @@ package android.hardware.camera2.params; import static com.android.internal.util.Preconditions.*; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,6 +28,9 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.graphics.ColorSpace; import android.graphics.ImageFormat; +import android.graphics.ImageFormat.Format; +import android.hardware.HardwareBuffer; +import android.hardware.HardwareBuffer.Usage; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; @@ -43,6 +47,8 @@ import android.util.Log; import android.util.Size; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -50,6 +56,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * A class for describing camera output, which contains a {@link Surface} and its specific @@ -361,6 +368,21 @@ public final class OutputConfiguration implements Parcelable { private final int SURFACE_TYPE_SURFACE_TEXTURE = 1; /** + * The surface is obtained from {@link android.media.MediaRecorder}. + */ + private static final int SURFACE_TYPE_MEDIA_RECORDER = 2; + + /** + * The surface is obtained from {@link android.media.MediaCodec}. + */ + private static final int SURFACE_TYPE_MEDIA_CODEC = 3; + + /** + * The surface is obtained from {@link android.media.ImageReader}. + */ + private static final int SURFACE_TYPE_IMAGE_READER = 4; + + /** * Maximum number of surfaces supported by one {@link OutputConfiguration}. * * <p>The combined number of surfaces added by the constructor and @@ -576,6 +598,7 @@ public final class OutputConfiguration implements Parcelable { mMirrorMode = MIRROR_MODE_AUTO; mReadoutTimestampEnabled = false; mIsReadoutSensorTimestampBase = false; + mUsage = 0; } /** @@ -592,13 +615,7 @@ public final class OutputConfiguration implements Parcelable { @NonNull MultiResolutionImageReader multiResolutionImageReader) { checkNotNull(multiResolutionImageReader, "Multi-resolution image reader must not be null"); - int groupId = MULTI_RESOLUTION_GROUP_ID_COUNTER; - MULTI_RESOLUTION_GROUP_ID_COUNTER++; - // Skip in case the group id counter overflows to -1, the invalid value. - if (MULTI_RESOLUTION_GROUP_ID_COUNTER == -1) { - MULTI_RESOLUTION_GROUP_ID_COUNTER++; - } - + int groupId = getAndIncreaseMultiResolutionGroupId(); ImageReader[] imageReaders = multiResolutionImageReader.getReaders(); ArrayList<OutputConfiguration> configs = new ArrayList<OutputConfiguration>(); for (int i = 0; i < imageReaders.length; i++) { @@ -620,6 +637,115 @@ public final class OutputConfiguration implements Parcelable { } /** + * Create a list of {@link OutputConfiguration} instances for a + * {@link android.hardware.camera2.params.MultiResolutionImageReader}. + * + * <p>This method can be used to create query OutputConfigurations for a + * MultiResolutionImageReader that can be included in a SessionConfiguration passed into + * {@link CameraDeviceSetup#isSessionConfigurationSupported} before opening and setting up + * a camera device in full, at which point {@link #setSurfacesForMultiResolutionOutput} + * can be used to link to the actual MultiResolutionImageReader.</p> + * + * <p>This constructor takes same arguments used to create a {@link + * MultiResolutionImageReader}: a collection of {@link MultiResolutionStreamInfo} + * objects and the format.</p> + * + * @param streams The group of multi-resolution stream info objects, which are used to create a + * multi-resolution image reader containing a number of ImageReaders. + * @param format The format of the MultiResolutionImageReader. This must be one of the {@link + * android.graphics.ImageFormat} or {@link android.graphics.PixelFormat} constants + * supported by the camera device. Note that not all formats are supported, like + * {@link ImageFormat.NV21}. The supported multi-resolution reader format can be + * queried by {@link MultiResolutionStreamConfigurationMap#getOutputFormats}. + * + * @return The list of {@link OutputConfiguration} objects for a MultiResolutionImageReader. + * + * @throws IllegaArgumentException If the {@code streams} is null or doesn't contain + * at least 2 items, or if {@code format} isn't a valid camera + * format. + * + * @see MultiResolutionImageReader + * @see MultiResolutionStreamInfo + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public static @NonNull List<OutputConfiguration> createInstancesForMultiResolutionOutput( + @NonNull Collection<MultiResolutionStreamInfo> streams, + @Format int format) { + if (streams == null || streams.size() <= 1) { + throw new IllegalArgumentException( + "The streams list must contain at least 2 entries"); + } + if (format == ImageFormat.NV21) { + throw new IllegalArgumentException( + "NV21 format is not supported"); + } + + int groupId = getAndIncreaseMultiResolutionGroupId(); + ArrayList<OutputConfiguration> configs = new ArrayList<OutputConfiguration>(); + for (MultiResolutionStreamInfo stream : streams) { + Size surfaceSize = new Size(stream.getWidth(), stream.getHeight()); + OutputConfiguration config = new OutputConfiguration( + groupId, format, surfaceSize); + config.setPhysicalCameraId(stream.getPhysicalCameraId()); + config.setMultiResolutionOutput(); + configs.add(config); + + // No need to call addSensorPixelModeUsed for ultra high resolution sensor camera, + // because regular and max resolution output configurations are used for DEFAULT mode + // and MAX_RESOLUTION mode respectively by default. + } + + return configs; + } + + /** + * Set the OutputConfiguration surfaces corresponding to the {@link MultiResolutionImageReader}. + * + * <p>This function should be used together with {@link + * #createInstancesForMultiResolutionOutput}. The application calls {@link + * #createInstancesForMultiResolutionOutput} first to create a list of + * OutputConfiguration objects without the actual MultiResolutionImageReader. + * Once the MultiResolutionImageReader is created later during full camera setup, the + * application then calls this function to assign the surfaces to the OutputConfiguration + * instances.</p> + * + * @param outputConfigurations The OutputConfiguration objects created by {@link + * #createInstancesFromMultiResolutionOutput} + * @param multiResolutionImageReader The MultiResolutionImageReader object created from the same + * MultiResolutionStreamInfo parameters as + * {@code outputConfigurations}. + * @throws IllegalArgumentException If {@code outputConfigurations} or {@code + * multiResolutionImageReader} is {@code null}, the {@code + * outputConfigurations} and {@code multiResolutionImageReader} + * sizes don't match, or if the + * {@code multiResolutionImageReader}'s surfaces don't match + * with the {@code outputConfigurations}. + * @throws IllegalStateException If {@code outputConfigurations} already contains valid output + * surfaces. + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public static void setSurfacesForMultiResolutionOutput( + @NonNull Collection<OutputConfiguration> outputConfigurations, + @NonNull MultiResolutionImageReader multiResolutionImageReader) { + checkNotNull(outputConfigurations, "outputConfigurations must not be null"); + checkNotNull(multiResolutionImageReader, "multiResolutionImageReader must not be null"); + if (outputConfigurations.size() != multiResolutionImageReader.getReaders().length) { + throw new IllegalArgumentException( + "outputConfigurations and multiResolutionImageReader sizes must match"); + } + + for (OutputConfiguration config : outputConfigurations) { + String physicalCameraId = config.getPhysicalCameraId(); + if (physicalCameraId == null) { + physicalCameraId = ""; + } + Surface surface = multiResolutionImageReader.getSurface(config.getConfiguredSize(), + physicalCameraId); + config.addSurface(surface); + } + } + + /** * Create a new {@link OutputConfiguration} instance, with desired Surface size and Surface * source class. * <p> @@ -628,30 +754,64 @@ public final class OutputConfiguration implements Parcelable { * with a deferred Surface. The application can use this output configuration to create a * session. * </p> - * <p> - * However, the actual output Surface must be set via {@link #addSurface} and the deferred - * Surface configuration must be finalized via {@link - * CameraCaptureSession#finalizeOutputConfigurations} before submitting a request with this - * Surface target. The deferred Surface can only be obtained either from {@link - * android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, or from + * + * <p>Starting from {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, + * the deferred Surface can be obtained: (1) from {@link android.view.SurfaceView} + * by calling {@link android.view.SurfaceHolder#getSurface}, (2) from * {@link android.graphics.SurfaceTexture} via - * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}). - * </p> + * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}, (3) from {@link + * android.media.MediaRecorder} via {@link android.media.MediaRecorder.getSurface} or {@link + * android.media.MediaCodec#createPersistentInputSurface}, or (4) from {@link + * android.media.MediaCodce} via {@link android.media.MediaCodec#createInputSurface} or {@link + * android.media.MediaCodec#createPersistentInputSource}.</p> + * + * <ul> + * <li>Surfaces for {@link android.view.SurfaceView} and {@link android.graphics.SurfaceTexture} + * can be deferred until after {@link CameraDevice#createCaptureSession}. In that case, the + * output Surface must be set via {@link #addSurface}, and the Surface configuration must be + * finalized via {@link CameraCaptureSession#finalizeOutputConfiguration} before submitting + * a request with the Surface target.</li> + * <li>For all other target types, the output Surface must be set by {@link #addSurface}, + * and {@link CameraCaptureSession#finalizeOutputConfiguration} is not needed because the + * OutputConfiguration used to create the session will contain the actual Surface.</li> + * </ul> + * + * <p>Before {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, only {@link + * android.view.SurfaceView} and {@link android.graphics.SurfaceTexture} are supported. Both + * kind of outputs can be deferred until after {@link + * CameraDevice#createCaptureSessionByOutputConfiguration}.</p> + * + * <p>An OutputConfiguration object created by this constructor can be used for {@link + * CameraDeviceSetup.isSessionConfigurationSupported} and {@link + * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p> * * @param surfaceSize Size for the deferred surface. * @param klass a non-{@code null} {@link Class} object reference that indicates the source of - * this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class} and - * {@link android.graphics.SurfaceTexture SurfaceTexture.class} are supported. + * this surface. Only {@link android.view.SurfaceHolder SurfaceHolder.class}, + * {@link android.graphics.SurfaceTexture SurfaceTexture.class}, {@link + * android.media.MediaRecorder MediaRecorder.class}, and + * {@link android.media.MediaCodec MediaCodec.class} are supported. + * Before {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, only + * {@link android.view.SurfaceHolder SurfaceHolder.class} and {@link + * android.graphics.SurfaceTexture SurfaceTexture.class} are supported. * @throws IllegalArgumentException if the Surface source class is not supported, or Surface * size is zero. */ public <T> OutputConfiguration(@NonNull Size surfaceSize, @NonNull Class<T> klass) { - checkNotNull(klass, "surfaceSize must not be null"); + checkNotNull(surfaceSize, "surfaceSize must not be null"); checkNotNull(klass, "klass must not be null"); if (klass == android.view.SurfaceHolder.class) { mSurfaceType = SURFACE_TYPE_SURFACE_VIEW; + mIsDeferredConfig = true; } else if (klass == android.graphics.SurfaceTexture.class) { mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE; + mIsDeferredConfig = true; + } else if (klass == android.media.MediaRecorder.class) { + mSurfaceType = SURFACE_TYPE_MEDIA_RECORDER; + mIsDeferredConfig = false; + } else if (klass == android.media.MediaCodec.class) { + mSurfaceType = SURFACE_TYPE_MEDIA_CODEC; + mIsDeferredConfig = false; } else { mSurfaceType = SURFACE_TYPE_UNKNOWN; throw new IllegalArgumentException("Unknown surface source class type"); @@ -668,7 +828,6 @@ public final class OutputConfiguration implements Parcelable { mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE); mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); mConfiguredGenerationId = 0; - mIsDeferredConfig = true; mIsShared = false; mPhysicalCameraId = null; mIsMultiResolution = false; @@ -678,6 +837,131 @@ public final class OutputConfiguration implements Parcelable { mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT; mReadoutTimestampEnabled = false; mIsReadoutSensorTimestampBase = false; + mUsage = 0; + } + + /** + * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given + * format and size. + * + * <p>This constructor creates an OutputConfiguration for an ImageReader without providing + * the actual output Surface. The actual output Surface must be set via {@link #addSurface} + * before creating the capture session.</p> + * + * <p>An OutputConfiguration object created by this constructor can be used for {@link + * CameraDeviceSetup.isSessionConfigurationSupported} and {@link + * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p> + * + * @param format The format of the ImageReader output. This must be one of the + * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat} + * constants. Note that not all formats are supported by the camera device. + * @param surfaceSize Size for the ImageReader surface. + * @throws IllegalArgumentException if the Surface size is null or zero. + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public OutputConfiguration(@Format int format, @NonNull Size surfaceSize) { + this(format, surfaceSize, + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN); + } + + /** + * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given + * surfaceGroupId, format, and size. + * + * <p>This constructor creates an OutputConfiguration for an ImageReader without providing + * the actual output Surface. The actual output Surface must be set via {@link #addSurface} + * before creating the capture session.</p> + * + * <p>An OutputConfiguration object created by this constructor can be used for {@link + * CameraDeviceSetup.isSessionConfigurationSupported} and {@link + * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p> + * + * @param surfaceGroupId A group ID for this output, used for sharing memory between multiple + * outputs. + * @param format The format of the ImageReader output. This must be one of the + * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat} + * constants. Note that not all formats are supported by the camera device. + * @param surfaceSize Size for the ImageReader surface. + * @throws IllegalArgumentException if the Surface size is null or zero. + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public OutputConfiguration(int surfaceGroupId, @Format int format, @NonNull Size surfaceSize) { + this(surfaceGroupId, format, surfaceSize, + format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN); + } + + /** + * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given + * format, size, and usage flags. + * + * <p>This constructor creates an OutputConfiguration for an ImageReader without providing + * the actual output Surface. The actual output Surface must be set via {@link #addSurface} + * before creating the capture session.</p> + * + * <p>An OutputConfiguration object created by this constructor can be used for {@link + * CameraDeviceSetup.isSessionConfigurationSupported} and {@link + * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p> + * + * @param format The format of the ImageReader output. This must be one of the + * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat} + * constants. Note that not all formats are supported by the camera device. + * @param surfaceSize Size for the ImageReader surface. + * @param usage The usage flags of the ImageReader output surface. + * @throws IllegalArgumentException if the Surface size is null or zero. + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public OutputConfiguration(@Format int format, @NonNull Size surfaceSize, @Usage long usage) { + this(SURFACE_GROUP_ID_NONE, format, surfaceSize, usage); + } + + /** + * Create a new {@link OutputConfiguration} instance for an {@link ImageReader} for a given + * surface group id, format, size, and usage flags. + * + * <p>This constructor creates an OutputConfiguration for an ImageReader without providing + * the actual output Surface. The actual output Surface must be set via {@link #addSurface} + * before creating the capture session.</p> + * + * <p>An OutputConfiguration object created by this constructor can be used for {@link + * CameraDeviceSetup.isSessionConfigurationSupported} and {@link + * CameraDeviceSetup.getSessionCharacteristics} without having called {@link #addSurface}.</p> + * + * @param surfaceGroupId A group ID for this output, used for sharing memory between multiple + * outputs. + * @param format The format of the ImageReader output. This must be one of the + * {@link android.graphics.ImageFormat} or {@link android.graphics.PixelFormat} + * constants. Note that not all formats are supported by the camera device. + * @param surfaceSize Size for the ImageReader surface. + * @param usage The usage flags of the ImageReader output surface. + * @throws IllegalArgumentException if the Surface size is null or zero. + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public OutputConfiguration(int surfaceGroupId, @Format int format, + @NonNull Size surfaceSize, @Usage long usage) { + checkNotNull(surfaceSize, "surfaceSize must not be null"); + if (surfaceSize.getWidth() == 0 || surfaceSize.getHeight() == 0) { + throw new IllegalArgumentException("Surface size needs to be non-zero"); + } + + mSurfaceType = SURFACE_TYPE_IMAGE_READER; + mSurfaceGroupId = surfaceGroupId; + mSurfaces = new ArrayList<Surface>(); + mRotation = ROTATION_0; + mConfiguredSize = surfaceSize; + mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(format); + mConfiguredDataspace = StreamConfigurationMap.imageFormatToDataspace(format); + mConfiguredGenerationId = 0; + mIsDeferredConfig = false; + mIsShared = false; + mPhysicalCameraId = null; + mIsMultiResolution = false; + mSensorPixelModesUsed = new ArrayList<Integer>(); + mDynamicRangeProfile = DynamicRangeProfiles.STANDARD; + mColorSpace = ColorSpaceProfiles.UNSPECIFIED; + mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT; + mReadoutTimestampEnabled = false; + mIsReadoutSensorTimestampBase = false; + mUsage = usage; } /** @@ -852,7 +1136,8 @@ public final class OutputConfiguration implements Parcelable { /** * Check if this configuration has deferred configuration. * - * <p>This will return true if the output configuration was constructed with surface deferred by + * <p>This will return true if the output configuration was constructed with {@link + * android.view.SurfaceView} or {@link android.graphics.SurfaceTexture} deferred by * {@link OutputConfiguration#OutputConfiguration(Size, Class)}. It will return true even after * the deferred surface is added later by {@link OutputConfiguration#addSurface}.</p> * @@ -872,13 +1157,28 @@ public final class OutputConfiguration implements Parcelable { * {@link CameraCaptureSession#finalizeOutputConfigurations}. It is possible to call this method * after the output configurations have been finalized only in cases of enabled surface sharing * see {@link #enableSurfaceSharing}. The modified output configuration must be updated with - * {@link CameraCaptureSession#updateOutputConfiguration}.</p> - * - * <p> If the OutputConfiguration was constructed with a deferred surface by {@link - * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained - * from {@link android.view.SurfaceView} by calling {@link android.view.SurfaceHolder#getSurface}, - * or from {@link android.graphics.SurfaceTexture} via - * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}).</p> + * {@link CameraCaptureSession#updateOutputConfiguration}. If this function is called before + * session creation, {@link CameraCaptureSession#finalizeOutputConfigurations} doesn't need to + * be called.</p> + * + * <p> If the OutputConfiguration was constructed by {@link + * OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained: + * <ul> + * <li>from {@link android.view.SurfaceView} by calling + * {@link android.view.SurfaceHolder#getSurface}</li> + * <li>from {@link android.graphics.SurfaceTexture} by calling + * {@link android.view.Surface#Surface(android.graphics.SurfaceTexture)}</li> + * <li>from {@link android.media.MediaRecorder} by calling + * {@link android.media.MediaRecorder#getSurface} or {@link + * android.media.MediaCodec#createPersistentInputSurface}</li> + * <li>from {@link android.media.MediaCodce} by calling + * {@link android.media.MediaCodec#createInputSurface} or {@link + * android.media.MediaCodec#createPersistentInputSource}</li> + * </ul> + * + * <p> If the OutputConfiguration was constructed by {@link #OutputConfiguration(int, Size)} + * or its variants, the added surface must be obtained from {@link android.media.ImageReader} + * by calling {@link android.media.ImageReader#getSurface}.</p> * * <p> If the OutputConfiguration was constructed by other constructors, the added * surface must be compatible with the existing surface. See {@link #enableSurfaceSharing} for @@ -934,9 +1234,13 @@ public final class OutputConfiguration implements Parcelable { * * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the * OutputConfiguration. The only notable exception is the surface associated with - * the OutputConfiguration see {@link #getSurface} which was passed as part of the constructor - * or was added first in the deferred case - * {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p> + * the OutputConfiguration (see {@link #getSurface}) which was passed as part of the + * constructor or was added first in the case of + * {@link OutputConfiguration#OutputConfiguration(Size, Class)}, {@link + * OutputConfiguration#OutputConfiguration(int, Size)}, {@link + * OutputConfiguration#OutputConfiguration(int, Size, long)}, {@link + * OutputConfiguration#OutputConfiguration(int, int, Size)}, {@link + * OutputConfiguration#OutputConfiguration(int, int, Size, long)}.</p> * * @param surface The surface to be removed. * @@ -945,6 +1249,7 @@ public final class OutputConfiguration implements Parcelable { * with {@link #addSurface}. */ public void removeSurface(@NonNull Surface surface) { + checkNotNull(surface, "Surface must not be null"); if (getSurface() == surface) { throw new IllegalArgumentException( "Cannot remove surface associated with this output configuration"); @@ -1175,6 +1480,7 @@ public final class OutputConfiguration implements Parcelable { this.mTimestampBase = other.mTimestampBase; this.mMirrorMode = other.mMirrorMode; this.mReadoutTimestampEnabled = other.mReadoutTimestampEnabled; + this.mUsage = other.mUsage; } /** @@ -1203,6 +1509,9 @@ public final class OutputConfiguration implements Parcelable { int timestampBase = source.readInt(); int mirrorMode = source.readInt(); boolean readoutTimestampEnabled = source.readInt() == 1; + int format = source.readInt(); + int dataSpace = source.readInt(); + long usage = source.readLong(); mSurfaceGroupId = surfaceSetId; mRotation = rotation; @@ -1211,6 +1520,7 @@ public final class OutputConfiguration implements Parcelable { mIsDeferredConfig = isDeferred; mIsShared = isShared; mSurfaces = surfaces; + mUsage = 0; if (mSurfaces.size() > 0) { mSurfaceType = SURFACE_TYPE_UNKNOWN; mConfiguredFormat = SurfaceUtils.getSurfaceFormat(mSurfaces.get(0)); @@ -1218,9 +1528,16 @@ public final class OutputConfiguration implements Parcelable { mConfiguredGenerationId = mSurfaces.get(0).getGenerationId(); } else { mSurfaceType = surfaceType; - mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal(ImageFormat.PRIVATE); - mConfiguredDataspace = - StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); + if (mSurfaceType != SURFACE_TYPE_IMAGE_READER) { + mConfiguredFormat = StreamConfigurationMap.imageFormatToInternal( + ImageFormat.PRIVATE); + mConfiguredDataspace = + StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); + } else { + mConfiguredFormat = format; + mConfiguredDataspace = dataSpace; + mUsage = usage; + } mConfiguredGenerationId = 0; } mPhysicalCameraId = physicalCameraId; @@ -1293,6 +1610,31 @@ public final class OutputConfiguration implements Parcelable { return mSurfaceGroupId; } + /** + * Get the configured size associated with this {@link OutputConfiguration}. + * + * @return The configured size associated with this {@link OutputConfiguration}. + * + * @hide + */ + public Size getConfiguredSize() { + return mConfiguredSize; + } + + /** + * Get the physical camera ID associated with this {@link OutputConfiguration}. + * + * <p>If this OutputConfiguration isn't targeting a physical camera of a logical + * multi-camera, this function returns {@code null}.</p> + * + * @return The physical camera Id associated with this {@link OutputConfiguration}. + * + * @hide + */ + public @Nullable String getPhysicalCameraId() { + return mPhysicalCameraId; + } + public static final @android.annotation.NonNull Parcelable.Creator<OutputConfiguration> CREATOR = new Parcelable.Creator<OutputConfiguration>() { @Override @@ -1353,6 +1695,9 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mTimestampBase); dest.writeInt(mMirrorMode); dest.writeInt(mReadoutTimestampEnabled ? 1 : 0); + dest.writeInt(mConfiguredFormat); + dest.writeInt(mConfiguredDataspace); + dest.writeLong(mUsage); } /** @@ -1372,22 +1717,24 @@ public final class OutputConfiguration implements Parcelable { return true; } else if (obj instanceof OutputConfiguration) { final OutputConfiguration other = (OutputConfiguration) obj; - if (mRotation != other.mRotation || - !mConfiguredSize.equals(other.mConfiguredSize) || - mConfiguredFormat != other.mConfiguredFormat || - mSurfaceGroupId != other.mSurfaceGroupId || - mSurfaceType != other.mSurfaceType || - mIsDeferredConfig != other.mIsDeferredConfig || - mIsShared != other.mIsShared || - mConfiguredDataspace != other.mConfiguredDataspace || - mConfiguredGenerationId != other.mConfiguredGenerationId || - !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) || - mIsMultiResolution != other.mIsMultiResolution || - mStreamUseCase != other.mStreamUseCase || - mTimestampBase != other.mTimestampBase || - mMirrorMode != other.mMirrorMode || - mReadoutTimestampEnabled != other.mReadoutTimestampEnabled) + if (mRotation != other.mRotation + || !mConfiguredSize.equals(other.mConfiguredSize) + || mConfiguredFormat != other.mConfiguredFormat + || mSurfaceGroupId != other.mSurfaceGroupId + || mSurfaceType != other.mSurfaceType + || mIsDeferredConfig != other.mIsDeferredConfig + || mIsShared != other.mIsShared + || mConfiguredDataspace != other.mConfiguredDataspace + || mConfiguredGenerationId != other.mConfiguredGenerationId + || !Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) + || mIsMultiResolution != other.mIsMultiResolution + || mStreamUseCase != other.mStreamUseCase + || mTimestampBase != other.mTimestampBase + || mMirrorMode != other.mMirrorMode + || mReadoutTimestampEnabled != other.mReadoutTimestampEnabled + || mUsage != other.mUsage) { return false; + } if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) { return false; } @@ -1416,6 +1763,16 @@ public final class OutputConfiguration implements Parcelable { } /** + * Get and increase the next MultiResolution group id. + * + * If the ID reaches -1, skip it. + */ + private static int getAndIncreaseMultiResolutionGroupId() { + return sNextMultiResolutionGroupId.getAndUpdate(i -> + i + 1 == SURFACE_GROUP_ID_NONE ? i + 2 : i + 1); + } + + /** * {@inheritDoc} */ @Override @@ -1430,7 +1787,8 @@ public final class OutputConfiguration implements Parcelable { mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), mDynamicRangeProfile, mColorSpace, mStreamUseCase, - mTimestampBase, mMirrorMode, mReadoutTimestampEnabled ? 1 : 0); + mTimestampBase, mMirrorMode, mReadoutTimestampEnabled ? 1 : 0, + Long.hashCode(mUsage)); } return HashCodeHelpers.hashCode( @@ -1440,14 +1798,14 @@ public final class OutputConfiguration implements Parcelable { mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(), mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(), mDynamicRangeProfile, mColorSpace, mStreamUseCase, mTimestampBase, - mMirrorMode, mReadoutTimestampEnabled ? 1 : 0); + mMirrorMode, mReadoutTimestampEnabled ? 1 : 0, Long.hashCode(mUsage)); } private static final String TAG = "OutputConfiguration"; // A surfaceGroupId counter used for MultiResolutionImageReader. Its value is // incremented every time {@link createInstancesForMultiResolutionOutput} is called. - private static int MULTI_RESOLUTION_GROUP_ID_COUNTER = 0; + private static AtomicInteger sNextMultiResolutionGroupId = new AtomicInteger(0); private ArrayList<Surface> mSurfaces; private final int mRotation; @@ -1486,4 +1844,6 @@ public final class OutputConfiguration implements Parcelable { private boolean mReadoutTimestampEnabled; // Whether the timestamp base is set to READOUT_SENSOR private boolean mIsReadoutSensorTimestampBase; + // The usage flags. Only set for instances created for ImageReader without specifying surface. + private long mUsage; } diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 991f545b5193..b0f354fac009 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -20,6 +20,7 @@ package android.hardware.camera2.params; import static com.android.internal.util.Preconditions.*; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +29,7 @@ import android.graphics.ColorSpace; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraDevice.CameraDeviceSetup; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.InputConfiguration; @@ -125,6 +127,34 @@ public final class SessionConfiguration implements Parcelable { } /** + * Create a new {@link SessionConfiguration} with sessionType and output configurations. + * + * <p>The SessionConfiguration objects created by this constructor can be used by + * {@link CameraDeviceSetup.isSessionConfigurationSupported} and {@link + * CameraDeviceSetup.getSessionCharacteristics} to query a camera device's feature + * combination support and session specific characteristics. For the SessionConfiguration + * object to be used to create a capture session, {@link #setCallback} must be called to + * specify the state callback function, and any incomplete OutputConfigurations must be + * completed via {@link OutputConfiguration#addSurface} or + * {@link OutputConfiguration#setSurfacesForMultiResolutionOutput} as appropriate.</p> + * + * @param sessionType The session type. + * @param outputs A list of output configurations for the capture session. + * + * @see #SESSION_REGULAR + * @see #SESSION_HIGH_SPEED + * @see CameraDevice#createCaptureSession(SessionConfiguration) + * @see CameraDeviceSetup#isSessionConfigurationSupported + * @see CameraDeviceSetup#getSessionCharacteristics + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public SessionConfiguration(@SessionMode int sessionType, + @NonNull List<OutputConfiguration> outputs) { + mSessionType = sessionType; + mOutputConfigurations = Collections.unmodifiableList(new ArrayList<>(outputs)); + } + + /** * Create a SessionConfiguration from Parcel. * No support for parcelable 'mStateCallback' and 'mExecutor' yet. */ @@ -376,4 +406,23 @@ public final class SessionConfiguration implements Parcelable { return null; } } + + /** + * Set the state callback and executor. + * + * <p>This function must be called for the SessionConfiguration object created via {@link + * #SessionConfiguration(int, List) SessionConfiguration(int, List<OutputConfiguration>)} + * before it's used to create a capture session.</p> + * + * @param executor The executor which should be used to invoke the callback. In general it is + * recommended that camera operations are not done on the main (UI) thread. + * @param cb A state callback interface implementation. + */ + @FlaggedApi(Flags.FLAG_CAMERA_DEVICE_SETUP) + public void setCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull CameraCaptureSession.StateCallback cb) { + mStateCallback = cb; + mExecutor = executor; + } } diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index b85d6869a1ff..b067095668b2 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -1509,6 +1509,8 @@ public final class StreamConfigurationMap { return HAL_DATASPACE_HEIF; case ImageFormat.JPEG_R: return HAL_DATASPACE_JPEG_R; + case ImageFormat.YUV_420_888: + return HAL_DATASPACE_JFIF; default: return HAL_DATASPACE_UNKNOWN; } @@ -2025,6 +2027,10 @@ public final class StreamConfigurationMap { * @hide */ public static final int HAL_DATASPACE_JPEG_R = 0x1005; + /** + * @hide + */ + public static final int HAL_DATASPACE_JFIF = 0x8C20000; private static final long DURATION_20FPS_NS = 50000000L; /** * @see #getDurations(int, int) diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index 76888f338615..b214da227a2d 100644 --- a/core/java/android/hardware/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -52,78 +52,6 @@ import java.util.Set; @FlaggedApi(android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_API) public final class DeviceState { /** - * Flag that indicates override requests should be cancelled when this device state becomes the - * base device state. - * @hide - * @deprecated use {@link #PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS} - */ - @Deprecated - public static final int FLAG_CANCEL_OVERRIDE_REQUESTS = 1 << 0; - - /** - * Flag that indicates this device state is inaccessible for applications to be placed in. This - * could be a device-state where the {@link Display#DEFAULT_DISPLAY} is not enabled. - * @hide - * @deprecated use {@link #PROPERTY_APP_INACCESSIBLE} - */ - @Deprecated - 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. - * @hide - * @deprecated use {@link #PROPERTY_EMULATED_ONLY} - */ - @Deprecated - public static final int FLAG_EMULATED_ONLY = 1 << 2; - - /** - * This flag indicates that the corresponding state should be automatically canceled when the - * requesting app is no longer on top. The app is considered not on top when (1) the top - * activity in the system is from a different app, (2) the device is in sleep mode, or - * (3) the keyguard shows up. - * @hide - * @deprecated use {@link #PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP} - */ - @Deprecated - public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3; - - /** - * This flag indicates that the corresponding state should be disabled when the device is - * overheating and reaching the critical status. - * @hide - * @deprecated use {@link #PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL} - */ - @Deprecated - public static final int FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4; - - /** - * This flag indicates that the corresponding state should be disabled when power save mode - * is enabled. - * @hide - * @deprecated use {@link #PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE} - */ - @Deprecated - public static final int FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE = 1 << 5; - - /** @hide */ - @IntDef(prefix = {"FLAG_"}, flag = true, value = { - FLAG_CANCEL_OVERRIDE_REQUESTS, - FLAG_APP_INACCESSIBLE, - FLAG_EMULATED_ONLY, - FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP, - FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL, - FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE - }) - @Deprecated - @Retention(RetentionPolicy.SOURCE) - public @interface DeviceStateFlags {} - - /** * Property that indicates that a fold-in style foldable device is currently in a fully closed * configuration. */ @@ -302,42 +230,11 @@ public final class DeviceState { @NonNull private final DeviceState.Configuration mDeviceStateConfiguration; - @DeviceStateFlags - private final int mFlags; - /** @hide */ + @TestApi public DeviceState(@NonNull DeviceState.Configuration deviceStateConfiguration) { Objects.requireNonNull(deviceStateConfiguration, "Device StateConfiguration is null"); mDeviceStateConfiguration = deviceStateConfiguration; - mFlags = 0; - } - - /** @hide */ - public DeviceState( - @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = - MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier, - @NonNull String name, - @NonNull Set<@DeviceStateProperties Integer> properties) { - mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name, properties, - Collections.emptySet()); - mFlags = 0; - } - - /** - * @deprecated Deprecated in favor of {@link #DeviceState(int, String, Set)} - * @hide - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @Deprecated - public DeviceState( - @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = - MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier, - @NonNull String name, - @DeviceStateFlags int flags) { - - mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name, - Collections.emptySet(), Collections.emptySet()); - mFlags = flags; } /** Returns the unique identifier for the device state. */ @@ -352,17 +249,6 @@ public final class DeviceState { return mDeviceStateConfiguration.getName(); } - /** - * @hide - * @deprecated in favor of {@link #hasProperty(int)} method - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @Deprecated - @DeviceStateFlags - public int getFlags() { - return mFlags; - } - @Override public String toString() { return "DeviceState{" + "identifier=" + mDeviceStateConfiguration.getIdentifier() @@ -388,16 +274,6 @@ public final class DeviceState { return Objects.hash(mDeviceStateConfiguration); } - /** Checks if a specific flag is set - * @hide - * @deprecated in favor of {@link #hasProperty(int)} - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @Deprecated - public boolean hasFlag(int flagToCheckFor) { - return (mFlags & flagToCheckFor) == flagToCheckFor; - } - /** * Checks if a specific property is set on this state */ @@ -438,6 +314,7 @@ public final class DeviceState { * @see DeviceStateManager * @hide */ + @TestApi public static final class Configuration implements Parcelable { /** Unique identifier for the device state. */ @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = MAXIMUM_DEVICE_STATE_IDENTIFIER) @@ -563,29 +440,35 @@ public final class DeviceState { }; /** @hide */ - public static class Builder { + @TestApi + public static final class Builder { private final int mIdentifier; + @NonNull private final String mName; + @NonNull private Set<@SystemDeviceStateProperties Integer> mSystemProperties = Collections.emptySet(); + @NonNull private Set<@PhysicalDeviceStateProperties Integer> mPhysicalProperties = Collections.emptySet(); - public Builder(int identifier, String name) { + public Builder(int identifier, @NonNull String name) { mIdentifier = identifier; mName = name; } /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */ + @NonNull public Builder setSystemProperties( - Set<@SystemDeviceStateProperties Integer> systemProperties) { + @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties) { mSystemProperties = systemProperties; return this; } /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */ + @NonNull public Builder setPhysicalProperties( - Set<@PhysicalDeviceStateProperties Integer> physicalProperties) { + @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) { mPhysicalProperties = physicalProperties; return this; } @@ -594,6 +477,7 @@ public final class DeviceState { * Returns a new {@link DeviceState.Configuration} whose values match the values set on * the builder. */ + @NonNull public DeviceState.Configuration build() { return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties, mPhysicalProperties); diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index a4c383343062..febc24c1465a 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -49,6 +49,7 @@ public final class DeviceStateManager { * * @hide */ + @TestApi public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1; /** @@ -96,20 +97,6 @@ public final class DeviceStateManager { /** * Returns the list of device states that are supported and can be requested with * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. - * @deprecated use {@link #getSupportedDeviceStates()} - * @hide - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @TestApi - @Deprecated - @NonNull - public int[] getSupportedStates() { - return mGlobal.getSupportedStates(); - } - - /** - * Returns the list of device states that are supported and can be requested with - * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. */ @NonNull public List<DeviceState> getSupportedDeviceStates() { @@ -239,23 +226,6 @@ public final class DeviceStateManager { * Guaranteed to be called once on registration of the callback with the initial value and * then on every subsequent change in the supported states. * - * @param supportedStates the new supported states. - * - * @see DeviceStateManager#getSupportedStates() - * @deprecated use {@link #onSupportedStatesChanged(List)} - * @hide - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @TestApi - @Deprecated - default void onSupportedStatesChanged(@NonNull int[] supportedStates) {} - - /** - * Called in response to a change in the states supported by the device. - * <p> - * Guaranteed to be called once on registration of the callback with the initial value and - * then on every subsequent change in the supported states. - * * The supported device states may change due to certain states becoming unavailable * due to device configuration or device conditions such as if the device is too hot or * external monitors have been connected. @@ -267,51 +237,14 @@ public final class DeviceStateManager { default void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) {} /** - * Called in response to a change in the base device state. - * <p> - * The base state is the state of the device without considering any requests made through - * calls to {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)} - * from any client process. The base state is guaranteed to match the state provided with a - * call to {@link #onStateChanged(int)} when there are no active requests from any process. - * <p> - * Guaranteed to be called once on registration of the callback with the initial value and - * then on every subsequent change in the non-override state. - * - * @param state the new base device state. - * @deprecated use {@link #onDeviceStateChanged(DeviceState)} and query for physical - * properties that are relevant to your needs. - * @hide - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @TestApi - @Deprecated - default void onBaseStateChanged(int state) {} - - /** * Called in response to device state changes. * <p> * Guaranteed to be called once on registration of the callback with the initial value and * then on every subsequent change in device state. * * @param state the new device state. - * @deprecated use {@link #onDeviceStateChanged(DeviceState)} - * @hide */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - @TestApi - @Deprecated - void onStateChanged(int state); - - /** - * Called in response to device state changes. - * <p> - * Guaranteed to be called once on registration of the callback with the initial value and - * then on every subsequent change in device state. - * - * @param state the new device state. - */ - // TODO(b/325124054): Make non-default and remove deprecated callback methods. - default void onDeviceStateChanged(@NonNull DeviceState state) {} + void onDeviceStateChanged(@NonNull DeviceState state); } /** @@ -340,9 +273,6 @@ public final class DeviceStateManager { } @Override - public final void onStateChanged(int state) {} - - @Override public final void onDeviceStateChanged(@NonNull DeviceState deviceState) { final boolean folded; if (mFeatureFlags.deviceStatePropertyApi()) { diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index d6cc00d45ec4..0c8401992913 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -90,33 +90,6 @@ public final class DeviceStateManagerGlobal { } /** - * Returns the set of supported device states. - * - * @see DeviceStateManager#getSupportedStates() - */ - // TODO(b/325124054): Remove unused methods when clients are migrated. - public int[] getSupportedStates() { - synchronized (mLock) { - final DeviceStateInfo currentInfo; - if (mLastReceivedInfo != null) { - // If we have mLastReceivedInfo a callback is registered for this instance and it - // is receiving the most recent info from the server. Use that info here. - currentInfo = mLastReceivedInfo; - } else { - // If mLastReceivedInfo is null there is no registered callback so we manually - // fetch the current info. - try { - currentInfo = mDeviceStateManager.getDeviceStateInfo(); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - return getSupportedStateIdentifiersLocked(currentInfo.supportedStates); - } - } - - /** * Returns {@link List} of supported {@link DeviceState}s. * * @see DeviceStateManager#getSupportedDeviceStates() @@ -264,14 +237,8 @@ public final class DeviceStateManagerGlobal { mCallbacks.add(wrapper); if (mLastReceivedInfo != null) { - // Copy the array to prevent the callback from modifying the internal state. - final int[] supportedStates = getSupportedStateIdentifiersLocked( - mLastReceivedInfo.supportedStates); - wrapper.notifySupportedStatesChanged(supportedStates); wrapper.notifySupportedDeviceStatesChanged( List.copyOf(mLastReceivedInfo.supportedStates)); - wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState.getIdentifier()); - wrapper.notifyStateChanged(mLastReceivedInfo.currentState.getIdentifier()); wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState); } } @@ -330,15 +297,6 @@ public final class DeviceStateManagerGlobal { return -1; } - @GuardedBy("mLock") - private int[] getSupportedStateIdentifiersLocked(List<DeviceState> states) { - int[] identifiers = new int[states.size()]; - for (int i = 0; i < states.size(); i++) { - identifiers[i] = states.get(i).getIdentifier(); - } - return identifiers; - } - @Nullable private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) { for (int i = 0; i < mRequests.size(); i++) { @@ -353,12 +311,10 @@ public final class DeviceStateManagerGlobal { private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) { ArrayList<DeviceStateCallbackWrapper> callbacks; DeviceStateInfo oldInfo; - int[] supportedStateIdentifiers; synchronized (mLock) { oldInfo = mLastReceivedInfo; mLastReceivedInfo = info; callbacks = new ArrayList<>(mCallbacks); - supportedStateIdentifiers = getSupportedStateIdentifiersLocked(info.supportedStates); } final int diff = oldInfo == null ? ~0 : info.diff(oldInfo); @@ -366,18 +322,11 @@ public final class DeviceStateManagerGlobal { for (int i = 0; i < callbacks.size(); i++) { callbacks.get(i).notifySupportedDeviceStatesChanged( List.copyOf(info.supportedStates)); - callbacks.get(i).notifySupportedStatesChanged(supportedStateIdentifiers); - } - } - if ((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0) { - for (int i = 0; i < callbacks.size(); i++) { - callbacks.get(i).notifyBaseStateChanged(info.baseState.getIdentifier()); } } if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) { for (int i = 0; i < callbacks.size(); i++) { callbacks.get(i).notifyDeviceStateChanged(info.currentState); - callbacks.get(i).notifyStateChanged(info.currentState.getIdentifier()); } } } @@ -439,26 +388,11 @@ public final class DeviceStateManagerGlobal { mExecutor = executor; } - void notifySupportedStatesChanged(int[] newSupportedStates) { - mExecutor.execute(() -> - mDeviceStateCallback.onSupportedStatesChanged(newSupportedStates)); - } - void notifySupportedDeviceStatesChanged(List<DeviceState> newSupportedDeviceStates) { mExecutor.execute(() -> mDeviceStateCallback.onSupportedStatesChanged(newSupportedDeviceStates)); } - void notifyBaseStateChanged(int newBaseState) { - execute("notifyBaseStateChanged", - () -> mDeviceStateCallback.onBaseStateChanged(newBaseState)); - } - - void notifyStateChanged(int newDeviceState) { - execute("notifyStateChanged", - () -> mDeviceStateCallback.onStateChanged(newDeviceState)); - } - void notifyDeviceStateChanged(DeviceState newDeviceState) { execute("notifyDeviceStateChanged", () -> mDeviceStateCallback.onDeviceStateChanged(newDeviceState)); diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java index 893d765e48da..7665e2f173b9 100644 --- a/core/java/android/hardware/devicestate/DeviceStateRequest.java +++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java @@ -115,8 +115,8 @@ public final class DeviceStateRequest { * requested state. * <p> * Guaranteed to be called after a call to - * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} with a state - * matching the requested state. + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} with a + * state matching the requested state. */ default void onRequestActivated(@NonNull DeviceStateRequest request) {} @@ -124,7 +124,7 @@ public final class DeviceStateRequest { * Called to indicate the request has been temporarily suspended. * <p> * Guaranteed to be called before a call to - * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}. + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}. */ default void onRequestSuspended(@NonNull DeviceStateRequest request) {} @@ -134,7 +134,7 @@ public final class DeviceStateRequest { * DeviceStateRequest.Callback)}. * <p> * Guaranteed to be called before a call to - * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}. + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}. * <p> * Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to * occur before this method. diff --git a/core/java/android/hardware/devicestate/DeviceStateUtil.java b/core/java/android/hardware/devicestate/DeviceStateUtil.java new file mode 100644 index 000000000000..627e740f4a5c --- /dev/null +++ b/core/java/android/hardware/devicestate/DeviceStateUtil.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.devicestate; + +import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; + +import android.annotation.NonNull; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * Utilities for {@link DeviceStateManager}. + * @hide + */ +public class DeviceStateUtil { + private DeviceStateUtil() { } + + /** + * Returns the state identifier of the {@link DeviceState} that matches the + * {@code currentState}s physical properties. This will return the identifier of the + * {@link DeviceState} that matches the devices physical configuration. + * + * Returns {@link INVALID_DEVICE_STATE_IDENTIFIER} if there is no {@link DeviceState} in the + * provided list of {@code supportedStates} that matches. + * @hide + */ + public static int calculateBaseStateIdentifier(@NonNull DeviceState currentState, + @NonNull List<DeviceState> supportedStates) { + DeviceState.Configuration stateConfiguration = currentState.getConfiguration(); + for (int i = 0; i < supportedStates.size(); i++) { + DeviceState stateToCompare = supportedStates.get(i); + if (stateToCompare.getConfiguration().getPhysicalProperties().isEmpty()) { + continue; + } + if (isDeviceStateMatchingPhysicalProperties(stateConfiguration.getPhysicalProperties(), + supportedStates.get(i))) { + return supportedStates.get(i).getIdentifier(); + } + } + return INVALID_DEVICE_STATE_IDENTIFIER; + } + + /** + * Returns if the physical properties provided, matches the same physical properties on the + * provided {@link DeviceState}. + */ + private static boolean isDeviceStateMatchingPhysicalProperties( + Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties, + DeviceState state) { + Iterator<@DeviceState.PhysicalDeviceStateProperties Integer> iterator = + physicalProperties.iterator(); + while (iterator.hasNext()) { + if (!state.hasProperty(iterator.next())) { + return false; + } + } + return true; + } + +} diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index eb26a768f2a6..4894fb1ec452 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1653,6 +1653,22 @@ public final class DisplayManager { } /** + * Allows internal application to restrict display modes to specified modeIds + * + * @param displayId display that restrictions will be applied to + * @param modeIds allowed mode ids + * + * @hide + */ + @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES") + public void requestDisplayModes(int displayId, @Nullable int[] modeIds) { + if (modeIds != null && modeIds.length == 0) { + throw new IllegalArgumentException("requestDisplayModes: modesIds can't be empty"); + } + mGlobal.requestDisplayModes(displayId, modeIds); + } + + /** * Listens for changes in available display devices. */ public interface DisplayListener { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 75f0ceb7e651..3d7b714a2f5b 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -38,6 +38,7 @@ import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.graphics.common.DisplayDecorationSupport; import android.media.projection.IMediaProjection; import android.media.projection.MediaProjection; +import android.os.Binder; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; @@ -138,6 +139,8 @@ public final class DisplayManagerGlobal { private int mWifiDisplayScanNestCount; + private final Binder mToken = new Binder(); + @VisibleForTesting public DisplayManagerGlobal(IDisplayManager dm) { mDm = dm; @@ -1182,6 +1185,20 @@ public final class DisplayManagerGlobal { } } + /** + * Sets allowed display mode ids + * + * @hide + */ + @RequiresPermission("android.permission.RESTRICT_DISPLAY_MODES") + public void requestDisplayModes(int displayId, @Nullable int[] modeIds) { + try { + mDm.requestDisplayModes(mToken, displayId, modeIds); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, @DisplayEvent int event) { diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 83de4e45cf2f..70efc6f2e33f 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -235,4 +235,8 @@ interface IDisplayManager { // Disable a connected display that is enabled. @EnforcePermission("MANAGE_DISPLAYS") void disableConnectedDisplay(int displayId); + + // Restricts display modes to specified modeIds. + @EnforcePermission("RESTRICT_DISPLAY_MODES") + void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index fdaa0b467566..84619a0eee2e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1024,7 +1024,11 @@ public class UserManager { /** * Specifies if a user is disallowed from creating a private profile. * <p>The default value for an unmanaged user is <code>false</code>. - * For users with a device owner set, the default is <code>true</code>. + * For users with a device owner set, the default value is <code>true</code> and the + * device owner currently cannot change it to <code>false</code>. + * On organization-owned managed profile devices, the default value is <code>false</code> but + * the profile owner can change it to <code>true</code> via the parent profile to block creating + * of private profiles on the personal user. * * <p>Holders of the permission * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILES} @@ -1034,9 +1038,10 @@ public class UserManager { * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see DevicePolicyManager#getParentProfileInstance(ComponentName) * @see #getUserRestrictions() - * @hide */ + @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE) public static final String DISALLOW_ADD_PRIVATE_PROFILE = "no_add_private_profile"; /** @@ -3189,6 +3194,7 @@ public class UserManager { conditional = true) @UserHandleAware public boolean canAddPrivateProfile() { + if (!android.multiuser.Flags.enablePrivateSpaceFeatures()) return false; if (android.multiuser.Flags.blockPrivateSpaceCreation()) { try { return mService.canAddPrivateProfile(mUserId); diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 943014caeb09..375d729a4e08 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -38,6 +38,7 @@ flag { bug: "288119641" } +# This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations. flag { name: "allow_private_profile" namespace: "profile_experiences" diff --git a/core/java/android/service/media/OWNERS b/core/java/android/service/media/OWNERS deleted file mode 100644 index 916fc36ffbc6..000000000000 --- a/core/java/android/service/media/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -# Bug component: 137631 - -hdmoon@google.com -insun@google.com -jaewan@google.com -jinpark@google.com -klhyun@google.com -gyumin@google.com diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java index 27e862868858..681544075e03 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java @@ -222,7 +222,10 @@ public abstract class OnDeviceIntelligenceService extends Service { /** * Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference - * service if there is a state change to be performed. + * service if there is a state change to be performed. State change could be config updates, + * performing initialization or cleanup tasks in the remote inference service. + * The Bundle passed in here is expected to be read-only and will be rejected if it has any + * writable fields as detailed under {@link InferenceParams}. * * @param processingState the updated state to be applied. * @param callbackExecutor executor to the run status callback on. diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java index 9b9cc1933b93..7a1c75a6034f 100644 --- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java +++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java @@ -19,6 +19,7 @@ package android.service.persistentdata; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -100,7 +101,7 @@ public class PersistentDataBlockManager { */ @SystemApi @SuppressLint("RequiresPermission") - public int write(byte[] data) { + public int write(@Nullable byte[] data) { try { return sService.write(data); } catch (RemoteException e) { @@ -115,7 +116,7 @@ public class PersistentDataBlockManager { */ @SystemApi @SuppressLint("RequiresPermission") - public byte[] read() { + public @Nullable byte[] read() { try { return sService.read(); } catch (RemoteException e) { diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index a098e4d6b589..4f5b51d04c4b 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -2631,7 +2631,10 @@ public final class SurfaceControl implements Parcelable { * Threshold values that are sent with * {@link Transaction#setTrustedPresentationCallback(SurfaceControl, * TrustedPresentationThresholds, Executor, Consumer)} + * + * @deprecated Use {@link android.window.TrustedPresentationThresholds} instead. */ + @Deprecated public static final class TrustedPresentationThresholds { private final float mMinAlpha; private final float mMinFractionRendered; @@ -4451,8 +4454,7 @@ public final class SurfaceControl implements Parcelable { * <tr><td>o</td><td>x</td><td>x</td><td>x</td></tr> * </table> * </blockquote> - * - *<p> + * <p> * We first start by computing fr=xscale*yscale=0.9*0.9=0.81, indicating * that "81%" of the pixels were rendered. This corresponds to what was 100 * pixels being displayed in 81 pixels. This is somewhat of an abuse of @@ -4469,6 +4471,7 @@ public final class SurfaceControl implements Parcelable { * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in * this API as well. * <p> + * * @param sc The {@link SurfaceControl} to set the callback on * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to * invoke the callback. @@ -4477,7 +4480,11 @@ public final class SurfaceControl implements Parcelable { * exited the threshold. * @return This transaction * @see TrustedPresentationThresholds + * @deprecated Use + * {@link WindowManager#registerTrustedPresentationListener(IBinder, + * android.window.TrustedPresentationThresholds, Executor, Consumer)} instead. */ + @Deprecated @NonNull public Transaction setTrustedPresentationCallback(@NonNull SurfaceControl sc, @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor, @@ -4506,7 +4513,10 @@ public final class SurfaceControl implements Parcelable { * * @param sc The SurfaceControl that the callback should be cleared from * @return This transaction + * @deprecated Use {@link WindowManager#unregisterTrustedPresentationListener(Consumer)} + * instead. */ + @Deprecated @NonNull public Transaction clearTrustedPresentationCallback(@NonNull SurfaceControl sc) { checkPreconditions(sc); diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 6f757df66d42..04ec4d0d382d 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -583,13 +583,16 @@ public class SurfaceControlViewHost { * associated host {@link InputTransferToken}. * * @return Whether the touch stream was transferred. + * @deprecated Use {@link WindowManager#transferTouchGesture(InputTransferToken, + * InputTransferToken)} instead. */ + @Deprecated public boolean transferTouchGestureToHost() { if (mViewRoot == null) { return false; } - final WindowManager wm = - (WindowManager) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE); + final WindowManager wm = (WindowManager) mViewRoot.mContext.getSystemService( + Context.WINDOW_SERVICE); InputTransferToken embeddedToken = getInputTransferToken(); InputTransferToken hostToken = mWm.mHostInputTransferToken; if (embeddedToken == null || hostToken == null) { diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index d494e2811747..c95d6ffe6268 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1946,7 +1946,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * be passed from the host process to the client process. * * @return The token + * @deprecated Use {@link AttachedSurfaceControl#getInputTransferToken()} instead. */ + @Deprecated public @Nullable IBinder getHostToken() { final ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot == null) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 64846d082601..c127a432f0bb 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1571,8 +1571,9 @@ public interface WindowManager extends ViewManager { /** * Value applicable for the {@link #PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN} property to * provide a signal to the system that an application or its specific activities explicitly - * opt into being displayed on small foldable device cover screens that measure at least 1.5 - * inches for the shorter dimension and at least 2.4 inches for the longer dimension. + * opt into being displayed on small cover screens on flippable style foldable devices that + * measure at least 1.5 inches up to 2.2 inches for the shorter dimension and at least 2.4 + * inches up to 3.4 inches for the longer dimension */ @CompatSmallScreenPolicy @FlaggedApi(Flags.FLAG_COVER_DISPLAY_OPT_IN) diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index e15baaeef570..1d9eb715ee3b 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -3056,19 +3056,17 @@ public final class AutofillManager { } @GuardedBy("mLock") - private void handleFailedIdsLocked(ArrayList<AutofillId> failedIds) { - if (failedIds != null && !failedIds.isEmpty()) { - if (sVerbose) { - Log.v(TAG, "autofill(): total failed views: " + failedIds); - } - try { - mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId()); - } catch (RemoteException e) { - // In theory, we could ignore this error since it's not a big deal, but - // in reality, we rather crash the app anyways, as the failure could be - // a consequence of something going wrong on the server side... - throw e.rethrowFromSystemServer(); - } + private void handleFailedIdsLocked(@NonNull ArrayList<AutofillId> failedIds) { + if (!failedIds.isEmpty() && sVerbose) { + Log.v(TAG, "autofill(): total failed views: " + failedIds); + } + try { + mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId()); + } catch (RemoteException e) { + // In theory, we could ignore this error since it's not a big deal, but + // in reality, we rather crash the app anyways, as the failure could be + // a consequence of something going wrong on the server side... + throw e.rethrowFromSystemServer(); } } @@ -3090,7 +3088,7 @@ public final class AutofillManager { final View[] views = client.autofillClientFindViewsByAutofillIdTraversal( Helper.toArray(ids)); - ArrayList<AutofillId> failedIds = null; + ArrayList<AutofillId> failedIds = new ArrayList<>(); for (int i = 0; i < itemCount; i++) { final AutofillId id = ids.get(i); @@ -3101,9 +3099,6 @@ public final class AutofillManager { // the service; this is fine, but we need to update the view status in the // server side so it can be triggered again. Log.d(TAG, "autofill(): no View with id " + id); - if (failedIds == null) { - failedIds = new ArrayList<>(); - } failedIds.add(id); continue; } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index c7695187e52d..6bd273bc1aeb 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -659,6 +659,7 @@ public class IntentForwarderActivity extends Activity { private boolean privateSpaceFlagsEnabled() { return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() && android.multiuser.Flags.enablePrivateSpaceIntentRedirection(); } diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 40a437f300f3..9dec1027d398 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -129,7 +129,6 @@ public class TransitionAnimation { private final int mDefaultWindowAnimationStyleResId; private final boolean mDebug; - private final boolean mGridLayoutRecentsEnabled; private final boolean mLowRamRecentsEnabled; public TransitionAnimation(Context context, boolean debug, String tag) { @@ -166,7 +165,6 @@ public class TransitionAnimation { mConfigShortAnimTime = context.getResources().getInteger( com.android.internal.R.integer.config_shortAnimTime); - mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false); mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic(); final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes( @@ -768,10 +766,8 @@ public class TransitionAnimation { // We scale the width and clip to the top/left square float scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right); - if (!mGridLayoutRecentsEnabled) { - int unscaledThumbHeight = (int) (thumbHeight / scale); - mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight; - } + int unscaledThumbHeight = (int) (thumbHeight / scale); + mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight; Animation scaleAnim = new ScaleAnimation( scaleUp ? scale : 1, scaleUp ? 1 : scale, @@ -887,12 +883,6 @@ public class TransitionAnimation { toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top; pivotX = mTmpRect.width() / 2; pivotY = appRect.height() / 2 / scaleW; - if (mGridLayoutRecentsEnabled) { - // In the grid layout, the header is displayed above the thumbnail instead of - // overlapping it. - fromY -= thumbHeightI; - toY -= thumbHeightI * scaleW; - } } else { pivotX = 0; pivotY = 0; @@ -936,10 +926,7 @@ public class TransitionAnimation { // This AnimationSet uses the Interpolators assigned above. AnimationSet set = new AnimationSet(false); set.addAnimation(scale); - if (!mGridLayoutRecentsEnabled) { - // In the grid layout, the header should be shown for the whole animation. - set.addAnimation(alpha); - } + set.addAnimation(alpha); set.addAnimation(translate); set.addAnimation(clipAnim); a = set; @@ -958,10 +945,7 @@ public class TransitionAnimation { // This AnimationSet uses the Interpolators assigned above. AnimationSet set = new AnimationSet(false); set.addAnimation(scale); - if (!mGridLayoutRecentsEnabled) { - // In the grid layout, the header should be shown for the whole animation. - set.addAnimation(alpha); - } + set.addAnimation(alpha); set.addAnimation(translate); a = set; @@ -1081,8 +1065,7 @@ public class TransitionAnimation { * @return whether the transition should show the thumbnail being scaled down. */ private boolean shouldScaleDownThumbnailTransition(int orientation) { - return mGridLayoutRecentsEnabled - || orientation == Configuration.ORIENTATION_PORTRAIT; + return orientation == Configuration.ORIENTATION_PORTRAIT; } private static int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) { diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index a2efbd29c54a..a22232ac945e 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -376,6 +376,12 @@ oneway interface IStatusBar void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop); /** + * Set the split screen focus to the left / top app or the right / bottom app based on + * {@param leftOrTop}. + */ + void setSplitscreenFocus(boolean leftOrTop); + + /** * Shows the media output switcher dialog. * * @param packageName of the session for which the output switcher is shown. diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 07cbaadf0580..3a1e8835c8db 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -19,24 +19,23 @@ //#define LOG_NDEBUG 0 -#include <inttypes.h> - -#include <nativehelper/JNIHelp.h> - #include <android-base/stringprintf.h> #include <android_runtime/AndroidRuntime.h> +#include <input/InputConsumer.h> #include <input/InputTransport.h> +#include <inttypes.h> #include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> #include <utils/Looper.h> + #include <variant> #include <vector> + #include "android_os_MessageQueue.h" #include "android_view_InputChannel.h" #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" - -#include <nativehelper/ScopedLocalRef.h> - #include "core_jni_helpers.h" namespace android { diff --git a/core/res/Android.bp b/core/res/Android.bp index e2e419fcf5b7..a44e92ca4019 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -156,11 +156,13 @@ android_app { generate_product_characteristics_rro: true, flags_packages: [ + "android.app.contextualsearch.flags-aconfig", "android.content.pm.flags-aconfig", "android.provider.flags-aconfig", "camera_platform_flags", "android.net.platform.flags-aconfig", "com.android.window.flags.window-aconfig", + "android.permission.flags-aconfig", ], } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8ea742d19047..9673fc7b083b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3801,12 +3801,12 @@ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK" android:protectionLevel="internal|role" /> - <!-- Allows an application to manage policy related to theft detection. + <!-- Allows an application to query the device stolen state. @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") @hide @SystemApi --> - <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION" + <permission android:name="android.permission.QUERY_DEVICE_STOLEN_STATE" android:protectionLevel="internal|role" /> <!-- Allows an application to manage policy related to system apps. @@ -6781,7 +6781,7 @@ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") --> <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" - android:protectionLevel="signature" /> + android:protectionLevel="signature|privileged" /> <!-- Allows an application to control keyguard. Only allowed for system processes. @hide --> @@ -6920,7 +6920,8 @@ projection. This is intended for on device screen recording system app. @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") --> <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT" - android:protectionLevel="signature"/> + android:protectionLevel="signature" + android:featureFlag="android.permission.flags.sensitive_notification_app_protection"/> <!-- @SystemApi Allows an application to read install sessions @hide This is not a third-party API (intended for system apps). --> @@ -7207,6 +7208,13 @@ <permission android:name="android.permission.ACCESS_SMARTSPACE" android:protectionLevel="signature|privileged|development" /> + <!-- @SystemApi Allows an application to start a contextual search. + @FlaggedApi("android.app.contextualsearch.flags.enable_service") + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.ACCESS_CONTEXTUAL_SEARCH" + android:protectionLevel="signature|privileged" + android:featureFlag="android.app.contextualsearch.flags.enable_service"/> + <!-- @SystemApi Allows an application to manage the wallpaper effects generation service. @hide <p>Not for use by third-party applications.</p> --> @@ -8149,6 +8157,14 @@ <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES" android:protectionLevel="signature|privileged"/> + <!-- Allows internal applications to restrict display modes + <p>Not for use by third-party applications. + <p>Protection level: signature + @hide + --> + <permission android:name="android.permission.RESTRICT_DISPLAY_MODES" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f59c0993c34f..e3f1cb619eb5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3586,14 +3586,6 @@ <!-- The minimum number of visible recent tasks to be presented to the user through the SystemUI. Can be -1 if there is no minimum limit. --> - <integer name="config_minNumVisibleRecentTasks_grid">-1</integer> - - <!-- The maximum number of visible recent tasks to be presented to the user through the - SystemUI. Can be -1 if there is no maximum limit. --> - <integer name="config_maxNumVisibleRecentTasks_grid">9</integer> - - <!-- The minimum number of visible recent tasks to be presented to the user through the - SystemUI. Can be -1 if there is no minimum limit. --> <integer name="config_minNumVisibleRecentTasks_lowRam">-1</integer> <!-- The maximum number of visible recent tasks to be presented to the user through the @@ -6985,4 +6977,8 @@ <!-- The key containing the branching boolean for legacy Search. --> <string name="config_defaultContextualSearchLegacyEnabled" translatable="false" /> + + <!-- Whether WM DisplayContent supports high performance transitions + (lower-end devices may want to disable) --> + <bool name="config_deviceSupportsHighPerfTransitions">true</bool> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c603fa78b08e..9628d301fc5b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -392,8 +392,6 @@ <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" /> <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" /> <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" /> - <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" /> - <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_grid" /> <java-symbol type="integer" name="config_minNumVisibleRecentTasks" /> <java-symbol type="integer" name="config_maxNumVisibleRecentTasks" /> <java-symbol type="integer" name="config_activeTaskDurationHours" /> @@ -5376,4 +5374,7 @@ <java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" /> <java-symbol type="string" name="unarchival_session_app_label" /> + + <!-- Whether WM DisplayContent supports high performance transitions --> + <java-symbol type="bool" name="config_deviceSupportsHighPerfTransitions" /> </resources> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 9a41fe0691fa..5cc1ee46f61c 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -1249,31 +1249,25 @@ public class NotificationTest { } @Test - @Ignore // TODO: b/329402256 - Restore or delete - public void testBigPictureStyle_setExtras_pictureIconNull_pictureIconKeyNull() { + public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() { Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture((Bitmap) null); Bundle extras = new Bundle(); bpStyle.addExtras(extras); - assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isTrue(); - final Parcelable pictureIcon = extras.getParcelable(EXTRA_PICTURE_ICON); - assertThat(pictureIcon).isNull(); + assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); } @Test - @Ignore // TODO: b/329402256 - Restore or delete - public void testBigPictureStyle_setExtras_pictureIconNull_pictureKeyNull() { + public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() { Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture((Bitmap) null); Bundle extras = new Bundle(); bpStyle.addExtras(extras); - assertThat(extras.containsKey(EXTRA_PICTURE)).isTrue(); - final Parcelable picture = extras.getParcelable(EXTRA_PICTURE); - assertThat(picture).isNull(); + assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse(); } @Test diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index 2905a5aa8c65..e118c98dd4da 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -31,7 +31,6 @@ import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.test.AndroidTestCase; import android.util.Log; -import android.util.Printer; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -47,15 +46,12 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Vector; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Phaser; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; @RunWith(AndroidJUnit4.class) @SmallTest @@ -407,138 +403,4 @@ public class SQLiteDatabaseTest { } assertFalse(allowed); } - - /** Dumpsys information about a single database. */ - - /** - * Collect and parse dumpsys output. This is not a full parser. It is only enough to support - * the unit tests. - */ - private static class Dumpsys { - // Regular expressions for parsing the output. Reportedly, regular expressions are - // expensive, so these are created only if a dumpsys object is created. - private static final Object sLock = new Object(); - static Pattern mPool; - static Pattern mConnection; - static Pattern mEntry; - static Pattern mSingleWord; - static Pattern mNone; - - // The raw strings read from dumpsys. Once loaded, this list never changes. - final ArrayList<String> mRaw = new ArrayList<>(); - - // Parsed dumpsys. This contains only the bits that are being tested. - static class Connection { - ArrayList<String> mRecent = new ArrayList<>(); - ArrayList<String> mLong = new ArrayList<>(); - } - static class Database { - String mPath; - ArrayList<Connection> mConnection = new ArrayList<>(); - } - ArrayList<Database> mDatabase; - ArrayList<String> mConcurrent; - - Dumpsys() { - SQLiteDebug.dump( - new Printer() { public void println(String x) { mRaw.add(x); } }, - new String[0]); - parse(); - } - - /** Parse the raw text. Return true if no errors were detected. */ - boolean parse() { - initialize(); - - // Reset the parsed information. This method may be called repeatedly. - mDatabase = new ArrayList<>(); - mConcurrent = new ArrayList<>(); - - Database current = null; - Connection connection = null; - Matcher matcher; - for (int i = 0; i < mRaw.size(); i++) { - final String line = mRaw.get(i); - matcher = mPool.matcher(line); - if (matcher.lookingAt()) { - current = new Database(); - mDatabase.add(current); - current.mPath = matcher.group(1); - continue; - } - matcher = mConnection.matcher(line); - if (matcher.lookingAt()) { - connection = new Connection(); - current.mConnection.add(connection); - continue; - } - - if (line.contains("Most recently executed operations")) { - i += readTable(connection.mRecent, i, mEntry); - continue; - } - - if (line.contains("Operations exceeding 2000ms")) { - i += readTable(connection.mLong, i, mEntry); - continue; - } - if (line.contains("Concurrently opened database files")) { - i += readTable(mConcurrent, i, mSingleWord); - continue; - } - } - return true; - } - - /** - * Read a series of lines following a header. Return the number of lines read. The input - * line number is the number of the header. - */ - private int readTable(List<String> s, int header, Pattern p) { - // Special case: if the first line is "<none>" then there are no more lines to the - // table. - if (lookingAt(header+1, mNone)) return 1; - - int i; - for (i = header + 1; i < mRaw.size() && lookingAt(i, p); i++) { - s.add(mRaw.get(i).trim()); - } - return i - header; - } - - /** Return true if the n'th raw line matches the pattern. */ - boolean lookingAt(int n, Pattern p) { - return p.matcher(mRaw.get(n)).lookingAt(); - } - - /** Compile the regular expressions the first time. */ - private static void initialize() { - synchronized (sLock) { - if (mPool != null) return; - mPool = Pattern.compile("Connection pool for (\\S+):"); - mConnection = Pattern.compile("\\s+Connection #(\\d+):"); - mEntry = Pattern.compile("\\s+(\\d+): "); - mSingleWord = Pattern.compile(" (\\S+)$"); - mNone = Pattern.compile("\\s+<none>$"); - } - } - } - - @Test - public void testDumpsys() throws Exception { - Dumpsys dumpsys = new Dumpsys(); - - assertEquals(1, dumpsys.mDatabase.size()); - // Note: cannot test mConcurrent because that attribute is not hermitic with respect to - // the tests. - - Dumpsys.Database db = dumpsys.mDatabase.get(0); - - // Work with normalized paths. - String wantPath = mDatabaseFile.toPath().toRealPath().toString(); - String realPath = new File(db.mPath).toPath().toRealPath().toString(); - assertEquals(wantPath, realPath); - - assertEquals(1, db.mConnection.size()); - } } diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 48ba52621f64..fc1fbb5368dc 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -120,6 +120,10 @@ public class ImeInsetsSourceConsumerTest { final var statsToken = ImeTracker.Token.empty(); mImeConsumer.onWindowFocusGained(true); mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken); + // Called once through the show flow. + verify(mController).applyAnimation( + eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, + eq(statsToken)); // set control and verify visibility is applied. InsetsSourceControl control = new InsetsSourceControl(ID_IME, @@ -127,11 +131,11 @@ public class ImeInsetsSourceConsumerTest { mController.onControlsChanged(new InsetsSourceControl[] { control }); // IME show animation should be triggered when control becomes available. verify(mController).applyAnimation( - eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(statsToken)); + eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, + and(not(eq(statsToken)), notNull())); verify(mController, never()).applyAnimation( - eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */, - eq(statsToken)); + eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(false) /* fromIme */, + and(not(eq(statsToken)), notNull())); }); } @@ -159,6 +163,10 @@ public class ImeInsetsSourceConsumerTest { final var statsToken = ImeTracker.Token.empty(); if (imeVisible) { mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsToken); + // Called once through the show flow. + verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), + eq(true) /* show */, eq(true) /* fromIme */, + eq(false) /* skipAnim */, eq(statsToken)); } // set control and verify visibility is applied. @@ -184,13 +192,17 @@ public class ImeInsetsSourceConsumerTest { if (!hasViewFocus) { final var statsTokenNext = ImeTracker.Token.empty(); mController.show(WindowInsets.Type.ime(), true /* fromIme */, statsTokenNext); + // Called once through the show flow. + verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), + eq(true) /* show */, eq(true) /* fromIme */, + eq(false) /* skipAnim */, eq(statsTokenNext)); mController.onControlsChanged(new InsetsSourceControl[]{ control }); // Verify IME show animation should be triggered when control becomes available and // the animation will be skipped by getAndClearSkipAnimationOnce invoked. verify(control).getAndClearSkipAnimationOnce(); verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), - eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */, eq(statsTokenNext)); + eq(true) /* show */, eq(false) /* fromIme */, + eq(true) /* skipAnim */, and(not(eq(statsToken)), notNull())); } }); } diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index 43e62275152e..dbabceaaf3cd 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -641,6 +641,7 @@ public class IntentForwarderActivityTest { public void shouldForwardToParent_telephony_privateProfile() throws Exception { mSetFlagsRule.enableFlags( android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION); sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME; diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb Binary files differindex 0415e44af72a..e1670be94767 100644 --- a/data/etc/core.protolog.pb +++ b/data/etc/core.protolog.pb diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml index 3b1867cb4df0..973bcb5c1475 100644 --- a/data/etc/enhanced-confirmation.xml +++ b/data/etc/enhanced-confirmation.xml @@ -24,33 +24,49 @@ Example usage: <enhanced-confirmation-trusted-package package="com.example.app" sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/> - ... <enhanced-confirmation-trusted-installer package="com.example.installer" sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/> - ... -The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app" -should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions. +The <enhanced-confirmation-trusted-package> entry shown in the above example indicates that +"com.example.app" should be considered a "trusted package". A "trusted package" will be exempt from +ECM restrictions. + +The <enhanced-confirmation-trusted-installer> entry shown in the above example indicates that +"com.example.app" should be considered a "trusted installer". Apps installed by "trusted installers" +will be exempt from ECM restrictions, with conditions explained in the next few paragraphs. + +If zero <enhanced-confirmation-trusted-installer> entries are declared, then *all* packages will be +exempt from ECM restrictions, except apps meeting *all* of the following criteria: + + A. The app is not pre-installed, and + B. The app has no matching <enhanced-confirmation-trusted-package> entries declared, and + C. The app is marked by its installer as coming from an untrustworthy package source. + +(For example, an installer may set an app's package source to +PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE, +which are considered untrustworthy.) + +If one or more <enhanced-confirmation-trusted-installer> entries are declared, then packages must, +in order to be exempt from ECM, meet at least one of the following criteria: -The "enhanced-confirmation-trusted-installer" entry shown above indicates that -"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all -packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this. -For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM -restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE -or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.) + A. Be installed by an installer with a matching <enhanced-confirmation-trusted-installer> entry + declared, and be marked as coming from an "trustworthy" package source by the installer, or + B. Be installed via a pre-installed installer, and be marked as coming from a "trustworthy" + package source by the installer, or + C. Have a matching <enhanced-confirmation-trusted-package> entry declared. -In either case: +For either type of XML element: - The "package" XML attribute refers to the app's package name. - The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate. For any entry to successfully apply to a package, both XML attributes must be present, and must match the package. That is, the package name must match the "package" attribute, and the app must be -signed by the signing certificate identified by the "sha256-cert-digest" attribute.. +signed by the signing certificate identified by the "sha256-cert-digest" attribute. --> <config></config> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 1aa8af5f5e84..d410d5f5400e 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3421,6 +3421,24 @@ "group": "WM_DEBUG_WALLPAPER", "at": "com\/android\/server\/wm\/WallpaperController.java" }, + "-6856158722649737204": { + "message": "Waiting for offset complete...", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, + "-5966696477376431672": { + "message": "Offset complete!", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, + "4198834090919802045": { + "message": "Timeout waiting for wallpaper to offset: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "-3477087868568520027": { "message": "No longer animating wallpaper targets!", "level": "VERBOSE", @@ -3451,8 +3469,32 @@ "group": "WM_DEBUG_WALLPAPER", "at": "com\/android\/server\/wm\/WallpaperController.java" }, - "-2504764636812266719": { - "message": "New wallpaper: target=%s prev=%s", + "7408402065665963407": { + "message": "Wallpaper at display %d - visibility: %b, keyguardLocked: %b", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, + "-8598497865499265448": { + "message": "Wallpaper target=%s prev=%s", + "level": "DEBUG", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, + "-5402010429724738603": { + "message": "Wallpaper should be visible but has not been drawn yet. mWallpaperDrawState=%d", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, + "4151327328872447804": { + "message": "New home screen wallpaper: %s, prev: %s", + "level": "DEBUG", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, + "6943105284590482059": { + "message": "New lock\/shared screen wallpaper: %s, prev: %s", "level": "DEBUG", "group": "WM_DEBUG_WALLPAPER", "at": "com\/android\/server\/wm\/WallpaperController.java" diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java index fa35b632a6b3..98935e95deaf 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java @@ -19,13 +19,14 @@ package androidx.window.common; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; -import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE; import static androidx.window.common.CommonFoldingFeature.parseListFromString; import android.annotation.NonNull; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; +import android.hardware.devicestate.DeviceStateUtil; import android.text.TextUtils; import android.util.Log; import android.util.SparseIntArray; @@ -54,29 +55,27 @@ public final class DeviceStateManagerFoldingFeatureProducer private static final boolean DEBUG = false; /** - * Emulated device state {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} to + * Emulated device state + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} to * {@link CommonFoldingFeature.State} map. */ private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); /** - * Emulated device state received via - * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}. - * "Emulated" states differ from "base" state in the sense that they may not correspond 1:1 with - * physical device states. They represent the state of the device when various software - * features and APIs are applied. The emulated states generally consist of all "base" states, - * but may have additional states such as "concurrent" or "rear display". Concurrent mode for - * example is activated via public API and can be active in both the "open" and "half folded" - * device states. + * Device state received via + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}. + * The identifier returned through {@link DeviceState#getIdentifier()} may not correspond 1:1 + * with the physical state of the device. This could correspond to the system state of the + * device when various software features or overrides are applied. The emulated states generally + * consist of all "base" states, but may have additional states such as "concurrent" or + * "rear display". Concurrent mode for example is activated via public API and can be active in + * both the "open" and "half folded" device states. */ - private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; + private DeviceState mCurrentDeviceState = new DeviceState( + new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER, + "INVALID").build()); - /** - * Base device state received via - * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}. - * "Base" in this context means the "physical" state of the device. - */ - private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; + private List<DeviceState> mSupportedStates; @NonNull private final RawFoldingFeatureProducer mRawFoldSupplier; @@ -85,22 +84,11 @@ public final class DeviceStateManagerFoldingFeatureProducer private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() { @Override - public void onStateChanged(int state) { + public void onDeviceStateChanged(@NonNull DeviceState state) { mCurrentDeviceState = state; mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer .this::notifyFoldingFeatureChange); } - - @Override - public void onBaseStateChanged(int state) { - mCurrentBaseDeviceState = state; - - if (mDeviceStateToPostureMap.get(mCurrentDeviceState) - == COMMON_STATE_USE_BASE_STATE) { - mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer - .this::notifyFoldingFeatureChange); - } - } }; public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context, @@ -109,6 +97,7 @@ public final class DeviceStateManagerFoldingFeatureProducer mRawFoldSupplier = rawFoldSupplier; String[] deviceStatePosturePairs = context.getResources() .getStringArray(R.array.config_device_state_postures); + mSupportedStates = deviceStateManager.getSupportedDeviceStates(); boolean isHalfOpenedSupported = false; for (String deviceStatePosturePair : deviceStatePosturePairs) { String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); @@ -168,7 +157,7 @@ public final class DeviceStateManagerFoldingFeatureProducer */ private boolean isCurrentStateValid() { // If the device state is not found in the map, indexOfKey returns a negative number. - return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0; + return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState.getIdentifier()) >= 0; } @Override @@ -177,7 +166,9 @@ public final class DeviceStateManagerFoldingFeatureProducer if (hasListeners()) { mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange); } else { - mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; + mCurrentDeviceState = new DeviceState( + new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER, + "INVALID").build()); mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange); } } @@ -251,10 +242,13 @@ public final class DeviceStateManagerFoldingFeatureProducer @CommonFoldingFeature.State private int currentHingeState() { @CommonFoldingFeature.State - int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN); + int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState.getIdentifier(), + COMMON_STATE_UNKNOWN); if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) { - posture = mDeviceStateToPostureMap.get(mCurrentBaseDeviceState, COMMON_STATE_UNKNOWN); + posture = mDeviceStateToPostureMap.get( + DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState, + mSupportedStates), COMMON_STATE_UNKNOWN); } return posture; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index d31bf2a662a3..a3d2d7f4dcdf 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -20,6 +20,7 @@ import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STA import android.app.Activity; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateRequest; import android.hardware.display.DisplayManager; @@ -40,6 +41,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -101,7 +103,9 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, mDisplayManager = context.getSystemService(DisplayManager.class); mExecutor = context.getMainExecutor(); - mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedStates(); + // TODO(b/329436166): Update the usage of device state manager API's + mCurrentSupportedDeviceStates = getSupportedStateIdentifiers( + mDeviceStateManager.getSupportedDeviceStates()); mFoldedDeviceStates = context.getResources().getIntArray( R.array.config_foldedDeviceStates); @@ -446,9 +450,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } @Override - public void onSupportedStatesChanged(int[] supportedStates) { + public void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) { synchronized (mLock) { - mCurrentSupportedDeviceStates = supportedStates; + // TODO(b/329436166): Update the usage of device state manager API's + mCurrentSupportedDeviceStates = getSupportedStateIdentifiers(supportedStates); updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); updateRearDisplayPresentationStatusListeners( getCurrentRearDisplayPresentationModeStatus()); @@ -456,9 +461,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } @Override - public void onStateChanged(int state) { + public void onDeviceStateChanged(@NonNull DeviceState state) { synchronized (mLock) { - mCurrentDeviceState = state; + // TODO(b/329436166): Update the usage of device state manager API's + mCurrentDeviceState = state.getIdentifier(); updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); updateRearDisplayPresentationStatusListeners( getCurrentRearDisplayPresentationModeStatus()); @@ -482,6 +488,15 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, return WindowAreaComponent.STATUS_AVAILABLE; } + // TODO(b/329436166): Remove and update the usage of device state manager API's + private int[] getSupportedStateIdentifiers(@NonNull List<DeviceState> states) { + int[] identifiers = new int[states.size()]; + for (int i = 0; i < states.size(); i++) { + identifiers[i] = states.get(i).getIdentifier(); + } + return identifiers; + } + /** * Helper method to determine if a rear display session is currently active by checking * if the current device state is that which corresponds to {@code mRearDisplayState}. diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 8989fc543044..f3d70f7c160b 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -18,27 +18,32 @@ package com.android.wm.shell.bubbles import android.content.Context import android.content.Intent +import android.content.pm.ShortcutInfo +import android.content.res.Resources import android.graphics.Color import android.graphics.drawable.Icon import android.os.UserHandle import android.view.IWindowManager import android.view.WindowManager import android.view.WindowManagerGlobal -import androidx.test.annotation.UiThreadTest import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.common.ProtoLog import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.R +import com.android.wm.shell.animation.PhysicsAnimatorTestUtils import com.android.wm.shell.bubbles.Bubbles.SysuiProxy +import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewTaskController import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor +import org.junit.After import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import java.util.function.Consumer @@ -64,6 +69,7 @@ class BubbleStackViewTest { @Before fun setUp() { + PhysicsAnimatorTestUtils.prepareForTest() // Disable protolog tool when running the tests from studio ProtoLog.REQUIRE_PROTOLOGTOOL = false windowManager = WindowManagerGlobal.getWindowManagerService()!! @@ -104,34 +110,158 @@ class BubbleStackViewTest { { sysuiProxy }, shellExecutor ) + + context + .getSharedPreferences(context.packageName, Context.MODE_PRIVATE) + .edit() + .putBoolean(StackEducationView.PREF_STACK_EDUCATION, true) + .apply() + } + + @After + fun tearDown() { + PhysicsAnimatorTestUtils.tearDown() } - @UiThreadTest @Test fun addBubble() { val bubble = createAndInflateBubble() - bubbleStackView.addBubble(bubble) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble) + } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() assertThat(bubbleStackView.bubbleCount).isEqualTo(1) } - @UiThreadTest @Test fun tapBubbleToExpand() { val bubble = createAndInflateBubble() - bubbleStackView.addBubble(bubble) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble) + } + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() assertThat(bubbleStackView.bubbleCount).isEqualTo(1) + var lastUpdate: BubbleData.Update? = null + val semaphore = Semaphore(0) + val listener = + BubbleData.Listener { update -> + lastUpdate = update + semaphore.release() + } + bubbleData.setListener(listener) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubble.iconView!!.performClick() + // we're checking the expanded state in BubbleData because that's the source of truth. + // This will eventually propagate an update back to the stack view, but setting the + // entire pipeline is outside the scope of a unit test. + assertThat(bubbleData.isExpanded).isTrue() + } - bubble.iconView!!.performClick() - // we're checking the expanded state in BubbleData because that's the source of truth. This - // will eventually propagate an update back to the stack view, but setting the entire - // pipeline is outside the scope of a unit test. - assertThat(bubbleData.isExpanded).isTrue() + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(lastUpdate).isNotNull() + assertThat(lastUpdate!!.expandedChanged).isTrue() + assertThat(lastUpdate!!.expanded).isTrue() + } + + @Test + fun tapDifferentBubble_shouldReorder() { + val bubble1 = createAndInflateChatBubble(key = "bubble1") + val bubble2 = createAndInflateChatBubble(key = "bubble2") + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubbleStackView.addBubble(bubble1) + bubbleStackView.addBubble(bubble2) + } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + + assertThat(bubbleStackView.bubbleCount).isEqualTo(2) + assertThat(bubbleData.bubbles).hasSize(2) + assertThat(bubbleData.selectedBubble).isEqualTo(bubble2) + assertThat(bubble2.iconView).isNotNull() + + var lastUpdate: BubbleData.Update? = null + val semaphore = Semaphore(0) + val listener = + BubbleData.Listener { update -> + lastUpdate = update + semaphore.release() + } + bubbleData.setListener(listener) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubble2.iconView!!.performClick() + assertThat(bubbleData.isExpanded).isTrue() + + bubbleStackView.setSelectedBubble(bubble2) + bubbleStackView.isExpanded = true + } + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(lastUpdate!!.expanded).isTrue() + assertThat(lastUpdate!!.bubbles.map { it.key }) + .containsExactly("bubble2", "bubble1") + .inOrder() + + // wait for idle to allow the animation to start + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + // wait for the expansion animation to complete before interacting with the bubbles + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( + AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y) + + // tap on bubble1 to select it + InstrumentationRegistry.getInstrumentation().runOnMainSync { + bubble1.iconView!!.performClick() + } + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(bubbleData.selectedBubble).isEqualTo(bubble1) + + // tap on bubble1 again to collapse the stack + InstrumentationRegistry.getInstrumentation().runOnMainSync { + // we have to set the selected bubble in the stack view manually because we don't have a + // listener wired up. + bubbleStackView.setSelectedBubble(bubble1) + bubble1.iconView!!.performClick() + } + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(bubbleData.selectedBubble).isEqualTo(bubble1) + assertThat(bubbleData.isExpanded).isFalse() + assertThat(lastUpdate!!.orderChanged).isTrue() + assertThat(lastUpdate!!.bubbles.map { it.key }) + .containsExactly("bubble1", "bubble2") + .inOrder() + } + + private fun createAndInflateChatBubble(key: String): Bubble { + val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button) + val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build() + val bubble = + Bubble( + key, + shortcutInfo, + /* desiredHeight= */ 6, + Resources.ID_NULL, + "title", + /* taskId= */ 0, + "locus", + /* isDismissable= */ true, + directExecutor() + ) {} + inflateBubble(bubble) + return bubble } private fun createAndInflateBubble(): Bubble { val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName) val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button) val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor()) + inflateBubble(bubble) + return bubble + } + + private fun inflateBubble(bubble: Bubble) { bubble.setInflateSynchronously(true) bubbleData.notificationEntryUpdated(bubble, true, false) @@ -152,7 +282,6 @@ class BubbleStackViewTest { assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() assertThat(bubble.isInflated).isTrue() - return bubble } private class FakeBubbleStackViewManager : BubbleStackViewManager { @@ -176,7 +305,7 @@ class BubbleStackViewTest { r.run() } - override fun removeCallbacks(r: Runnable) {} + override fun removeCallbacks(r: Runnable?) {} override fun hasCallback(r: Runnable): Boolean = false } diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 7dd39613b438..00fb298ea1cc 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -272,6 +272,10 @@ <dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen> <!-- Corner radius for expanded view while it is being dragged --> <dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen> + <!-- Width of the box around bottom center of the screen where drag only leads to dismiss --> + <dimen name="bubble_bar_dismiss_zone_width">192dp</dimen> + <!-- Height of the box around bottom center of the screen where drag only leads to dismiss --> + <dimen name="bubble_bar_dismiss_zone_height">242dp</dimen> <!-- Bottom and end margin for compat buttons. --> <dimen name="compat_button_margin">24dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt index ee8c41417458..b7f0890ec2bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt @@ -505,7 +505,6 @@ class PhysicsAnimator<T> private constructor (target: T) { // Check for a spring configuration. If one is present, we're either springing, or // flinging-then-springing. if (springConfig != null) { - // If there is no corresponding fling config, we're only springing. if (flingConfig == null) { // Apply the configuration and start the animation. @@ -679,7 +678,6 @@ class PhysicsAnimator<T> private constructor (target: T) { value: Float, velocity: Float ) { - // If this property animation isn't relevant to this listener, ignore it. if (!properties.contains(property)) { return @@ -702,7 +700,6 @@ class PhysicsAnimator<T> private constructor (target: T) { finalVelocity: Float, isFling: Boolean ): Boolean { - // If this property animation isn't relevant to this listener, ignore it. if (!properties.contains(property)) { return false @@ -971,17 +968,18 @@ class PhysicsAnimator<T> private constructor (target: T) { companion object { /** - * Constructor to use to for new physics animator instances in [getInstance]. This is - * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that - * all code using the physics animator is given testable instances instead. + * Callback to notify that a new animator was created. Used in [PhysicsAnimatorTestUtils] + * to be able to keep track of animators and wait for them to finish. */ - internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator + internal var onAnimatorCreated: (PhysicsAnimator<*>, Any) -> Unit = { _, _ -> } @JvmStatic @Suppress("UNCHECKED_CAST") fun <T : Any> getInstance(target: T): PhysicsAnimator<T> { if (!animators.containsKey(target)) { - animators[target] = instanceConstructor(target) + val animator = PhysicsAnimator(target) + onAnimatorCreated(animator, target) + animators[target] = animator } return animators[target] as PhysicsAnimator<T> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt index 86eb8da952f1..7defc26eef35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt @@ -62,12 +62,9 @@ object PhysicsAnimatorTestUtils { */ @JvmStatic fun prepareForTest() { - val defaultConstructor = PhysicsAnimator.instanceConstructor - PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> { - val animator = defaultConstructor(target) + PhysicsAnimator.onAnimatorCreated = { animator, target -> allAnimatedObjects.add(target) animatorTestHelpers[animator] = AnimatorTestHelper(animator) - return animator } timeoutMs = 2000 @@ -158,12 +155,12 @@ object PhysicsAnimatorTestUtils { @Throws(InterruptedException::class) @Suppress("UNCHECKED_CAST") fun <T : Any> blockUntilAnimationsEnd( - properties: FloatPropertyCompat<in T> + vararg properties: FloatPropertyCompat<in T> ) { for (target in allAnimatedObjects) { try { blockUntilAnimationsEnd( - PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties) + PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, *properties) } catch (e: ClassCastException) { // Keep checking the other objects for ones whose types match the provided // properties. @@ -267,10 +264,8 @@ object PhysicsAnimatorTestUtils { // Loop through the updates from the testable animator. for (update in framesForProperty) { - // Check whether this frame satisfies the current matcher. if (curMatcher(update)) { - // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining // frames and return without failing. if (matchers.size == 0) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index 056598b86d58..b5b8a81c8886 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -17,8 +17,10 @@ package com.android.wm.shell.bubbles.bar import android.annotation.SuppressLint +import android.graphics.RectF import android.view.MotionEvent import android.view.View +import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.common.bubbles.BubbleBarLocation import com.android.wm.shell.common.bubbles.DismissView @@ -43,6 +45,8 @@ class BubbleBarExpandedViewDragController( private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> = MagnetizedObject.magnetizeView(expandedView) private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget + private val dismissZoneHeight: Int + private val dismissZoneWidth: Int init { magnetizedExpandedView.magnetListener = MagnetListener() @@ -74,6 +78,11 @@ class BubbleBarExpandedViewDragController( } return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed } + + dismissZoneHeight = + dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_height) + dismissZoneWidth = + dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_width) } /** Listener to receive callback about dragging events */ @@ -97,12 +106,23 @@ class BubbleBarExpandedViewDragController( private var isMoving = false private var screenCenterX: Int = -1 private var isOnLeft = false + private val dismissZone = RectF() override fun onDown(v: View, ev: MotionEvent): Boolean { // While animating, don't allow new touch events if (expandedView.isAnimating) return false - screenCenterX = bubblePositioner.screenRect.centerX() isOnLeft = bubblePositioner.isBubbleBarOnLeft + + val screenRect = bubblePositioner.screenRect + screenCenterX = screenRect.centerX() + val screenBottom = screenRect.bottom + + dismissZone.set( + screenCenterX - dismissZoneWidth / 2f, + (screenBottom - dismissZoneHeight).toFloat(), + screenCenterX + dismissZoneHeight / 2f, + screenBottom.toFloat() + ) return true } @@ -122,6 +142,11 @@ class BubbleBarExpandedViewDragController( expandedView.translationY = expandedViewInitialTranslationY + dy dismissView.show() + // Check if we are in the zone around dismiss view where drag can only lead to dismiss + if (dismissZone.contains(ev.rawX, ev.rawY)) { + return + } + if (isOnLeft && ev.rawX > screenCenterX) { isOnLeft = false dragListener.onLocationChanged(BubbleBarLocation.RIGHT) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java index 8b4ac1a8dc79..d17e8620ff12 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java @@ -107,7 +107,7 @@ public class DevicePostureController { DeviceStateManager.class); if (deviceStateManager != null) { deviceStateManager.registerCallback(mMainExecutor, state -> onDevicePostureChanged( - mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN))); + mDeviceStateToPostureMap.get(state.getIdentifier(), DEVICE_POSTURE_UNKNOWN))); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt new file mode 100644 index 000000000000..6781d08c9904 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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. + */ + +@file:JvmName("AppCompatUtils") + +package com.android.wm.shell.compatui + +import android.app.TaskInfo +fun isSingleTopActivityTranslucent(task: TaskInfo) = + task.isTopActivityTransparent && task.numActivities == 1 + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 4a3130f3c54b..992e5aecdce8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.PendingIntent +import android.app.TaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -46,6 +47,7 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.internal.policy.ScreenDecorationsUtils +import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController @@ -63,6 +65,7 @@ import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT +import com.android.wm.shell.compatui.isSingleTopActivityTranslucent import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.draganddrop.DragAndDropController @@ -81,6 +84,8 @@ import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener +import com.android.wm.shell.windowdecor.extension.isFreeform +import com.android.wm.shell.windowdecor.extension.isFullscreen import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -164,8 +169,11 @@ class DesktopTasksController( private fun onInit() { KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") shellCommandHandler.addDumpCallback(this::dump, this) - shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, - this) + shellCommandHandler.addCommandCallback( + "desktopmode", + desktopModeShellCommandHandler, + this + ) shellController.addExternalInterface( ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, { createExternalInterface() }, @@ -269,9 +277,11 @@ class DesktopTasksController( // Split-screen case where there are two focused tasks, then we find the child // task to move to desktop. val splitFocusedTask = - if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) + if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) { allFocusedTasks[1] - else allFocusedTasks[0] + } else { + allFocusedTasks[0] + } moveToDesktop(splitFocusedTask) } 1 -> { @@ -310,8 +320,18 @@ class DesktopTasksController( ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { KtProtoLog.w( - WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " + - "display does not meet minimum size requirements") + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: Cannot enter desktop, " + + "display does not meet minimum size requirements" + ) + return + } + if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) { + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: Cannot enter desktop, " + + "translucent top activity found. This is likely a modal dialog." + ) return } KtProtoLog.v( @@ -446,7 +466,11 @@ class DesktopTasksController( if (Transitions.ENABLE_SHELL_TRANSITIONS) { exitDesktopTaskTransitionHandler.startTransition( - Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, position, mOnAnimationFinishedCallback) + Transitions.TRANSIT_EXIT_DESKTOP_MODE, + wct, + position, + mOnAnimationFinishedCallback + ) } else { shellTaskOrganizer.applyTransaction(wct) releaseVisualIndicator() @@ -492,8 +516,12 @@ class DesktopTasksController( KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId) return } - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d", - taskId, task.displayId) + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "moveToNextDisplay: taskId=%d taskDisplayId=%d", + taskId, + task.displayId + ) val displayIds = rootTaskDisplayAreaOrganizer.displayIds.sorted() // Get the first display id that is higher than current task display id @@ -515,8 +543,12 @@ class DesktopTasksController( * No-op if task is already on that display per [RunningTaskInfo.displayId]. */ private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d", - task.taskId, displayId) + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "moveToDisplay: taskId=%d displayId=%d", + task.taskId, + displayId + ) if (task.displayId == displayId) { KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display") @@ -590,7 +622,8 @@ class DesktopTasksController( // Center the task in screen bounds outBounds.offset( screenBounds.centerX() - outBounds.centerX(), - screenBounds.centerY() - outBounds.centerY()) + screenBounds.centerY() - outBounds.centerY() + ) } private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect { @@ -737,11 +770,13 @@ class DesktopTasksController( val result = triggerTask?.let { task -> when { // If display has tasks stashed, handle as stashed launch - desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task) + task.isStashed -> handleStashedTaskLaunch(task) + // Check if the task has a top transparent activity + shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task) // Check if fullscreen task should be updated - task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task) + task.isFullscreen -> handleFullscreenTaskLaunch(task) // Check if freeform task should be updated - task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task) + task.isFreeform -> handleFreeformTaskLaunch(task) else -> { null } @@ -774,6 +809,13 @@ class DesktopTasksController( .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } } + private val TaskInfo.isStashed: Boolean + get() = desktopModeTaskRepository.isStashed(displayId) + + private fun shouldLaunchAsModal(task: TaskInfo): Boolean { + return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) + } + private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch") val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) @@ -821,6 +863,16 @@ class DesktopTasksController( return wct } + // Always launch transparent tasks in fullscreen. + private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + // Already fullscreen, no-op. + if (task.isFullscreen) + return null + return WindowContainerTransaction().also { wct -> + addMoveToFullscreenChanges(wct, task) + } + } + private fun addMoveToDesktopChanges( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo @@ -901,9 +953,12 @@ class DesktopTasksController( ) { val wct = WindowContainerTransaction() addMoveToSplitChanges(wct, taskInfo) - splitScreenController.requestEnterSplitSelect(taskInfo, wct, + splitScreenController.requestEnterSplitSelect( + taskInfo, + wct, if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT, - taskInfo.configuration.windowConfiguration.bounds) + taskInfo.configuration.windowConfiguration.bounds + ) } } @@ -1111,7 +1166,8 @@ class DesktopTasksController( pendingIntentLaunchFlags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK setPendingIntentBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) isPendingIntentBackgroundActivityLaunchAllowedByPermission = true } val wct = WindowContainerTransaction() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index ad4049320d93..2b433e9c4227 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -85,6 +85,9 @@ public interface SplitScreen { /** Called when requested to go to fullscreen from the current active split app. */ void goToFullscreenFromSplit(); + /** Called when splitscreen focused app is changed. */ + void setSplitscreenFocus(boolean leftOrTop); + /** Get a string representation of a stage type */ static String stageTypeToString(@StageType int stage) { switch (stage) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 86c8f04f8138..3e34c303e161 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -485,6 +485,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } + public void setSplitscreenFocus(boolean leftOrTop) { + if (mStageCoordinator.isSplitActive()) { + mStageCoordinator.grantFocusToPosition(leftOrTop); + } + } + /** Move the specified task to fullscreen, regardless of focus state. */ public void moveTaskToFullscreen(int taskId, int exitReason) { mStageCoordinator.moveTaskToFullscreen(taskId, exitReason); @@ -1146,6 +1152,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public void goToFullscreenFromSplit() { mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit); } + + @Override + public void setSplitscreenFocus(boolean leftOrTop) { + mMainExecutor.execute( + () -> SplitScreenController.this.setSplitscreenFocus(leftOrTop)); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 36368df9af36..41890df9a4ee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1592,6 +1592,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + protected void grantFocusToPosition(boolean leftOrTop) { + grantFocusToStage(mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT + ? getMainStagePosition() : getSideStagePosition()); + } + private void clearRequestIfPresented() { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented"); if (mSideStageListener.mVisible && mSideStageListener.mHasChildren diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index 4ea71490798c..5b402a5a7d53 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -212,6 +212,7 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { switch (mType) { case TYPE_RECENTS_DURING_DESKTOP: case TYPE_RECENTS_DURING_SPLIT: + case TYPE_RECENTS_DURING_KEYGUARD: mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); break; default: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 918cefbee9f4..6d2109ca037e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -30,6 +30,7 @@ import static android.view.WindowInsets.Type.statusBars; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR; @@ -75,6 +76,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; @@ -1055,6 +1057,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && taskInfo.isFocused) { return false; } + if (Flags.enableDesktopWindowingModalsPolicy() + && isSingleTopActivityTranslucent(taskInfo)) { + return false; + } return DesktopModeStatus.isEnabled() && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt index 7a64a47a46cc..a2293d53618a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt @@ -17,6 +17,8 @@ package com.android.wm.shell.windowdecor.extension import android.app.TaskInfo +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND @@ -31,3 +33,9 @@ val TaskInfo.isLightCaptionBarAppearance: Boolean val appearance = taskDescription?.systemBarsAppearance ?: 0 return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0 } + +val TaskInfo.isFullscreen: Boolean + get() = windowingMode == WINDOWING_MODE_FULLSCREEN + +val TaskInfo.isFreeform: Boolean + get() = windowingMode == WINDOWING_MODE_FREEFORM diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt new file mode 100644 index 000000000000..4cd2a366f5eb --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests for {@link AppCompatUtils}. + * + * Build/Install/Run: + * atest WMShellUnitTests:AppCompatUtilsTest + */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +class AppCompatUtilsTest : ShellTestCase() { + + @Test + fun testIsSingleTopActivityTranslucent() { + assertTrue(isSingleTopActivityTranslucent( + createFreeformTask(/* displayId */ 0) + .apply { + isTopActivityTransparent = true + numActivities = 1 + })) + assertFalse(isSingleTopActivityTranslucent( + createFreeformTask(/* displayId */ 0) + .apply { + isTopActivityTransparent = true + numActivities = 0 + })) + assertFalse(isSingleTopActivityTranslucent( + createFreeformTask(/* displayId */ 0) + .apply { + isTopActivityTransparent = false + numActivities = 1 + })) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 5df9dd38a75d..254bf7da08a6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -23,9 +23,14 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED +import android.graphics.Point +import android.graphics.PointF +import android.graphics.Rect import android.os.Binder +import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY +import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_OPEN @@ -40,14 +45,15 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.window.flags.Flags import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.transition.TestRemoteTransition import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.LaunchAdjacentController import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.ShellExecutor @@ -65,16 +71,17 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.OneShotRemoteHandler +import com.android.wm.shell.transition.TestRemoteTransition import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE import com.android.wm.shell.transition.Transitions.TransitionHandler -import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -86,15 +93,25 @@ import org.mockito.Mockito import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.kotlin.times import org.mockito.Mockito.`when` as whenever import org.mockito.quality.Strictness +/** + * Test class for {@link DesktopTasksController} + * + * Usage: atest WMShellUnitTests:DesktopTasksControllerTest + */ @SmallTest @RunWith(AndroidTestingRunner::class) class DesktopTasksControllerTest : ShellTestCase() { + @JvmField + @Rule + val setFlagsRule = SetFlagsRule() + @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var shellCommandHandler: ShellCommandHandler @Mock lateinit var shellController: ShellController @@ -109,7 +126,6 @@ class DesktopTasksControllerTest : ShellTestCase() { ToggleResizeDesktopTaskTransitionHandler @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler @Mock lateinit var launchAdjacentController: LaunchAdjacentController - @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration @Mock lateinit var splitScreenController: SplitScreenController @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler @Mock lateinit var dragAndDropController: DragAndDropController @@ -123,6 +139,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener private val shellExecutor = TestShellExecutor() + // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf<RunningTaskInfo>() @@ -350,6 +367,18 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_topActivityTranslucent_doesNothing() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + val task = setUpFullscreenTask().apply { + isTopActivityTransparent = true + numActivities = 1 + } + + controller.moveToDesktop(task) + verifyWCTNotExecuted() + } + + @Test fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() { val task = setUpFullscreenTask() @@ -424,7 +453,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) - verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(), + verify(splitScreenController).prepareExitSplitScreen( + any(), + anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE) ) } @@ -436,7 +467,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) - verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(), + verify(splitScreenController, never()).prepareExitSplitScreen( + any(), + anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE) ) } @@ -744,6 +777,19 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_shouldLaunchAsModal_returnSwitchToFullscreenWCT() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + val task = setUpFreeformTask().apply { + isTopActivityTransparent = true + numActivities = 1 + } + + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + + @Test fun stashDesktopApps_stateUpdates() { whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) @@ -822,7 +868,9 @@ class DesktopTasksControllerTest : ShellTestCase() { val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task4.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) - verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(), + verify(splitScreenController).prepareExitSplitScreen( + any(), + anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE) ) } @@ -844,6 +892,31 @@ class DesktopTasksControllerTest : ShellTestCase() { .isEqualTo(WINDOWING_MODE_FULLSCREEN) } + @Test + fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() { + val task = setUpFreeformTask() + val mockSurface = mock(SurfaceControl::class.java) + val mockDisplayLayout = mock(DisplayLayout::class.java) + whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout) + whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000)) + controller.onDragPositioningMove(task, mockSurface, 200f, + Rect(100, -100, 500, 1000)) + + controller.onDragPositioningEnd(task, + Point(100, -100), /* position */ + PointF(200f, -200f), /* inputCoordinate */ + Rect(100, -100, 500, 1000), /* taskBounds */ + Rect(0, 50, 2000, 2000) /* validDragArea */ + ) + val rectAfterEnd = Rect(100, 50, 500, 1150) + verify(transitions).startTransition( + eq(TRANSIT_CHANGE), Mockito.argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + change.configuration.windowConfiguration.bounds == rectAfterEnd + } + }, eq(null)) + } + fun enterSplit_freeformTaskIsMovedToSplit() { val task1 = setUpFreeformTask() val task2 = setUpFreeformTask() @@ -855,9 +928,12 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.enterSplit(DEFAULT_DISPLAY, false) - verify(splitScreenController).requestEnterSplitSelect(task2, any(), + verify(splitScreenController).requestEnterSplitSelect( + task2, + any(), SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT, - task2.configuration.windowConfiguration.bounds) + task2.configuration.windowConfiguration.bounds + ) } private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 6940739d68b2..8e9619dc1430 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -44,8 +44,8 @@ import android.view.SurfaceView import android.view.WindowInsets.Type.navigationBars import android.view.WindowInsets.Type.statusBars import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -65,11 +65,14 @@ import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener +import java.util.Optional +import java.util.function.Supplier import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.anyInt import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -78,15 +81,14 @@ import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq +import org.mockito.kotlin.spy import org.mockito.kotlin.whenever import org.mockito.quality.Strictness -import java.util.Optional -import java.util.function.Supplier -import org.mockito.Mockito -import org.mockito.kotlin.spy - -/** Tests of [DesktopModeWindowDecorViewModel] */ +/** + * Tests of [DesktopModeWindowDecorViewModel] + * Usage: atest WMShellUnitTests:DesktopModeWindowDecorViewModelTests + */ @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper @@ -291,6 +293,19 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + fun testDescorationIsNotCreatedForTopTranslucentActivities() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply { + isTopActivityTransparent = true + numActivities = 1 + } + onTaskOpening(task) + + verify(mockDesktopModeWindowDecorFactory, never()) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + } + + @Test fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() { val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) val decoration = setUpMockDecorationForTask(task) @@ -470,7 +485,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration { val decoration = mock(DesktopModeWindowDecoration::class.java) - whenever(mockDesktopModeWindowDecorFactory.create( + whenever( + mockDesktopModeWindowDecorFactory.create( any(), any(), any(), eq(task), any(), any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task @@ -489,7 +505,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400, - /*densityDpi=*/320, + /*densityDpi=*/ 320, surfaceView.holder.surface, DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY ) diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 4486f55aabf6..33830f154835 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -563,6 +563,7 @@ cc_defaults { "FrameInfoVisualizer.cpp", "FrameMetricsReporter.cpp", "Gainmap.cpp", + "HWUIProperties.sysprop", "Interpolator.cpp", "JankTracker.cpp", "LightingInfo.cpp", @@ -628,7 +629,6 @@ cc_defaults { "AutoBackendTextureRelease.cpp", "DeferredLayerUpdater.cpp", "HardwareBitmapUploader.cpp", - "HWUIProperties.sysprop", "Layer.cpp", "LayerUpdateQueue.cpp", "ProfileDataContainer.cpp", diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 755332ff66fd..325bdd63ab22 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -16,10 +16,6 @@ #include "Properties.h" -#include "Debug.h" -#ifdef __ANDROID__ -#include "HWUIProperties.sysprop.h" -#endif #include <android-base/properties.h> #include <cutils/compiler.h> #include <log/log.h> @@ -28,6 +24,8 @@ #include <cstdlib> #include <optional> +#include "Debug.h" +#include "HWUIProperties.sysprop.h" #include "src/core/SkTraceEventCommon.h" #ifdef __ANDROID__ @@ -47,16 +45,6 @@ constexpr bool hdr_10bit_plus() { namespace android { namespace uirenderer { -#ifndef __ANDROID__ // Layoutlib does not compile HWUIProperties.sysprop as it depends on cutils properties -std::optional<bool> use_vulkan() { - return base::GetBoolProperty("ro.hwui.use_vulkan", true); -} - -std::optional<std::int32_t> render_ahead() { - return base::GetIntProperty("ro.hwui.render_ahead", 0); -} -#endif - bool Properties::debugLayersUpdates = false; bool Properties::debugOverdraw = false; bool Properties::debugTraceGpuResourceCategories = false; diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 6ccb212fe6ca..40dfc9d4309b 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -16,6 +16,7 @@ #pragma once +#include <FileBlobCache.h> #include <GrContextOptions.h> #include <SkRefCnt.h> #include <cutils/compiler.h> @@ -32,7 +33,6 @@ class SkData; namespace android { class BlobCache; -class FileBlobCache; namespace uirenderer { namespace skiapipeline { diff --git a/media/OWNERS b/media/OWNERS index 994a7b810009..2e9276d73392 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -3,14 +3,9 @@ atneya@google.com elaurent@google.com essick@google.com etalvala@google.com -hdmoon@google.com hunga@google.com -insun@google.com -jaewan@google.com -jinpark@google.com jmtrivi@google.com jsharkey@android.com -klhyun@google.com lajos@google.com nchalko@google.com philburk@google.com @@ -20,8 +15,6 @@ wonsik@google.com # go/android-fwk-media-solutions for info on areas of ownership. include platform/frameworks/av:/media/janitors/media_solutions_OWNERS -# SEO - # SEA/KIR/BVE jtinker@google.com robertshih@google.com diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 4be282b5de87..42175628a45d 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -1690,6 +1690,18 @@ public final class AudioAttributes implements Parcelable { } /** + * Query if the usage is a hidden (neither sdk nor SystemApi) usage + * + * @param usage the {@link android.media.AudioAttributes usage} + * @return {@code true} if the usage is {@link AudioAttributes#USAGE_VIRTUAL_SOURCE} or + * {@code false} otherwise + * @hide + */ + public static boolean isHiddenUsage(@AttributeUsage int usage) { + return usage == USAGE_VIRTUAL_SOURCE; + } + + /** * Query if the content type is a valid sdk content type * @param contentType one of {@link AttributeContentType} * @return {@code true} if the content type is valid for sdk or {@code false} otherwise diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java index e6ec2c330340..eaafa595ae4f 100644 --- a/media/java/android/media/FadeManagerConfiguration.java +++ b/media/java/android/media/FadeManagerConfiguration.java @@ -694,7 +694,8 @@ public final class FadeManagerConfiguration implements Parcelable { } private static boolean isUsageValid(int usage) { - return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage); + return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage) + || AudioAttributes.isHiddenUsage(usage); } private void ensureFadingIsEnabled() { diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index f5dc6ead374a..1905fa8ce612 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -5101,7 +5101,7 @@ final public class MediaCodec { * size is larger than 16x16, then the qpOffset information of all 16x16 blocks that * encompass the coding unit is combined and used. The QP of target block will be calculated * as 'frameQP + offsetQP'. If the result exceeds minQP or maxQP configured then the value - * may be clamped. Negative offset results in blocks encoded at lower QP than frame QP and + * will be clamped. Negative offset results in blocks encoded at lower QP than frame QP and * positive offsets will result in encoding blocks at higher QP than frame QP. If the areas * of negative QP and positive QP are chosen wisely, the overall viewing experience can be * improved. @@ -5128,7 +5128,7 @@ final public class MediaCodec { * quantization parameter (QP) offset of the blocks in the bounding box. The bounding box * will get stretched outwards to align to LCU boundaries during encoding. The Qp Offset is * integral and shall be in the range [-128, 127]. The QP of target block will be calculated - * as frameQP + offsetQP. If the result exceeds minQP or maxQP configured then the value may + * as frameQP + offsetQP. If the result exceeds minQP or maxQP configured then the value will * be clamped. Negative offset results in blocks encoded at lower QP than frame QP and * positive offsets will result in blocks encoded at higher QP than frame QP. If the areas of * negative QP and positive QP are chosen wisely, the overall viewing experience can be diff --git a/media/java/android/media/browse/OWNERS b/media/java/android/media/browse/OWNERS deleted file mode 100644 index 916fc36ffbc6..000000000000 --- a/media/java/android/media/browse/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -# Bug component: 137631 - -hdmoon@google.com -insun@google.com -jaewan@google.com -jinpark@google.com -klhyun@google.com -gyumin@google.com diff --git a/media/java/android/media/session/OWNERS b/media/java/android/media/session/OWNERS deleted file mode 100644 index 916fc36ffbc6..000000000000 --- a/media/java/android/media/session/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -# Bug component: 137631 - -hdmoon@google.com -insun@google.com -jaewan@google.com -jinpark@google.com -klhyun@google.com -gyumin@google.com diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java index 236b1fdb1989..c48a95625e8f 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java @@ -45,10 +45,8 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) public final class FadeManagerConfigurationUnitTest { - private static final long DEFAULT_FADE_OUT_DURATION_MS = - FadeManagerConfiguration.getDefaultFadeOutDurationMillis(); - private static final long DEFAULT_FADE_IN_DURATION_MS = - FadeManagerConfiguration.getDefaultFadeInDurationMillis(); + private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000; + private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000; private static final long TEST_FADE_OUT_DURATION_MS = 1_500; private static final long TEST_FADE_IN_DURATION_MS = 750; private static final int TEST_INVALID_USAGE = -10; @@ -251,6 +249,20 @@ public final class FadeManagerConfigurationUnitTest { } @Test + public void testGetDefaultFadeOutDuration() { + expect.withMessage("Default fade out duration") + .that(FadeManagerConfiguration.getDefaultFadeOutDurationMillis()) + .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS); + } + + @Test + public void testGetDefaultFadeInDuration() { + expect.withMessage("Default fade in duration") + .that(FadeManagerConfiguration.getDefaultFadeInDurationMillis()) + .isEqualTo(DEFAULT_FADE_IN_DURATION_MS); + } + + @Test public void testSetFadeState_toDisable() { final int fadeState = FadeManagerConfiguration.FADE_STATE_DISABLED; FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder() @@ -746,6 +758,87 @@ public final class FadeManagerConfigurationUnitTest { .isEqualTo(FadeManagerConfiguration.CREATOR.createFromParcel(parcel)); } + @Test + public void testGetFadeOutVolumeShaperConfigForUsage_withInvalidUsage_fails() { + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> + mFmc.getFadeOutVolumeShaperConfigForUsage(TEST_INVALID_USAGE) + ); + + expect.withMessage("Fade out volume shaper config for invalid usage exception") + .that(thrown).hasMessageThat().contains("Invalid usage"); + } + + @Test + public void testGetFadeInVolumeShaperConfigForUsage_withInvalidUsage_fails() { + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> + mFmc.getFadeInVolumeShaperConfigForUsage(TEST_INVALID_USAGE) + ); + + expect.withMessage("Fade in volume shaper config for invalid usage exception") + .that(thrown).hasMessageThat().contains("Invalid usage"); + } + + @Test + public void testGetFadeVolumeShaperConfigForUsage_forSdkUsages() { + FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder(); + for (int usage : AudioAttributes.getSdkUsages()) { + builder.addFadeableUsage(usage); + builder.setFadeOutVolumeShaperConfigForUsage(usage, TEST_FADE_OUT_VOLUME_SHAPER_CONFIG); + builder.setFadeInVolumeShaperConfigForUsage(usage, TEST_FADE_IN_VOLUME_SHAPER_CONFIG); + } + FadeManagerConfiguration fmc = builder.build(); + + for (int usage : AudioAttributes.getSdkUsages()) { + expect.withMessage("Fade out volume shaper config for sdk usage") + .that(fmc.getFadeOutVolumeShaperConfigForUsage(usage)) + .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG); + expect.withMessage("Fade in volume shaper config for sdk usage") + .that(fmc.getFadeInVolumeShaperConfigForUsage(usage)) + .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG); + } + } + + @Test + public void testGetFadeVolumeShaperConfigForUsage_forSystemUsages() { + int[] systemUsages = {AudioAttributes.USAGE_CALL_ASSISTANT, AudioAttributes.USAGE_EMERGENCY, + AudioAttributes.USAGE_SAFETY, AudioAttributes.USAGE_VEHICLE_STATUS, + AudioAttributes.USAGE_ANNOUNCEMENT}; + FadeManagerConfiguration.Builder builder = new FadeManagerConfiguration.Builder(); + for (int usage : systemUsages) { + builder.addFadeableUsage(usage); + builder.setFadeOutVolumeShaperConfigForUsage(usage, TEST_FADE_OUT_VOLUME_SHAPER_CONFIG); + builder.setFadeInVolumeShaperConfigForUsage(usage, TEST_FADE_IN_VOLUME_SHAPER_CONFIG); + } + FadeManagerConfiguration fmc = builder.build(); + + for (int usage : systemUsages) { + expect.withMessage("Fade out volume shaper config for system usage") + .that(fmc.getFadeOutVolumeShaperConfigForUsage(usage)) + .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG); + expect.withMessage("Fade in volume shaper config for system usage") + .that(fmc.getFadeInVolumeShaperConfigForUsage(usage)) + .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG); + } + } + + @Test + public void testGetFadeVolumeShaperConfigForUsage_forHiddenUsage() { + int hiddenUsage = AudioAttributes.USAGE_VIRTUAL_SOURCE; + FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder() + .addFadeableUsage(hiddenUsage) + .setFadeOutVolumeShaperConfigForUsage(hiddenUsage, + TEST_FADE_OUT_VOLUME_SHAPER_CONFIG) + .setFadeInVolumeShaperConfigForUsage(hiddenUsage, + TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build(); + + expect.withMessage("Fade out volume shaper config for hidden usage") + .that(fmc.getFadeOutVolumeShaperConfigForUsage(hiddenUsage)) + .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG); + expect.withMessage("Fade in volume shaper config for hidden usage") + .that(fmc.getFadeInVolumeShaperConfigForUsage(hiddenUsage)) + .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG); + } + private static AudioAttributes createAudioAttributesForUsage(int usage) { if (AudioAttributes.isSystemUsage(usage)) { return new AudioAttributes.Builder().setSystemUsage(usage).build(); diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java index 7028c8f43eed..af63a6e4350b 100644 --- a/nfc/java/android/nfc/cardemulation/PollingFrame.java +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java @@ -174,6 +174,16 @@ public final class PollingFrame implements Parcelable{ && frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT); } + /** + * Constructor for Polling Frames. + * + * @param type the type of the frame + * @param data a byte array of the data contained in the frame + * @param gain the vendor-specific gain of the field + * @param timestamp the timestamp in millisecones + * @param triggeredAutoTransact whether or not this frame triggered the device to start a + * transaction automatically + */ public PollingFrame(@PollingFrameType int type, @Nullable byte[] data, int gain, int timestamp, boolean triggeredAutoTransact) { mType = type; diff --git a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml index 685e2595119a..0ae9c2674bc7 100644 --- a/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml +++ b/packages/SettingsLib/IllustrationPreference/res/layout/illustration_preference.xml @@ -19,7 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="match_parent" - android:importantForAccessibility="noHideDescendants" + android:importantForAccessibility="no" android:gravity="center" android:orientation="horizontal"> diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index f4d4dbadb37f..815a101957ad 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -24,6 +24,7 @@ import android.graphics.drawable.Animatable2; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -33,16 +34,18 @@ import android.widget.FrameLayout; import android.widget.ImageView; import androidx.annotation.RawRes; +import androidx.annotation.StringRes; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import androidx.vectordrawable.graphics.drawable.Animatable2Compat; +import com.android.settingslib.widget.preference.illustration.R; + import com.airbnb.lottie.LottieAnimationView; import com.airbnb.lottie.LottieDrawable; import java.io.FileNotFoundException; import java.io.InputStream; -import com.android.settingslib.widget.preference.illustration.R; /** * IllustrationPreference is a preference that can play lottie format animation @@ -62,8 +65,8 @@ public class IllustrationPreference extends Preference { private Drawable mImageDrawable; private View mMiddleGroundView; private OnBindListener mOnBindListener; - private boolean mLottieDynamicColor; + private CharSequence mContentDescription; /** * Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs. @@ -123,7 +126,10 @@ public class IllustrationPreference extends Preference { (FrameLayout) holder.findViewById(R.id.middleground_layout); final LottieAnimationView illustrationView = (LottieAnimationView) holder.findViewById(R.id.lottie_view); - + if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) { + illustrationView.setContentDescription(mContentDescription); + illustrationView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + } // To solve the problem of non-compliant illustrations, we set the frame height // to 300dp and set the length of the short side of the screen to // the width of the frame. @@ -208,6 +214,29 @@ public class IllustrationPreference extends Preference { } /** + * To set content description of the {@link Illustration Preference}. This can use for talkback + * environment if developer wants to have a customization content. + * + * @param contentDescription The CharSequence of the content description. + */ + public void setContentDescription(CharSequence contentDescription) { + if (!TextUtils.equals(mContentDescription, contentDescription)) { + mContentDescription = contentDescription; + notifyChanged(); + } + } + + /** + * To set content description of the {@link Illustration Preference}. This can use for talkback + * environment if developer wants to have a customization content. + * + * @param contentDescriptionResId The resource id of the content description. + */ + public void setContentDescription(@StringRes int contentDescriptionResId) { + setContentDescription(getContext().getText(contentDescriptionResId)); + } + + /** * Gets the lottie illustration resource id. */ public int getLottieAnimationResId() { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt index 79c5ebbce5fa..5dd7cafb962a 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt @@ -112,7 +112,8 @@ object CardPageProvider : SettingsPageProvider { isVisible = { isVisible0 }, onDismiss = { isVisible0 = false }, buttons = listOf( - CardButton(text = "Action") {}, + CardButton(text = "Override") {}, + CardButton(text = "Learn more") {}, ), ), CardModel( diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png Binary files differindex 63efaf557c3c..3e016f791962 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png Binary files differindex ae11f810c0bf..b8bb25fc9382 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png Binary files differindex a3692cdc92f9..63983eeacb8f 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png Binary files differindex 233d088cf890..8fcc350804a3 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png Binary files differindex fe877eeff117..c2f6165daefc 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png Binary files differindex 3eaecc14935f..f32d7421b028 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png Binary files differindex ca619117cbf4..6659d7ccca2f 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png Binary files differindex 8333e68bfc13..36cbadc5bdaf 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png Binary files differindex 19d0afd6618a..7b6e702616de 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png Binary files differindex fcfd1d8763c1..cd44fb8a9c0c 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png Binary files differindex 693c59243257..19c028e395b7 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png Binary files differindex 05213688b0a8..8a2b80031615 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png Binary files differindex 5bb318fa0a91..a279481d2a5c 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png Binary files differindex d0d014e5f8ed..9aee004cca79 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png Binary files differindex 5bd1144b4e88..cc74aac45961 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt index 16f6b5e773c9..f30a957a5fe0 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt @@ -50,19 +50,15 @@ class SettingsScreenshotTestRule( ) ) private val composeRule = createAndroidComposeRule<ComponentActivity>() - private val roboRule = - RuleChain.outerRule(deviceEmulationRule) - .around(screenshotRule) - .around(composeRule) private val delegateRule = RuleChain.outerRule(colorsRule) - .around(roboRule) + .around(deviceEmulationRule) + .around(screenshotRule) + .around(composeRule) private val matcher = UnitTestBitmapMatcher - private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false override fun apply(base: Statement, description: Description): Statement { - val ruleToApply = if (isRobolectric) roboRule else delegateRule - return ruleToApply.apply(base, description) + return delegateRule.apply(base, description) } /** diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt index 621825a82c1e..f5cbe8ffcee3 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt @@ -21,6 +21,8 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -165,10 +167,11 @@ private fun DismissButton(onDismiss: (() -> Unit)?) { } } +@OptIn(ExperimentalLayoutApi::class) @Composable private fun Buttons(buttons: List<CardButton>, color: Color) { if (buttons.isNotEmpty()) { - Row( + FlowRow( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy( space = SettingsDimension.itemPaddingEnd, diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index a4bc235aaa7a..8a5dfefa18c3 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1485,7 +1485,7 @@ <string name="user_nickname">Nickname</string> <!-- Confirmation message on dialog for editing user name and profile picture. Inform user on who will be able to see the changes [CHAR LIMIT=NONE]--> - <string name="edit_user_info_message">Your name and picture will be visible to anyone that uses this device.</string> + <string name="edit_user_info_message">The name and picture you choose will be visible to anyone who uses this device.</string> <!-- Label for adding a new user in the user switcher [CHAR LIMIT=35] --> <string name="user_add_user">Add user</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index c2c82b35317b..b8624fd9605b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1029,15 +1029,17 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> @Override public String toString() { - return "CachedBluetoothDevice{" - + "anonymizedAddress=" - + mDevice.getAnonymizedAddress() - + ", name=" - + getName() - + ", groupId=" - + mGroupId - + ", member=" + mMemberDevices - + "}"; + StringBuilder builder = new StringBuilder("CachedBluetoothDevice{"); + builder.append("anonymizedAddress=").append(mDevice.getAnonymizedAddress()); + builder.append(", name=").append(getName()); + builder.append(", groupId=").append(mGroupId); + builder.append(", member=").append(mMemberDevices); + if (isHearingAidDevice()) { + builder.append(", hearingAidInfo=").append(mHearingAidInfo); + builder.append(", subDevice=").append(mSubDevice); + } + builder.append("}"); + return builder.toString(); } @Override diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 3266c12f3ad2..b151a538670d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -60,10 +60,12 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; @@ -161,6 +163,11 @@ final class SettingsState { private static final String APEX_DIR = "/apex"; private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb"; + private static final String STORAGE_MIGRATION_FLAG = + "core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1"; + private static final String STORAGE_MIGRATION_LOG = + "/metadata/aconfig/flags/storage_migration.log"; + /** * This tag is applied to all aconfig default value-loaded flags. */ @@ -1439,6 +1446,20 @@ final class SettingsState { } } + if (name != null && name.equals(STORAGE_MIGRATION_FLAG) && value.equals("true")) { + File file = new File(STORAGE_MIGRATION_LOG); + if (!file.exists()) { + try (BufferedWriter writer = + new BufferedWriter(new FileWriter(STORAGE_MIGRATION_LOG))) { + final long timestamp = System.currentTimeMillis(); + String entry = String.format("%d | Log init", timestamp); + writer.write(entry); + } catch (IOException e) { + Slog.e(LOG_TAG, "failed to write storage migration file", e); + } + } + } + mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag, fromSystem, id, isPreservedInRestore)); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index 2e14e9b8be4c..c572bdb57c6a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -33,3 +33,10 @@ flag { bug: "327383546" is_fixed_read_only: true } + +flag { + name: "storage_test_mission_1" + namespace: "core_experiments_team_internal" + description: "If this flag is detected as true on boot, writes a logfile to track storage migration correctness." + bug: "328444881" +} diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 3c18f17b8e96..d9c371a79b2d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -434,7 +434,7 @@ </intent-filter> </receiver> - <activity android:name=".screenshot.LongScreenshotActivity" + <activity android:name=".screenshot.scroll.LongScreenshotActivity" android:theme="@style/LongScreenshotActivity" android:process=":screenshot" android:exported="false" @@ -529,15 +529,6 @@ </intent-filter> </activity-alias> - <!-- Springboard for launching the share and edit activity. This needs to be in the main - system ui process since we need to notify the status bar to dismiss the keyguard --> - <receiver android:name=".screenshot.ActionProxyReceiver" - android:exported="false" /> - - <!-- Callback for deleting screenshot notification --> - <receiver android:name=".screenshot.DeleteScreenshotReceiver" - android:exported="false" /> - <!-- Callback for invoking a smart action from the screenshot notification. --> <receiver android:name=".screenshot.SmartActionsReceiver" android:exported="false"/> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index e5e34695f40c..79ae38946195 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -345,6 +345,16 @@ flag { } flag { + name: "activity_transition_use_largest_window" + namespace: "systemui" + description: "Target largest opening window during activity transitions." + bug: "323294573" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "centralized_status_bar_height_fix" namespace: "systemui" description: "Refactors shade header and keyguard status bar to read status bar dimens from a" diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp index 99b7c36d6fb9..2268d16e5b0a 100644 --- a/packages/SystemUI/animation/Android.bp +++ b/packages/SystemUI/animation/Android.bp @@ -44,6 +44,7 @@ android_library { "androidx.core_core-animation-nodeps", "androidx.core_core-ktx", "androidx.annotation_annotation", + "com_android_systemui_flags_lib", "SystemUIShaderLib", "WindowManager-Shell-shared", "animationlib", diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 1b99e19eeb95..ea1cb3441215 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -43,6 +43,7 @@ import androidx.annotation.UiThread import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils +import com.android.systemui.Flags.activityTransitionUseLargestWindow import kotlin.math.roundToInt private const val TAG = "ActivityTransitionAnimator" @@ -648,11 +649,27 @@ class ActivityTransitionAnimator( var candidate: RemoteAnimationTarget? = null for (it in apps) { if (it.mode == RemoteAnimationTarget.MODE_OPENING) { - if (!it.hasAnimatingParent) { - return it - } - if (candidate == null) { - candidate = it + if (activityTransitionUseLargestWindow()) { + if ( + candidate == null || + !it.hasAnimatingParent && candidate.hasAnimatingParent + ) { + candidate = it + continue + } + if ( + !it.hasAnimatingParent && + it.screenSpaceBounds.hasGreaterAreaThan(candidate.screenSpaceBounds) + ) { + candidate = it + } + } else { + if (!it.hasAnimatingParent) { + return it + } + if (candidate == null) { + candidate = it + } } } } @@ -960,5 +977,9 @@ class ActivityTransitionAnimator( e.printStackTrace() } } + + private fun Rect.hasGreaterAreaThan(other: Rect): Boolean { + return (this.width() * this.height()) > (other.width() * other.height()) + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index fa94ea01d533..761292b63c35 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -87,7 +87,7 @@ fun VolumeSlider( enabled = state.isEnabled, icon = { isDragging -> if (isDragging) { - Text(text = value.toInt().toString(), color = LocalContentColor.current) + Text(text = state.valueText, color = LocalContentColor.current) } else { state.icon?.let { IconButton( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 63ec54fbef9c..82083f99ba3e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -605,6 +605,8 @@ private class SwipeTransition( override val isInitiatedByUserInput = true + override var bouncingScene: SceneKey? = null + /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) @@ -694,14 +696,31 @@ private class SwipeTransition( ): OffsetAnimation { return startOffsetAnimation { val animatable = Animatable(dragOffset, OffsetVisibilityThreshold) + val isTargetGreater = targetOffset > animatable.value val job = coroutineScope .launch { - animatable.animateTo( - targetValue = targetOffset, - animationSpec = swipeSpec, - initialVelocity = initialVelocity, - ) + try { + animatable.animateTo( + targetValue = targetOffset, + animationSpec = swipeSpec, + initialVelocity = initialVelocity, + ) { + if (bouncingScene == null) { + val isBouncing = + if (isTargetGreater) { + value > targetOffset + } else { + value < targetOffset + } + if (isBouncing) { + bouncingScene = targetScene + } + } + } + } finally { + bouncingScene = null + } } // Make sure that we settle to target scene at the end of the animation or if // the animation is cancelled. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index c7186da6b961..f1177a8eb864 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -588,7 +588,8 @@ private inline fun <T> computeValue( // TODO(b/290184746): Make sure that we don't overflow transformations associated to a // range. val directionSign = if (transition.isUpOrLeft) -1 else 1 - val overscrollProgress = transition.progress.let { if (it > 1f) it - 1f else it } + val isToScene = overscroll.scene == transition.toScene + val overscrollProgress = transition.progress.let { if (isToScene) it - 1f else it } val progress = directionSign * overscrollProgress val rangeProgress = propertySpec.range?.progress(progress) ?: progress diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index e6f5d585e915..617a8ea0b6cd 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -255,6 +255,12 @@ sealed interface TransitionState { */ val overscrollScope: OverscrollScope + /** + * The scene around which the transition is currently bouncing. When not `null`, this + * transition is currently oscillating around this scene and will soon settle to that scene. + */ + val bouncingScene: SceneKey? + companion object { const val DistanceUnspecified = 0f } @@ -287,9 +293,10 @@ internal abstract class BaseSceneTransitionLayoutState( val transition = currentTransition ?: return null if (transition !is TransitionState.HasOverscrollProperties) return null val progress = transition.progress + val bouncingScene = transition.bouncingScene return when { - progress < 0f -> fromOverscrollSpec - progress > 1f -> toOverscrollSpec + progress < 0f || bouncingScene == transition.fromScene -> fromOverscrollSpec + progress > 1f || bouncingScene == transition.toScene -> toOverscrollSpec else -> null } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 26e01fefcb9b..080476101821 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -16,6 +16,8 @@ package com.android.compose.animation.scene +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.Orientation @@ -752,4 +754,55 @@ class ElementTest { assertThat(state.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f) } + + @Test + fun elementTransitionWithDistanceDuringOverscrollBouncing() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + val state = + setupOverscrollScenario( + layoutWidth = layoutWidth, + layoutHeight = layoutHeight, + sceneTransitions = { + defaultSwipeSpec = + spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow, + ) + + overscroll(TestScenes.SceneB, Orientation.Vertical) { + // On overscroll 100% -> Foo should translate by layoutHeight + translate(TestElements.Foo, y = { absoluteDistance }) + } + }, + firstScroll = 1f, // 100% scroll + ) + + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) + fooElement.assertTopPositionInRootIsEqualTo(0.dp) + + rule.onRoot().performTouchInput { + // Scroll another 50% + moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000) + } + + val transition = state.currentTransition + assertThat(transition).isNotNull() + transition as TransitionState.HasOverscrollProperties + + // Scroll 150% (100% scroll + 50% overscroll) + assertThat(transition.progress).isEqualTo(1.5f) + assertThat(state.currentOverscrollSpec).isNotNull() + fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * (transition.progress - 1f)) + + // finger raised + rule.onRoot().performTouchInput { up() } + + // The target value is 1f, but the spring (defaultSwipeSpec) allows you to go to a lower + // value. + rule.waitUntil(timeoutMillis = 10_000) { transition.progress < 1f } + + assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(transition.bouncingScene).isEqualTo(transition.toScene) + } } diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt index 73a66c629024..a32fe2273804 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt @@ -27,6 +27,7 @@ fun transition( isInitiatedByUserInput: Boolean = false, isUserInputOngoing: Boolean = false, isUpOrLeft: Boolean = false, + bouncingScene: SceneKey? = null, orientation: Orientation = Orientation.Horizontal, ): TransitionState.Transition { return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties { @@ -37,6 +38,7 @@ fun transition( override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput override val isUserInputOngoing: Boolean = isUserInputOngoing override val isUpOrLeft: Boolean = isUpOrLeft + override val bouncingScene: SceneKey? = bouncingScene override val orientation: Orientation = orientation override val overscrollScope: OverscrollScope = object : OverscrollScope { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 24c651f3c702..a9541d962639 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -61,6 +61,7 @@ class KeyguardInteractorTest : SysuiTestCase() { private val testScope = kosmos.testScope private val repository by lazy { kosmos.fakeKeyguardRepository } private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor } private val commandQueue by lazy { FakeCommandQueue() } private val bouncerRepository = FakeKeyguardBouncerRepository() private val shadeRepository = FakeShadeRepository() @@ -79,6 +80,7 @@ class KeyguardInteractorTest : SysuiTestCase() { shadeRepository = shadeRepository, keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, + fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index de659cf17c05..f517cec040a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -81,12 +81,12 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test - fun movement_initializedToZero() = + fun movement_initializedToDefaultValues() = testScope.runTest { val movement by collectLastValue(underTest.movement(burnInParameters)) assertThat(movement?.translationY).isEqualTo(0) assertThat(movement?.translationX).isEqualTo(0) - assertThat(movement?.scale).isEqualTo(0f) + assertThat(movement?.scale).isEqualTo(1f) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 869b2bfd7752..fc604aa5a1d2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -86,6 +86,13 @@ class KeyguardRootViewModelTest : SysuiTestCase() { } @Test + fun defaultBurnInScaleEqualsOne() = + testScope.runTest { + val burnInScale by collectLastValue(underTest.scale) + assertThat(burnInScale!!.scale).isEqualTo(1f) + } + + @Test fun burnInLayerVisibility() = testScope.runTest { val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility) @@ -341,6 +348,48 @@ class KeyguardRootViewModelTest : SysuiTestCase() { } @Test + fun alpha_shadeClosedOverLockscreen_isOne() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + + // Transition to the lockscreen. + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + // Open the shade. + shadeRepository.setQsExpansion(1f) + assertThat(alpha).isEqualTo(0f) + + // Close the shade, alpha returns to 1. + shadeRepository.setQsExpansion(0f) + assertThat(alpha).isEqualTo(1f) + } + + @Test + fun alpha_shadeClosedOverDream_isZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + + // Transition to dreaming. + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + testScope, + ) + + // Open the shade. + shadeRepository.setQsExpansion(1f) + assertThat(alpha).isEqualTo(0f) + + // Close the shade, alpha is still 0 since we're not on the lockscreen. + shadeRepository.setQsExpansion(0f) + assertThat(alpha).isEqualTo(0f) + } + + @Test fun alpha_idleOnOccluded_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha(viewState)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt index 183a58a495a3..be63301e5749 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun +import com.android.systemui.statusbar.policy.HeadsUpManagerTestUtil.createFullScreenIntentEntry import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.time.FakeSystemClock @@ -97,6 +98,12 @@ class AvalancheControllerTest : SysuiTestCase() { return entry } + private fun createFsiHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry { + val entry = testableHeadsUpManager!!.createHeadsUpEntry() + entry.setEntry(createFullScreenIntentEntry(id, mContext)) + return entry + } + @Test fun testUpdate_isShowing_runsRunnable() { // Entry is showing @@ -238,4 +245,68 @@ class AvalancheControllerTest : SysuiTestCase() { // Next entry is shown Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry) } + + @Test + fun testGetDurationMs_lastEntry_useAutoDismissTime() { + // Entry is showing + val showingEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = showingEntry + + // Nothing is next + mAvalancheController.clearNext() + + val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000) + Truth.assertThat(durationMs).isEqualTo(5000) + } + + @Test + fun testGetDurationMs_nextEntryLowerPriority_500() { + // Entry is showing + val showingEntry = createFsiHeadsUpEntry(id = 1) + mAvalancheController.headsUpEntryShowing = showingEntry + + // There's another entry waiting to show next + val nextEntry = createHeadsUpEntry(id = 0) + mAvalancheController.addToNext(nextEntry, runnableMock!!) + + // Next entry has lower priority + Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(1) + + val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000) + Truth.assertThat(durationMs).isEqualTo(5000) + } + + @Test + fun testGetDurationMs_nextEntrySamePriority_1000() { + // Entry is showing + val showingEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = showingEntry + + // There's another entry waiting to show next + val nextEntry = createHeadsUpEntry(id = 1) + mAvalancheController.addToNext(nextEntry, runnableMock!!) + + // Same priority + Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(0) + + val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000) + Truth.assertThat(durationMs).isEqualTo(1000) + } + + @Test + fun testGetDurationMs_nextEntryHigherPriority_500() { + // Entry is showing + val showingEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = showingEntry + + // There's another entry waiting to show next + val nextEntry = createFsiHeadsUpEntry(id = 1) + mAvalancheController.addToNext(nextEntry, runnableMock!!) + + // Next entry has higher priority + Truth.assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(-1) + + val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000) + Truth.assertThat(durationMs).isEqualTo(500) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 830bcef55046..ed0d272cd848 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -117,21 +117,6 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { return HeadsUpManagerTestUtil.createEntry(id, notif); } - private PendingIntent createFullScreenIntent() { - return PendingIntent.getActivity( - getContext(), 0, new Intent(getContext(), this.getClass()), - PendingIntent.FLAG_MUTABLE_UNAUDITED); - } - - private NotificationEntry createFullScreenIntentEntry(int id) { - final Notification notif = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true) - .build(); - return HeadsUpManagerTestUtil.createEntry(id, notif); - } - - private void useAccessibilityTimeout(boolean use) { if (use) { doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr) @@ -239,7 +224,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { @Test public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() { final BaseHeadsUpManager hum = createHeadsUpManager(); - final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0); + final NotificationEntry notifEntry = + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext); // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned hum.showNotification(notifEntry); @@ -254,7 +240,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { @Test public void testShouldHeadsUpBecomePinned_wasUnpinned_false() { final BaseHeadsUpManager hum = createHeadsUpManager(); - final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0); + final NotificationEntry notifEntry = + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext); // Add notifEntry to ANM mAlertEntries map and make it unpinned hum.showNotification(notifEntry); @@ -443,7 +430,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { @Test public void testIsSticky_hasFullScreenIntent_true() { final BaseHeadsUpManager hum = createHeadsUpManager(); - final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0); + final NotificationEntry notifEntry = + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext); hum.showNotification(notifEntry); @@ -554,7 +542,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { // Needs full screen intent in order to be pinned final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(); - entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0)); + entryToPin.setEntry( + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext)); // Note: the standard way to show a notification would be calling showNotification rather // than onAlertEntryAdded. However, in practice showNotification in effect adds diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java index c70b03b08789..bda86197d3bd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; import android.app.ActivityManager; +import android.app.PendingIntent; +import android.content.Intent; import android.os.UserHandle; import android.content.Context; @@ -67,4 +69,16 @@ public class HeadsUpManagerTestUtil { return new NotificationEntryBuilder().setSbn( HeadsUpManagerTestUtil.createSbn(id, context)).build(); } + + protected static NotificationEntry createFullScreenIntentEntry(int id, Context context) { + final PendingIntent intent = PendingIntent.getActivity( + context, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); + + final Notification notif = new Notification.Builder(context, "") + .setSmallIcon(com.android.systemui.res.R.drawable.ic_person) + .setFullScreenIntent(intent, /* highPriority */ true) + .build(); + return HeadsUpManagerTestUtil.createEntry(id, notif); + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt index 449e8bf6998f..1ed7f5d04622 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.volume.panel.component.spatial.domain +import android.media.AudioDeviceAttributes +import android.media.AudioDeviceInfo import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.TestableLooper.RunWithLooper @@ -49,26 +51,27 @@ import org.junit.runner.RunWith class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { private val kosmos = testKosmos() - private val cachedBluetoothDevice: CachedBluetoothDevice = mock { - whenever(address).thenReturn("test_address") - } - private val bluetoothMediaDevice: BluetoothMediaDevice = mock { - whenever(cachedDevice).thenReturn(cachedBluetoothDevice) - } - private lateinit var underTest: SpatialAudioAvailabilityCriteria @Before fun setup() { with(kosmos) { - mediaControllerRepository.setActiveLocalMediaController( - mediaController.apply { - whenever(packageName).thenReturn("test.pkg") - whenever(sessionToken).thenReturn(MediaSession.Token(0, mock {})) - whenever(playbackState).thenReturn(PlaybackState.Builder().build()) + val cachedBluetoothDevice: CachedBluetoothDevice = mock { + whenever(address).thenReturn("test_address") + } + localMediaRepository.updateCurrentConnectedDevice( + mock<BluetoothMediaDevice> { + whenever(name).thenReturn("test_device") + whenever(cachedDevice).thenReturn(cachedBluetoothDevice) } ) + whenever(mediaController.packageName).thenReturn("test.pkg") + whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {})) + whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build()) + + mediaControllerRepository.setActiveLocalMediaController(mediaController) + underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor) } } @@ -77,9 +80,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { fun noSpatialAudio_noHeadTracking_unavailable() { with(kosmos) { testScope.runTest { - localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice) - spatializerRepository.defaultHeadTrackingAvailable = false - spatializerRepository.defaultSpatialAudioAvailable = false + spatializerRepository.setIsSpatialAudioAvailable(headset, false) + spatializerRepository.setIsHeadTrackingAvailable(headset, false) val isAvailable by collectLastValue(underTest.isAvailable()) runCurrent() @@ -93,9 +95,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { fun spatialAudio_noHeadTracking_available() { with(kosmos) { testScope.runTest { - localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice) - spatializerRepository.defaultHeadTrackingAvailable = false - spatializerRepository.defaultSpatialAudioAvailable = true + spatializerRepository.setIsSpatialAudioAvailable(headset, true) + spatializerRepository.setIsHeadTrackingAvailable(headset, false) val isAvailable by collectLastValue(underTest.isAvailable()) runCurrent() @@ -109,9 +110,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { fun spatialAudio_headTracking_available() { with(kosmos) { testScope.runTest { - localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice) - spatializerRepository.defaultHeadTrackingAvailable = true - spatializerRepository.defaultSpatialAudioAvailable = true + spatializerRepository.setIsSpatialAudioAvailable(headset, true) + spatializerRepository.setIsHeadTrackingAvailable(headset, true) val isAvailable by collectLastValue(underTest.isAvailable()) runCurrent() @@ -125,8 +125,8 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { fun spatialAudio_headTracking_noDevice_unavailable() { with(kosmos) { testScope.runTest { - spatializerRepository.defaultHeadTrackingAvailable = true - spatializerRepository.defaultSpatialAudioAvailable = true + localMediaRepository.updateCurrentConnectedDevice(null) + spatializerRepository.setIsSpatialAudioAvailable(headset, false) val isAvailable by collectLastValue(underTest.isAvailable()) runCurrent() @@ -135,4 +135,13 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { } } } + + private companion object { + val headset = + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + "test_address" + ) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt index 7c6ab9dc53e6..281b03d69536 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt @@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.media.BluetoothMediaDevice import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testScope import com.android.systemui.media.spatializerInteractor @@ -37,6 +38,7 @@ import com.android.systemui.volume.localMediaRepository import com.android.systemui.volume.mediaController import com.android.systemui.volume.mediaControllerRepository import com.android.systemui.volume.mediaOutputInteractor +import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -74,16 +76,6 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { mediaControllerRepository.setActiveLocalMediaController(mediaController) - spatializerRepository.setIsSpatialAudioAvailable( - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_HEADSET, - "test_address" - ), - true - ) - spatializerRepository.defaultHeadTrackingAvailable = true - underTest = SpatialAudioComponentInteractor( mediaOutputInteractor, @@ -97,6 +89,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { fun setEnabled_changesIsEnabled() { with(kosmos) { testScope.runTest { + spatializerRepository.setIsSpatialAudioAvailable(headset, true) val values by collectValues(underTest.isEnabled) underTest.setEnabled(SpatialAudioEnabledModel.Disabled) @@ -117,4 +110,92 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { } } } + + @Test + fun connectedDeviceSupports_isAvailable_SpatialAudio() { + with(kosmos) { + testScope.runTest { + spatializerRepository.setIsSpatialAudioAvailable(headset, true) + + val isAvailable by collectLastValue(underTest.isAvailable) + + assertThat(isAvailable) + .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java) + } + } + } + + @Test + fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() { + with(kosmos) { + testScope.runTest { + spatializerRepository.setIsSpatialAudioAvailable(headset, true) + spatializerRepository.setIsHeadTrackingAvailable(headset, true) + + val isAvailable by collectLastValue(underTest.isAvailable) + + assertThat(isAvailable) + .isInstanceOf(SpatialAudioAvailabilityModel.HeadTracking::class.java) + } + } + } + + @Test + fun connectedDeviceDoesntSupport_isAvailable_Unavailable() { + with(kosmos) { + testScope.runTest { + spatializerRepository.setIsSpatialAudioAvailable(headset, false) + + val isAvailable by collectLastValue(underTest.isAvailable) + + assertThat(isAvailable) + .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java) + } + } + } + + @Test + fun noConnectedDeviceBuiltinSupports_isAvailable_SpatialAudio() { + with(kosmos) { + testScope.runTest { + localMediaRepository.updateCurrentConnectedDevice(null) + spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, true) + + val isAvailable by collectLastValue(underTest.isAvailable) + + assertThat(isAvailable) + .isInstanceOf(SpatialAudioAvailabilityModel.SpatialAudio::class.java) + } + } + } + + @Test + fun noConnectedDeviceBuiltinDoesntSupport_isAvailable_Unavailable() { + with(kosmos) { + testScope.runTest { + localMediaRepository.updateCurrentConnectedDevice(null) + spatializerRepository.setIsSpatialAudioAvailable(builtinSpeaker, false) + + val isAvailable by collectLastValue(underTest.isAvailable) + + assertThat(isAvailable) + .isInstanceOf(SpatialAudioAvailabilityModel.Unavailable::class.java) + } + } + } + + private companion object { + val headset = + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + "test_address" + ) + val builtinSpeaker = + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + "" + ) + } } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml index b562d7b5ee48..6780e5721af3 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml @@ -85,6 +85,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="8dp" + android:focusable="false" androidprv:layout_constraintVertical_bias="1.0" androidprv:layout_constraintDimensionRatio="1.0" androidprv:layout_constraintStart_toStartOf="parent" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml index d9011c26dfd7..d991581c2665 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml @@ -57,6 +57,7 @@ android:id="@+id/lockPatternView" android:layout_width="0dp" android:layout_height="0dp" + android:focusable="false" androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline" androidprv:layout_constraintBottom_toBottomOf="parent" androidprv:layout_constraintLeft_toLeftOf="parent" diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml index 5fe74aa6817f..2d63c8da54f9 100644 --- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml @@ -26,7 +26,7 @@ android:layout_height="match_parent"> android:paddingHorizontal="16dp" android:paddingVertical="16dp" android:visibility="visible" - app:layout_constraintBottom_toTopOf="@+id/bottomGuideline" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/rightGuideline" app:layout_constraintStart_toStartOf="@+id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topGuideline" /> @@ -59,7 +59,7 @@ android:layout_height="match_parent"> android:layout_width="0dp" android:layout_height="0dp" android:fillViewport="true" - android:padding="16dp" + android:padding="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/buttonBarrier" @@ -82,20 +82,20 @@ android:layout_height="match_parent"> app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="wrap_content" + <TextView + android:id="@+id/logo_description" + android:layout_width="0dp" android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingHorizontal="0dp" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" + android:ellipsize="marquee" + android:gravity="@integer/biometric_dialog_text_gravity" + android:marqueeRepeatLimit="1" + android:singleLine="true" + android:textAlignment="viewStart" + android:paddingLeft="8dp" + app:layout_constraintBottom_toBottomOf="@+id/logo" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.0" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" - app:layout_constraintVertical_bias="0.0" /> + app:layout_constraintStart_toEndOf="@+id/logo" + app:layout_constraintTop_toTopOf="@+id/logo" /> <Space android:id="@+id/space_above_content" @@ -108,6 +108,7 @@ android:layout_height="match_parent"> style="@style/TextAppearance.AuthCredential.Title" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="12dp" android:gravity="@integer/biometric_dialog_text_gravity" android:paddingHorizontal="0dp" android:textAlignment="viewStart" @@ -124,6 +125,7 @@ android:layout_height="match_parent"> style="@style/TextAppearance.AuthCredential.Subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="12dp" android:gravity="@integer/biometric_dialog_text_gravity" android:paddingHorizontal="0dp" android:textAlignment="viewStart" @@ -133,14 +135,14 @@ android:layout_height="match_parent"> app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" /> - <TextView - android:id="@+id/description" - style="@style/TextAppearance.AuthCredential.Description" + <LinearLayout + android:id="@+id/customized_view_container" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" + android:gravity="center_vertical" + android:orientation="vertical" android:paddingHorizontal="0dp" - android:textAlignment="viewStart" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" @@ -149,19 +151,20 @@ android:layout_height="match_parent"> app:layout_constraintVertical_bias="0.0" /> <TextView - android:id="@+id/logo_description" - android:layout_width="0dp" + android:id="@+id/description" + style="@style/TextAppearance.AuthCredential.Description" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:ellipsize="marquee" android:gravity="@integer/biometric_dialog_text_gravity" - android:marqueeRepeatLimit="1" - android:singleLine="true" + android:paddingHorizontal="0dp" android:textAlignment="viewStart" - android:paddingLeft="8dp" - app:layout_constraintBottom_toBottomOf="@+id/logo" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/logo" - app:layout_constraintTop_toTopOf="@+id/logo" /> + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/subtitle" + app:layout_constraintVertical_bias="0.0" /> + <androidx.constraintlayout.widget.Barrier android:id="@+id/contentBarrier" @@ -178,7 +181,7 @@ android:layout_height="match_parent"> android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" android:accessibilityLiveRegion="polite" android:fadingEdge="horizontal" android:gravity="center_horizontal" @@ -192,80 +195,15 @@ android:layout_height="match_parent"> app:layout_constraintTop_toBottomOf="@+id/biometric_icon" app:layout_constraintVertical_bias="0.0" /> - <!-- Negative Button, reserved for app --> - <Button - android:id="@+id/button_negative" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:ellipsize="end" - android:maxLines="2" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintStart_toStartOf="@+id/scrollView" /> - - <!-- Cancel Button, replaces negative button when biometric is accepted --> - <Button - android:id="@+id/button_cancel" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:text="@string/cancel" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintStart_toStartOf="@+id/scrollView" /> - - <!-- "Use Credential" Button, replaces if device credential is allowed --> - <Button - android:id="@+id/button_use_credential" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintStart_toStartOf="@+id/scrollView" /> - - <!-- Positive Button --> - <Button - android:id="@+id/button_confirm" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_confirm" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintEnd_toEndOf="@+id/scrollView" - tools:visibility="invisible" /> - - <!-- Try Again Button --> - <Button - android:id="@+id/button_try_again" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_try_again" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/bottomGuideline" - app:layout_constraintEnd_toEndOf="@+id/scrollView" /> + <include + android:id="@+id/button_bar" + layout="@layout/biometric_prompt_button_bar" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@id/bottomGuideline" + app:layout_constraintEnd_toEndOf="@id/scrollView" + app:layout_constraintStart_toStartOf="@id/scrollView" + app:layout_constraintTop_toBottomOf="@id/scrollView" /> <!-- Guidelines for setting panel border --> <androidx.constraintlayout.widget.Barrier @@ -282,7 +220,7 @@ android:layout_height="match_parent"> android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="false" app:barrierDirection="top" - app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" /> + app:constraint_referenced_ids="button_bar" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/leftGuideline" diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml index 5b30dfb77342..329fc466d378 100644 --- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml @@ -23,92 +23,29 @@ android:clickable="true" android:clipToOutline="true" android:importantForAccessibility="no" - android:paddingHorizontal="16dp" - android:paddingVertical="16dp" android:visibility="visible" - app:layout_constraintBottom_toTopOf="@+id/bottomGuideline" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/rightGuideline" app:layout_constraintStart_toStartOf="@+id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topBarrier" /> - <Button - android:id="@+id/button_negative" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:ellipsize="end" - android:maxLines="2" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <Button - android:id="@+id/button_cancel" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:text="@string/cancel" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <Button - android:id="@+id/button_use_credential" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <Button - android:id="@+id/button_confirm" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_confirm" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintEnd_toEndOf="@+id/panel" - tools:visibility="invisible" /> - - <Button - android:id="@+id/button_try_again" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_try_again" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintEnd_toEndOf="@+id/panel" /> - - <!-- Negative Button, reserved for app --> + <include + layout="@layout/biometric_prompt_button_bar" + android:id="@+id/button_bar" + android:layout_width="0dp" + android:layout_height="match_parent" + app:layout_constraintBottom_toTopOf="@id/bottomGuideline" + app:layout_constraintEnd_toEndOf="@id/panel" + app:layout_constraintStart_toStartOf="@id/panel"/> <ScrollView android:id="@+id/scrollView" android:layout_width="0dp" android:layout_height="wrap_content" android:fillViewport="true" - android:padding="16dp" + android:paddingBottom="36dp" + android:paddingHorizontal="24dp" + android:paddingTop="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/biometric_icon" @@ -134,18 +71,19 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="wrap_content" + <TextView + android:id="@+id/logo_description" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" + android:ellipsize="marquee" + android:gravity="@integer/biometric_dialog_text_gravity" + android:marqueeRepeatLimit="1" + android:singleLine="true" + android:paddingTop="16dp" + app:layout_constraintBottom_toTopOf="@+id/title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" /> + app:layout_constraintTop_toBottomOf="@+id/logo" /> <Space android:id="@+id/space_above_content" @@ -159,6 +97,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/subtitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -170,23 +109,24 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/contentBarrier" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" /> - <TextView - android:id="@+id/logo_description" - android:layout_width="match_parent" + <LinearLayout + android:id="@+id/customized_view_container" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:ellipsize="marquee" - android:gravity="@integer/biometric_dialog_text_gravity" - android:marqueeRepeatLimit="1" - android:singleLine="true" - app:layout_constraintBottom_toTopOf="@+id/title" + android:gravity="center_vertical" + android:orientation="vertical" + android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/logo" /> + app:layout_constraintTop_toBottomOf="@+id/subtitle" /> <TextView android:id="@+id/description" @@ -215,7 +155,7 @@ android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" android:accessibilityLiveRegion="polite" android:fadingEdge="horizontal" android:gravity="center_horizontal" @@ -247,7 +187,7 @@ android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="false" app:barrierDirection="top" - app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" /> + app:constraint_referenced_ids="button_bar" /> <!-- Guidelines for setting panel border --> <androidx.constraintlayout.widget.Guideline diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml index cb638ee87834..bcc7bca8c915 100644 --- a/packages/SystemUI/res/layout/app_clips_screenshot.xml +++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml @@ -69,7 +69,7 @@ tools:minHeight="100dp" tools:minWidth="100dp" /> - <com.android.systemui.screenshot.CropView + <com.android.systemui.screenshot.scroll.CropView android:id="@+id/crop_view" android:layout_width="0px" android:layout_height="0px" diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml new file mode 100644 index 000000000000..810c7433e4ad --- /dev/null +++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <!-- Negative Button, reserved for app --> + <Button + android:id="@+id/button_negative" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="24dp" + android:ellipsize="end" + android:maxLines="2" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <!-- Cancel Button, replaces negative button when biometric is accepted --> + <Button + android:id="@+id/button_cancel" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="24dp" + android:text="@string/cancel" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <!-- "Use Credential" Button, replaces if device credential is allowed --> + <Button + android:id="@+id/button_use_credential" + style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="24dp" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <!-- Positive Button --> + <Button + android:id="@+id/button_confirm" + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginRight="24dp" + android:ellipsize="end" + android:maxLines="2" + android:text="@string/biometric_dialog_confirm" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + <!-- Try Again Button --> + <Button + android:id="@+id/button_try_again" + style="@*android:style/Widget.DeviceDefault.Button.Colored" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginRight="24dp" + android:ellipsize="end" + android:maxLines="2" + android:text="@string/biometric_dialog_try_again" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml index 74292b4edf0a..6391813754d0 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml @@ -23,43 +23,29 @@ android:clickable="true" android:clipToOutline="true" android:importantForAccessibility="no" - android:paddingHorizontal="16dp" - android:paddingVertical="16dp" android:visibility="visible" - app:layout_constraintBottom_toTopOf="@+id/bottomGuideline" - app:layout_constraintEnd_toStartOf="@+id/rightGuideline" - app:layout_constraintStart_toStartOf="@+id/leftGuideline" - app:layout_constraintTop_toTopOf="@+id/topBarrier" /> - - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.8" - tools:srcCompat="@tools:sample/avatars" /> + app:layout_constraintEnd_toEndOf="@id/rightGuideline" + app:layout_constraintStart_toStartOf="@id/leftGuideline" + app:layout_constraintTop_toTopOf="@id/topBarrier" /> - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon_overlay" + <include + android:id="@+id/button_bar" + layout="@layout/biometric_prompt_button_bar" android:layout_width="0dp" - android:layout_height="0dp" - android:layout_gravity="center" - android:contentDescription="@null" - android:scaleType="fitXY" - app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" - app:layout_constraintEnd_toEndOf="@+id/biometric_icon" - app:layout_constraintStart_toStartOf="@+id/biometric_icon" - app:layout_constraintTop_toTopOf="@+id/biometric_icon" /> + android:layout_height="match_parent" + app:layout_constraintBottom_toTopOf="@id/bottomGuideline" + app:layout_constraintEnd_toEndOf="@id/panel" + app:layout_constraintStart_toStartOf="@id/panel" /> <ScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="wrap_content" android:fillViewport="true" - android:padding="16dp" + android:paddingBottom="36dp" + android:paddingHorizontal="24dp" + android:paddingTop="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintBottom_toTopOf="@+id/biometric_icon" @@ -79,24 +65,22 @@ android:layout_height="@dimen/biometric_auth_icon_size" android:layout_gravity="center" android:scaleType="fitXY" - android:visibility="visible" app:layout_constraintBottom_toTopOf="@+id/logo_description" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="wrap_content" + <TextView + android:id="@+id/logo_description" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingHorizontal="@dimen/biometric_prompt_content_container_padding_horizontal" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" + android:gravity="@integer/biometric_dialog_text_gravity" + android:singleLine="true" + app:layout_constraintBottom_toTopOf="@+id/title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" /> + app:layout_constraintTop_toBottomOf="@+id/logo" /> <Space android:id="@+id/space_above_content" @@ -110,6 +94,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/subtitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -121,23 +106,24 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/contentBarrier" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" /> - <TextView - android:id="@+id/logo_description" + <LinearLayout + android:id="@+id/customized_view_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:ellipsize="marquee" - android:gravity="@integer/biometric_dialog_text_gravity" - android:marqueeRepeatLimit="1" - android:singleLine="true" - app:layout_constraintBottom_toTopOf="@+id/title" + android:gravity="center_vertical" + android:orientation="vertical" + android:visibility="gone" + android:paddingTop="8dp" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/logo" /> + app:layout_constraintTop_toBottomOf="@+id/subtitle" /> <TextView android:id="@+id/description" @@ -145,6 +131,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -165,96 +152,17 @@ android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="16dp" + android:layout_marginTop="24dp" android:accessibilityLiveRegion="polite" android:fadingEdge="horizontal" android:gravity="center_horizontal" - android:marqueeRepeatLimit="marquee_forever" android:scrollHorizontally="true" - android:textColor="@color/biometric_dialog_gray" - android:textSize="12sp" app:layout_constraintBottom_toTopOf="@+id/buttonBarrier" app:layout_constraintEnd_toEndOf="@+id/panel" app:layout_constraintStart_toStartOf="@+id/panel" app:layout_constraintTop_toBottomOf="@+id/biometric_icon" app:layout_constraintVertical_bias="0.0" /> - <!-- Negative Button, reserved for app --> - <Button - android:id="@+id/button_negative" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:ellipsize="end" - android:maxLines="2" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <!-- Cancel Button, replaces negative button when biometric is accepted --> - <Button - android:id="@+id/button_cancel" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:text="@string/cancel" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <!-- "Use Credential" Button, replaces if device credential is allowed --> - <Button - android:id="@+id/button_use_credential" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginLeft="8dp" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" /> - - <!-- Positive Button --> - <Button - android:id="@+id/button_confirm" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_confirm" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintEnd_toEndOf="@+id/panel" - tools:visibility="invisible" /> - - <!-- Try Again Button --> - <Button - android:id="@+id/button_try_again" - style="@*android:style/Widget.DeviceDefault.Button.Colored" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginBottom="8dp" - android:layout_marginRight="8dp" - android:ellipsize="end" - android:maxLines="2" - android:text="@string/biometric_dialog_try_again" - android:visibility="invisible" - app:layout_constraintBottom_toBottomOf="@+id/panel" - app:layout_constraintEnd_toEndOf="@+id/panel" /> - - <!-- Guidelines for setting panel border --> <androidx.constraintlayout.widget.Barrier android:id="@+id/topBarrier" android:layout_width="wrap_content" @@ -269,21 +177,21 @@ android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="false" app:barrierDirection="top" - app:constraint_referenced_ids="button_negative, button_cancel, button_use_credential, button_confirm, button_try_again" /> + app:constraint_referenced_ids="button_bar" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/leftGuideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" /> + app:layout_constraintGuide_begin="0dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/rightGuideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> + app:layout_constraintGuide_end="0dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/bottomGuideline" @@ -297,6 +205,29 @@ android:layout_width="0dp" android:layout_height="0dp" android:orientation="horizontal" - app:layout_constraintGuide_percent="0.25171" /> + app:layout_constraintGuide_percent="0.25" /> + + <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper + android:id="@+id/biometric_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.8" + tools:srcCompat="@tools:sample/avatars" /> + + <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper + android:id="@+id/biometric_icon_overlay" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_gravity="center" + android:contentDescription="@null" + android:scaleType="fitXY" + app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" + app:layout_constraintEnd_toEndOf="@+id/biometric_icon" + app:layout_constraintStart_toStartOf="@+id/biometric_icon" + app:layout_constraintTop_toTopOf="@+id/biometric_icon" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index 8a19c2ebdcd6..4d207da851cd 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -100,7 +100,7 @@ app:layout_constraintStart_toStartOf="parent" android:transitionName="screenshot_preview_image"/> - <com.android.systemui.screenshot.CropView + <com.android.systemui.screenshot.scroll.CropView android:id="@+id/crop_view" android:layout_width="0px" android:layout_height="0px" @@ -122,7 +122,7 @@ tools:minHeight="100dp" tools:minWidth="100dp" /> - <com.android.systemui.screenshot.MagnifierView + <com.android.systemui.screenshot.scroll.MagnifierView android:id="@+id/magnifier" android:visibility="invisible" android:layout_width="200dp" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b7eff38aa015..cac32892d9fd 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -898,8 +898,9 @@ <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen> <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub. - Keep it the same as in Launcher--> - <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen> + Larger than the 16dp Launcher uses, to ensure consistency on the hub, where it's much more + obvious when corner radii differ.--> + <dimen name="communal_enforced_rounded_corner_max_radius">28dp</dimen> <!-- Width and height used to filter widgets displayed in the communal widget picker --> <dimen name="communal_widget_picker_desired_width">424dp</dimen> @@ -1087,7 +1088,7 @@ <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen> <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen> <dimen name="biometric_dialog_button_positive_max_width">136dp</dimen> - <dimen name="biometric_dialog_corner_size">4dp</dimen> + <dimen name="biometric_dialog_corner_size">28dp</dimen> <!-- Y translation when showing/dismissing the dialog--> <dimen name="biometric_dialog_animation_translation_offset">350dp</dimen> <dimen name="biometric_dialog_border_padding">4dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e3a5e156b055..774bbe504b03 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3358,7 +3358,7 @@ <string name="microphone_blocked_dream_overlay_content_description">Microphone blocked</string> <!-- Content description for priority mode icon on dream [CHAR LIMIT=NONE] --> - <string name="priority_mode_dream_overlay_content_description">Priority mode on</string> + <string name="priority_mode_dream_overlay_content_description">Do not disturb</string> <!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] --> <string name="assistant_attention_content_description">User presence is detected</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java index 0bb5c174444f..fb331b62369e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java @@ -15,8 +15,10 @@ */ package com.android.keyguard; +import android.annotation.NonNull; import android.app.Presentation; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.media.MediaRouter; @@ -315,11 +317,11 @@ public class KeyguardDisplayManager { } @Override - public void onStateChanged(int state) { + public void onDeviceStateChanged(@NonNull DeviceState state) { // When concurrent state ends, the display also turns off. This is enforced in various // ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke // hide() since that will happen through the onDisplayRemoved callback. - mIsInConcurrentDisplayState = state == mConcurrentState; + mIsInConcurrentDisplayState = state.getIdentifier() == mConcurrentState; } boolean isConcurrentDisplayActive(Display display) { diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 8e9815085e31..039a2e5a8ffc 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -23,6 +23,7 @@ import static com.android.keyguard.LockIconView.ICON_FINGERPRINT; import static com.android.keyguard.LockIconView.ICON_LOCK; import static com.android.keyguard.LockIconView.ICON_UNLOCK; import static com.android.systemui.Flags.keyguardBottomAreaRefactor; +import static com.android.systemui.Flags.migrateClocksToBlueprint; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; @@ -452,7 +453,7 @@ public class LockIconViewController implements Dumpable { private void updateLockIconLocation() { final float scaleFactor = mAuthController.getScaleFactor(); final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor); - if (keyguardBottomAreaRefactor()) { + if (keyguardBottomAreaRefactor() || migrateClocksToBlueprint()) { mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding, scaledPadding); } else { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index da49201db808..c6716e44899f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -54,7 +54,7 @@ import com.android.systemui.recents.Recents; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; -import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; @@ -190,7 +190,7 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con private final NotificationShadeWindowController mNotificationShadeController; private final KeyguardStateController mKeyguardStateController; private final ShadeController mShadeController; - private final Lazy<ShadeViewController> mShadeViewController; + private final Lazy<PanelExpansionInteractor> mPanelExpansionInteractor; private final StatusBarWindowCallback mNotificationShadeCallback; private boolean mDismissNotificationShadeActionRegistered; @@ -200,14 +200,14 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con NotificationShadeWindowController notificationShadeController, KeyguardStateController keyguardStateController, ShadeController shadeController, - Lazy<ShadeViewController> shadeViewController, + Lazy<PanelExpansionInteractor> panelExpansionInteractor, Optional<Recents> recentsOptional, DisplayTracker displayTracker) { mContext = context; mUserTracker = userTracker; mKeyguardStateController = keyguardStateController; mShadeController = shadeController; - mShadeViewController = shadeViewController; + mPanelExpansionInteractor = panelExpansionInteractor; mRecentsOptional = recentsOptional; mDisplayTracker = displayTracker; mReceiver = new SystemActionsBroadcastReceiver(); @@ -330,7 +330,7 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con private void registerOrUnregisterDismissNotificationShadeAction() { Assert.isMainThread(); - if (mShadeViewController.get().isPanelExpanded() + if (mPanelExpansionInteractor.get().isPanelExpanded() && !mKeyguardStateController.isShowing()) { if (!mDismissNotificationShadeActionRegistered) { mA11yManager.registerSystemAction( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 8bd675c44943..99cdc0181553 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -340,13 +340,19 @@ public class AuthContainerView extends LinearLayout mPanelInteractionDetector = panelInteractionDetector; mApplicationCoroutineScope = applicationCoroutineScope; + mPromptViewModel = promptViewModel; mTranslationY = getResources() .getDimension(R.dimen.biometric_dialog_animation_translation_offset); mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mBiometricCallback = new BiometricCallback(); + mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; + mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential( + config.mPromptInfo); + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - if (constraintBp()) { + if (constraintBp() && (Utils.isBiometricAllowed(config.mPromptInfo) + || mPromptViewModel.getShowBpWithoutIconForCredential().getValue())) { mLayout = (ConstraintLayout) layoutInflater.inflate( R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */); } else { @@ -375,9 +381,7 @@ public class AuthContainerView extends LinearLayout mBackgroundExecutor = bgExecutor; mInteractionJankMonitor = jankMonitor; mPromptCredentialInteractor = credentialInteractor; - mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; mCredentialViewModelProvider = credentialViewModelProvider; - mPromptViewModel = promptViewModel; mFpProps = fpProps; mFaceProps = faceProps; @@ -408,10 +412,6 @@ public class AuthContainerView extends LinearLayout @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper ) { - // Set this value before showing either of the prompt. - mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential( - config.mPromptInfo); - if (Utils.isBiometricAllowed(config.mPromptInfo) || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) { addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index 478ef8f296b2..1dfd2e5f9cc9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -23,6 +23,7 @@ import android.graphics.Outline import android.graphics.Rect import android.transition.AutoTransition import android.transition.TransitionManager +import android.util.TypedValue import android.view.Surface import android.view.View import android.view.ViewGroup @@ -54,13 +55,10 @@ import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall import com.android.systemui.biometrics.ui.viewmodel.isRight import com.android.systemui.biometrics.ui.viewmodel.isSmall import com.android.systemui.biometrics.ui.viewmodel.isTop -import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R -import kotlin.math.abs import kotlin.math.min import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** Helper for [BiometricViewBinder] to handle resize transitions. */ @@ -106,6 +104,13 @@ object BiometricViewSizeBinder { val iconHolderView = view.requireViewById<View>(R.id.biometric_icon) val panelView = view.requireViewById<View>(R.id.panel) val cornerRadius = view.resources.getDimension(R.dimen.biometric_dialog_corner_size) + val cornerRadiusPx = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + cornerRadius, + view.resources.displayMetrics + ) + .toInt() // ConstraintSets for animating between prompt sizes val mediumConstraintSet = ConstraintSet() @@ -116,9 +121,10 @@ object BiometricViewSizeBinder { val largeConstraintSet = ConstraintSet() largeConstraintSet.clone(mediumConstraintSet) - largeConstraintSet.setGuidelineBegin(leftGuideline.id, 0) - largeConstraintSet.setGuidelineEnd(rightGuideline.id, 0) - largeConstraintSet.setGuidelineEnd(bottomGuideline.id, 0) + largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) + largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + largeConstraintSet.setVisibility(R.id.indicator, View.GONE) + largeConstraintSet.setVisibility(R.id.scrollView, View.GONE) // TODO: Investigate better way to handle 180 rotations val flipConstraintSet = ConstraintSet() @@ -141,7 +147,13 @@ object BiometricViewSizeBinder { panelView.outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect(0, 0, view.width, view.height, cornerRadius) + outline.setRoundRect( + 0, + 0, + view.width, + view.height + cornerRadiusPx, + cornerRadiusPx.toFloat() + ) } } @@ -164,35 +176,81 @@ object BiometricViewSizeBinder { when { position.isTop -> { + // Round bottom corners + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + -cornerRadiusPx, + view.width, + view.height, + cornerRadiusPx.toFloat() + ) + } + } left = windowBounds.centerX() - width / 2 + viewModel.promptMargin right = windowBounds.centerX() - width / 2 + viewModel.promptMargin bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4 } position.isBottom -> { - left = windowBounds.centerX() - width / 2 + viewModel.promptMargin - right = windowBounds.centerX() - width / 2 + viewModel.promptMargin - bottom = viewModel.promptMargin + // Round top corners + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + 0, + view.width, + view.height + cornerRadiusPx, + cornerRadiusPx.toFloat() + ) + } + } + + left = windowBounds.centerX() - width / 2 + right = windowBounds.centerX() - width / 2 + bottom = if (view.isLandscape()) bottomInset else 0 } position.isLeft -> { - left = viewModel.promptMargin - mid = - abs( - windowBounds.width() - iconHolderView.centerX() * 2 + - viewModel.promptMargin - ) + // Round right corners + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + -cornerRadiusPx, + 0, + view.width, + view.height, + cornerRadiusPx.toFloat() + ) + } + } + + left = 0 + mid = (windowBounds.width() * .85).toInt() / 2 right = windowBounds.width() - (windowBounds.width() * .85).toInt() - bottom = bottomInset + viewModel.promptMargin + bottom = if (view.isLandscape()) bottomInset else 0 } position.isRight -> { + // Round left corners + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + 0, + 0, + view.width + cornerRadiusPx, + view.height, + cornerRadiusPx.toFloat() + ) + } + } + left = windowBounds.width() - (windowBounds.width() * .85).toInt() - right = viewModel.promptMargin - bottom = bottomInset + viewModel.promptMargin - mid = - abs( - iconHolderView.centerX() - - (windowBounds.width() - iconHolderView.centerX()) - - viewModel.promptMargin - ) + right = 0 + bottom = if (view.isLandscape()) bottomInset else 0 + mid = windowBounds.width() - (windowBounds.width() * .85).toInt() / 2 } } @@ -226,16 +284,8 @@ object BiometricViewSizeBinder { } } - fun setConstraintSetVisibility() { - viewsToHideWhenSmall.forEach { - mediumConstraintSet.setVisibility(it.id, it.showContentOrHide()) - largeConstraintSet.setVisibility(it.id, View.GONE) - smallConstraintSet.setVisibility(it.id, View.GONE) - } - - largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) - largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) - largeConstraintSet.setVisibility(R.id.indicator, View.GONE) + fun setVisibilities(size: PromptSize) { + viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) } if (viewModel.showBpWithoutIconForCredential.value) { smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) @@ -261,7 +311,7 @@ object BiometricViewSizeBinder { } measureBounds(position) - setConstraintSetVisibility() + setVisibilities(size) when { size.isSmall -> { val ratio = @@ -353,7 +403,7 @@ object BiometricViewSizeBinder { // prepare for animated size transitions for (v in viewsToHideWhenSmall) { - v.visibility = v.showContentOrHide(forceHide = size.isSmall) + v.showContentOrHide(forceHide = size.isSmall) } if (viewModel.showBpWithoutIconForCredential.value) { @@ -490,14 +540,15 @@ private fun View.isLandscape(): Boolean { } } -private fun View.showContentOrHide(forceHide: Boolean = false): Int { +private fun View.showContentOrHide(forceHide: Boolean = false) { val isTextViewWithBlankText = this is TextView && this.text.isBlank() val isImageViewWithoutImage = this is ImageView && this.drawable == null - return if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) { - View.GONE - } else { - View.VISIBLE - } + visibility = + if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) { + View.GONE + } else { + View.VISIBLE + } } private fun View.centerX(): Int { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt index e457601a6d52..d8265c7d40e8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt @@ -63,12 +63,6 @@ object PromptIconViewBinder { iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second - } else { - iconView.layoutParams.width = viewModel.fingerprintIconWidth.first() - iconView.layoutParams.height = viewModel.fingerprintIconWidth.first() - - iconOverlayView.layoutParams.width = viewModel.fingerprintIconWidth.first() - iconOverlayView.layoutParams.height = viewModel.fingerprintIconWidth.first() } var faceIcon: AnimatedVectorDrawable? = null @@ -84,10 +78,8 @@ object PromptIconViewBinder { } launch { - var width = 0 - var height = 0 - combine(promptViewModel.size, viewModel.activeAuthType, ::Pair).collect { - (_, activeAuthType) -> + combine(viewModel.activeAuthType, viewModel.iconSize, ::Pair).collect { + (activeAuthType, iconSize) -> // Every time after bp shows, [isIconViewLoaded] is set to false in // [BiometricViewSizeBinder]. Then when biometric prompt view is redrew // (when size or activeAuthType changes), we need to update @@ -109,8 +101,6 @@ object PromptIconViewBinder { } } AuthType.Face -> { - width = viewModel.faceIconWidth - height = viewModel.faceIconHeight /** * Set to true by default since face icon is a drawable, which * doesn't have a LottieOnCompositionLoadedListener equivalent. @@ -122,11 +112,12 @@ object PromptIconViewBinder { } } - if (width != 0 && height != 0) { - iconView.layoutParams.width = width - iconView.layoutParams.height = height - iconOverlayView.layoutParams.width = width - iconOverlayView.layoutParams.height = height + if (iconViewLayoutParamSizeOverride == null) { + iconView.layoutParams.width = iconSize.first + iconView.layoutParams.height = iconSize.second + + iconOverlayView.layoutParams.width = iconSize.first + iconOverlayView.layoutParams.height = iconSize.second } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt index 257eb4a60328..d0c140b353e2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt @@ -21,6 +21,7 @@ import android.annotation.DrawableRes import android.annotation.RawRes import android.content.res.Configuration import android.graphics.Rect +import android.hardware.face.Face import android.util.RotationUtils import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor @@ -65,9 +66,10 @@ constructor( */ val activeAuthType: Flow<AuthType> = combine( + promptViewModel.size, promptViewModel.modalities.distinctUntilChanged(), promptViewModel.faceMode.distinctUntilChanged() - ) { modalities, faceMode -> + ) { _, modalities, faceMode -> if (modalities.hasFaceAndFingerprint && !faceMode) { AuthType.Coex } else if (modalities.hasFaceOnly || faceMode) { @@ -158,13 +160,18 @@ constructor( /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */ val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow() - /** Layout params for fingerprint iconView */ - val fingerprintIconWidth: Flow<Int> = promptViewModel.fingerprintSensorDiameter - val fingerprintIconHeight: Flow<Int> = promptViewModel.fingerprintSensorDiameter - - /** Layout params for face iconView */ - val faceIconWidth: Int = promptViewModel.faceIconWidth - val faceIconHeight: Int = promptViewModel.faceIconHeight + val iconSize: Flow<Pair<Int, Int>> = + combine( + activeAuthType, + promptViewModel.fingerprintSensorWidth, + promptViewModel.fingerprintSensorHeight, + ) { activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight -> + if (activeAuthType == AuthType.Face) { + Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight) + } else { + Pair(fingerprintSensorWidth, fingerprintSensorHeight) + } + } /** Current BiometricPromptLayout.iconView asset. */ val iconAsset: Flow<Int> = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 86b0b4455f61..fbd87fd3a9f5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -86,7 +86,7 @@ constructor( val faceIconHeight: Int = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) - val fingerprintSensorDiameter: Flow<Int> = + val fingerprintSensorWidth: Flow<Int> = combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams -> if (modalities.hasUdfps) { @@ -96,6 +96,16 @@ constructor( } } + val fingerprintSensorHeight: Flow<Int> = + combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams + -> + if (modalities.hasUdfps) { + overlayParams.sensorBounds.height() + } else { + fingerprintIconHeight + } + } + private val _accessibilityHint = MutableSharedFlow<String>() /** Hint for talkback directional guidance */ @@ -342,7 +352,7 @@ constructor( position, message, ) { size, _, message -> - size.isNotSmall && message.message.isNotBlank() + size.isMedium && message.message.isNotBlank() } /** If the auth is pending confirmation and the confirm button should be shown. */ diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index 8c87b0c78ea7..893887fad176 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -93,6 +93,8 @@ constructor( val keyguardAuthenticatedPrimaryAuth: Flow<Int> = repository.keyguardAuthenticatedPrimaryAuth val keyguardAuthenticatedBiometrics: Flow<Boolean> = repository.keyguardAuthenticatedBiometrics.filterNotNull() + val keyguardAuthenticatedBiometricsHandled: Flow<Unit> = + repository.keyguardAuthenticatedBiometrics.filter { it == null }.map {} val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> = repository.userRequestedBouncerWhenAlreadyAuthenticated.filterNotNull() val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt index 1c9d1f01e89e..e1fea5f9c62a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -23,12 +23,14 @@ import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.bouncer.ui.BouncerViewDelegate import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge /** Models UI state for the lock screen bouncer; handles user input. */ +@ExperimentalCoroutinesApi class KeyguardBouncerViewModel @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index 9afd5ede0b4c..d2df276002cc 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -24,9 +24,9 @@ import com.android.systemui.contrast.ContrastDialogActivity; import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.people.widget.LaunchConversationActivity; -import com.android.systemui.screenshot.LongScreenshotActivity; import com.android.systemui.screenshot.appclips.AppClipsActivity; import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity; +import com.android.systemui.screenshot.scroll.LongScreenshotActivity; import com.android.systemui.sensorprivacy.SensorUseStartedActivity; import com.android.systemui.settings.brightness.BrightnessDialog; import com.android.systemui.telephony.ui.activity.SwitchToManagedProfileForCallActivity; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java index 3d8e4cb71aca..6aa5e8b6d098 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java @@ -22,8 +22,6 @@ import com.android.systemui.GuestResetOrExitSessionReceiver; import com.android.systemui.media.dialog.MediaOutputDialogReceiver; import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; -import com.android.systemui.screenshot.ActionProxyReceiver; -import com.android.systemui.screenshot.DeleteScreenshotReceiver; import com.android.systemui.screenshot.SmartActionsReceiver; import com.android.systemui.volume.VolumePanelDialogReceiver; @@ -42,24 +40,6 @@ public abstract class DefaultBroadcastReceiverBinder { */ @Binds @IntoMap - @ClassKey(ActionProxyReceiver.class) - public abstract BroadcastReceiver bindActionProxyReceiver( - ActionProxyReceiver broadcastReceiver); - - /** - * - */ - @Binds - @IntoMap - @ClassKey(DeleteScreenshotReceiver.class) - public abstract BroadcastReceiver bindDeleteScreenshotReceiver( - DeleteScreenshotReceiver broadcastReceiver); - - /** - * - */ - @Binds - @IntoMap @ClassKey(SmartActionsReceiver.class) public abstract BroadcastReceiver bindSmartActionsReceiver( SmartActionsReceiver broadcastReceiver); diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt index 83337f760c33..fa7603ff777c 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt @@ -62,7 +62,7 @@ constructor( conflatedCallbackFlow { val callback = DeviceStateManager.DeviceStateCallback { state -> - trySend(deviceStateToPosture(state)) + trySend(deviceStateToPosture(state.identifier)) } deviceStateManager.registerCallback(executor, callback) awaitClose { deviceStateManager.unregisterCallback(callback) } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt index 0cb57fb937d2..1b832d4ab98d 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -21,7 +21,6 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dock.DockManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel @@ -47,7 +46,6 @@ constructor( fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel, private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, - private val dockManager: DockManager, private val communalInteractor: CommunalInteractor, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val userTracker: UserTracker, @@ -55,8 +53,7 @@ constructor( fun startTransitionFromDream() { val showGlanceableHub = - dockManager.isDocked && - communalInteractor.isCommunalEnabled.value && + communalInteractor.isCommunalEnabled.value && !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId) if (showGlanceableHub) { toGlanceableHubTransitionViewModel.startTransition() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 43a8b40a3150..3b34750756b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -40,6 +40,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.systemui.DejankUtils.whitelistIpcs; +import static com.android.systemui.Flags.migrateClocksToBlueprint; import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground; import static com.android.systemui.Flags.refactorGetCurrentUser; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; @@ -3401,6 +3402,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mSurfaceBehindRemoteAnimationFinishedCallback = null; } } + + // Ensure that keyguard becomes visible if the going away animation is canceled + if (showKeyguard && !KeyguardWmStateRefactor.isEnabled() && migrateClocksToBlueprint()) { + mKeyguardInteractor.showKeyguard(); + } } private void adjustStatusBarLocked() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 88ddfd4f4347..47f8046a92ad 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -33,7 +33,11 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch @SysUISingleton @@ -66,6 +70,25 @@ constructor( listenForTransitionToCamera(scope, keyguardInteractor) } + val surfaceBehindVisibility: Flow<Boolean?> = + combine( + transitionInteractor.startedKeyguardTransitionStep, + transitionInteractor.transitionStepsFromState(KeyguardState.ALTERNATE_BOUNCER) + ) { startedStep, fromBouncerStep -> + if (startedStep.to != KeyguardState.GONE) { + return@combine null + } + + // The alt bouncer is pretty fast to hide, so start the surface behind animation + // around 30%. + fromBouncerStep.value > 0.3f + } + .onStart { + // Default to null ("don't care, use a reasonable default"). + emit(null) + } + .distinctUntilChanged() + private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() { scope.launch { keyguardInteractor.alternateBouncerShowing diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index d5a9bd19d766..4a3232e755bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -71,6 +71,10 @@ constructor( listenForGoneToDreamingLockscreenHosted() } + fun showKeyguard() { + scope.launch { startTransitionTo(KeyguardState.LOCKSCREEN) } + } + // Primarily for when the user chooses to lock down the device private fun listenForGoneToLockscreenOrHub() { if (KeyguardWmStateRefactor.isEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 391dccc7f444..c7fafba3aa19 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -26,7 +26,6 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -96,36 +95,6 @@ constructor( } .distinctUntilChanged() - val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> = - combine( - transitionInteractor.startedKeyguardTransitionStep, - transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER) - ) { startedStep, fromBouncerStep -> - if (startedStep.to != KeyguardState.GONE) { - // BOUNCER to anything but GONE does not require any special surface - // visibility handling. - return@combine null - } - - if (fromBouncerStep.value > 0.5f) { - KeyguardSurfaceBehindModel( - animateFromAlpha = 0f, - alpha = 1f, - animateFromTranslationY = 500f, - translationY = 0f, - ) - } else { - KeyguardSurfaceBehindModel( - alpha = 0f, - ) - } - } - .onStart { - // Default to null ("don't care, use a reasonable default"). - emit(null) - } - .distinctUntilChanged() - fun dismissPrimaryBouncer() { scope.launch { startTransitionTo(KeyguardState.GONE) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 773497383f0b..283f1601846b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -83,6 +83,7 @@ constructor( shadeRepository: ShadeRepository, keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractorProvider: Provider<SceneInteractor>, + private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>, ) { // TODO(b/296118689): move to a repository private val _sharedNotificationContainerBounds = MutableStateFlow(NotificationContainerBounds()) @@ -383,6 +384,11 @@ constructor( repository.topClippingBounds.value = top } + /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */ + fun showKeyguard() { + fromGoneTransitionInteractor.get().showKeyguard() + } + companion object { private const val TAG = "KeyguardInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt index c496a6ee437f..80e94a27bec5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt @@ -95,7 +95,17 @@ constructor( } .distinctUntilChanged() - val isAnimatingSurface = repository.isAnimatingSurface + /** + * Whether we're animating the surface, or a notification launch animation is running (which + * means we're going to animate the surface, even if animators aren't yet running). + */ + val isAnimatingSurface = + combine( + repository.isAnimatingSurface, + notificationLaunchInteractor.isLaunchAnimationRunning + ) { animatingSurface, animatingLaunch -> + animatingSurface || animatingLaunch + } fun setAnimatingSurface(animating: Boolean) { repository.setAnimatingSurface(animating) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index cff74b333530..8d02e0efd72d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -40,6 +40,7 @@ constructor( surfaceBehindInteractor: KeyguardSurfaceBehindInteractor, fromLockscreenInteractor: FromLockscreenTransitionInteractor, fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor, + fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, ) { private val defaultSurfaceBehindVisibility = @@ -65,6 +66,9 @@ constructor( KeyguardState.PRIMARY_BOUNCER -> { fromBouncerInteractor.surfaceBehindVisibility } + KeyguardState.ALTERNATE_BOUNCER -> { + fromAlternateBouncerInteractor.surfaceBehindVisibility + } else -> flowOf(null) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt index 02d1471ef29c..0c8ddeeeb2cf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt @@ -20,6 +20,6 @@ package com.android.systemui.keyguard.shared.model data class BurnInModel( val translationX: Int = 0, val translationY: Int = 0, - val scale: Float = 0f, + val scale: Float = 1f, val scaleClockOnly: Boolean = false, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index d40021007a2b..3a2781c81f7c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -102,9 +102,16 @@ object AlternateBouncerViewBinder { launch { viewModel.scrimAlpha.collect { + val wasVisible = alternateBouncerViewContainer.visibility == View.VISIBLE alternateBouncerViewContainer.visibility = if (it < .1f) View.INVISIBLE else View.VISIBLE scrim.viewAlpha = it + if ( + wasVisible && alternateBouncerViewContainer.visibility == View.INVISIBLE + ) { + // view is no longer visible + viewModel.hideAlternateBouncer() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt index 1abf4a6e2f1c..f20c4acba448 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt @@ -21,8 +21,10 @@ import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransiti import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel @@ -73,13 +75,17 @@ abstract class DeviceEntryIconTransitionModule { @Binds @IntoSet + abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet abstract fun aodToLockscreen( impl: AodToLockscreenTransitionViewModel ): DeviceEntryIconTransition @Binds @IntoSet - abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition + abstract fun aodToOccluded(impl: AodToOccludedTransitionViewModel): DeviceEntryIconTransition @Binds @IntoSet @@ -93,6 +99,12 @@ abstract class DeviceEntryIconTransitionModule { @Binds @IntoSet + abstract fun dozingToOccluded( + impl: DozingToOccludedTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet abstract fun dozingToPrimaryBouncer( impl: DozingToPrimaryBouncerTransitionViewModel ): DeviceEntryIconTransition diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 1085f945506a..8fd8becab76f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -29,6 +29,7 @@ import androidx.constraintlayout.widget.ConstraintSet import com.android.keyguard.LockIconView import com.android.keyguard.LockIconViewController import com.android.systemui.Flags.keyguardBottomAreaRefactor +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.biometrics.AuthController import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor @@ -70,7 +71,11 @@ constructor( private val deviceEntryIconViewId = R.id.device_entry_icon_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) { + if ( + !keyguardBottomAreaRefactor() && + !migrateClocksToBlueprint() && + !DeviceEntryUdfpsRefactor.isEnabled + ) { return } @@ -82,7 +87,7 @@ constructor( if (DeviceEntryUdfpsRefactor.isEnabled) { DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId } } else { - // keyguardBottomAreaRefactor() + // keyguardBottomAreaRefactor() or migrateClocksToBlueprint() LockIconView(context, null).apply { id = R.id.lock_icon_view } } constraintLayout.addView(view) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt index 2526f0aec796..10a9e3bba7f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt @@ -89,4 +89,8 @@ constructor( fun showPrimaryBouncer() { statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true) } + + fun hideAlternateBouncer() { + statusBarKeyguardViewManager.hideAlternateBouncer(false) + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 5ca9215ce199..41cc1d620820 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -56,6 +56,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull @@ -125,13 +126,36 @@ constructor( .onStart { emit(false) } .distinctUntilChanged() - private val alphaOnShadeExpansion: Flow<Float> = + private val isOnLockscreen: Flow<Boolean> = combine( + keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) }, + keyguardTransitionInteractor + .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN } + .onStart { emit(false) } + ) { onLockscreen, transitioningToOrFromLockscreen -> + onLockscreen || transitioningToOrFromLockscreen + } + .distinctUntilChanged() + + private val alphaOnShadeExpansion: Flow<Float> = + combineTransform( + isOnLockscreen, shadeInteractor.qsExpansion, shadeInteractor.shadeExpansion, - ) { qsExpansion, shadeExpansion -> + ) { isOnLockscreen, qsExpansion, shadeExpansion -> // Fade out quickly as the shade expands - 1f - MathUtils.constrainedMap(0f, 1f, 0f, 0.2f, max(qsExpansion, shadeExpansion)) + if (isOnLockscreen) { + val alpha = + 1f - + MathUtils.constrainedMap( + /* rangeMin = */ 0f, + /* rangeMax = */ 1f, + /* valueMin = */ 0f, + /* valueMax = */ 0.2f, + /* value = */ max(qsExpansion, shadeExpansion) + ) + emit(alpha) + } } .distinctUntilChanged() @@ -235,11 +259,7 @@ constructor( burnInJob?.cancel() burnInJob = - scope.launch { - aodBurnInViewModel.movement(params).collect { - burnInModel.value = it - } - } + scope.launch { aodBurnInViewModel.movement(params).collect { burnInModel.value = it } } } val scale: Flow<BurnInScaleViewModel> = diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 730aa620690a..5dde14bf0867 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -65,7 +65,7 @@ constructor( it.topActivity, it.baseIntent?.component, it.taskDescription?.backgroundColor, - isForegroundTask = it.taskId in foregroundTaskIds + isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index c657b55d42d6..9c88eb95c274 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -17,6 +17,8 @@ package com.android.systemui.qs; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static com.android.systemui.Flags.centralizedStatusBarHeightFix; + import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -27,6 +29,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import com.android.systemui.res.R; +import com.android.systemui.shade.LargeScreenHeaderHelper; import com.android.systemui.util.LargeScreenUtils; /** @@ -97,7 +100,9 @@ public class QuickStatusBarHeader extends FrameLayout { qqsLP.topMargin = mContext.getResources() .getDimensionPixelSize(R.dimen.qqs_layout_margin_top); } else { - qqsLP.topMargin = mContext.getResources() + qqsLP.topMargin = centralizedStatusBarHeightFix() + ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext) + : mContext.getResources() .getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height); } mHeaderQsPanel.setLayoutParams(qqsLP); diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt index aed08f8b5457..4a8e33a99b25 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt @@ -37,7 +37,11 @@ sealed class TileSpec private constructor(open val spec: String) { data class PlatformTileSpec internal constructor( override val spec: String, - ) : TileSpec(spec) + ) : TileSpec(spec) { + override fun toString(): String { + return "P($spec)" + } + } /** * Container for the spec of a tile provided by an app. @@ -50,7 +54,7 @@ sealed class TileSpec private constructor(open val spec: String) { val componentName: ComponentName, ) : TileSpec(spec) { override fun toString(): String { - return "CustomTileSpec(${componentName.toShortString()})" + return "C(${componentName.flattenToShortString()})" } } diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java index 9076182def70..f02b871b196d 100644 --- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java @@ -16,12 +16,14 @@ package com.android.systemui.reardisplay; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManagerGlobal; import android.view.LayoutInflater; @@ -29,7 +31,6 @@ import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.LinearLayout; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -179,6 +180,7 @@ public class RearDisplayDialogController implements */ private void initializeValues(int startingBaseState) { mRearDisplayEducationDialog = mSystemUIDialogFactory.create(); + // TODO(b/329170810): Refactor and remove with updated DeviceStateManager values. if (mFoldedStates == null) { mFoldedStates = mResources.getIntArray( com.android.internal.R.array.config_foldedDeviceStates); @@ -228,21 +230,19 @@ public class RearDisplayDialogController implements private class DeviceStateManagerCallback implements DeviceStateManager.DeviceStateCallback { @Override - public void onBaseStateChanged(int state) { - if (mStartedFolded && !isFoldedState(state)) { + public void onDeviceStateChanged(@NonNull DeviceState state) { + if (mStartedFolded && !state.hasProperty( + DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) { // We've opened the device, we can close the overlay mRearDisplayEducationDialog.dismiss(); closeOverlayAndNotifyService(false); - } else if (!mStartedFolded && isFoldedState(state)) { + } else if (!mStartedFolded && state.hasProperty( + DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) { // We've closed the device, finish activity mRearDisplayEducationDialog.dismiss(); closeOverlayAndNotifyService(true); } } - - // We only care about physical device changes in this scenario - @Override - public void onStateChanged(int state) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java deleted file mode 100644 index 7e234aeed0aa..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_EDIT; -import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_DISALLOW_ENTER_PIP; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED; -import static com.android.systemui.statusbar.phone.CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT; - -import android.app.ActivityOptions; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import android.view.RemoteAnimationAdapter; -import android.view.WindowManagerGlobal; - -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.settings.DisplayTracker; -import com.android.systemui.shared.system.ActivityManagerWrapper; - -import javax.inject.Inject; - -/** - * Receiver to proxy the share or edit intent, used to clean up the notification and send - * appropriate signals to the system (ie. to dismiss the keyguard if necessary). - */ -public class ActionProxyReceiver extends BroadcastReceiver { - private static final String TAG = "ActionProxyReceiver"; - - private final ActivityManagerWrapper mActivityManagerWrapper; - private final ScreenshotSmartActions mScreenshotSmartActions; - private final DisplayTracker mDisplayTracker; - private final ActivityStarter mActivityStarter; - - @Inject - public ActionProxyReceiver(ActivityManagerWrapper activityManagerWrapper, - ScreenshotSmartActions screenshotSmartActions, - DisplayTracker displayTracker, - ActivityStarter activityStarter) { - mActivityManagerWrapper = activityManagerWrapper; - mScreenshotSmartActions = screenshotSmartActions; - mDisplayTracker = displayTracker; - mActivityStarter = activityStarter; - } - - @Override - public void onReceive(Context context, final Intent intent) { - Runnable startActivityRunnable = () -> { - mActivityManagerWrapper.closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT); - - PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); - ActivityOptions opts = ActivityOptions.makeBasic(); - opts.setDisallowEnterPictureInPictureWhileLaunching( - intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false)); - opts.setPendingIntentBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - try { - actionIntent.send(context, 0, null, null, null, null, opts.toBundle()); - if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) { - RemoteAnimationAdapter runner = new RemoteAnimationAdapter( - ScreenshotController.SCREENSHOT_REMOTE_RUNNER, 0, 0); - try { - WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionRemote(runner, - mDisplayTracker.getDefaultDisplayId()); - } catch (Exception e) { - Log.e(TAG, "Error overriding screenshot app transition", e); - } - } - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Pending intent canceled", e); - } - - }; - - mActivityStarter.executeRunnableDismissingKeyguard(startActivityRunnable, null, - true /* dismissShade */, true /* afterKeyguardGone */, - true /* deferred */); - - if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { - String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) - ? ACTION_TYPE_EDIT - : ACTION_TYPE_SHARE; - mScreenshotSmartActions.notifyScreenshotAction( - intent.getStringExtra(EXTRA_ID), actionType, false, null); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java deleted file mode 100644 index e0346f2e2a98..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_DELETE; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED; -import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_URI_ID; - -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import com.android.systemui.dagger.qualifiers.Background; - -import java.util.concurrent.Executor; - -import javax.inject.Inject; - -/** - * Removes the file at a provided URI. - */ -public class DeleteScreenshotReceiver extends BroadcastReceiver { - - private final ScreenshotSmartActions mScreenshotSmartActions; - private final Executor mBackgroundExecutor; - - @Inject - public DeleteScreenshotReceiver(ScreenshotSmartActions screenshotSmartActions, - @Background Executor backgroundExecutor) { - mScreenshotSmartActions = screenshotSmartActions; - mBackgroundExecutor = backgroundExecutor; - } - - @Override - public void onReceive(Context context, Intent intent) { - if (!intent.hasExtra(SCREENSHOT_URI_ID)) { - return; - } - - // And delete the image from the media store - final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID)); - mBackgroundExecutor.execute(() -> { - ContentResolver resolver = context.getContentResolver(); - resolver.delete(uri, null, null); - }); - if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { - mScreenshotSmartActions.notifyScreenshotAction( - intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false, null); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index c4287ca899dc..864f29a5c5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -126,7 +126,7 @@ public class ImageExporter { /** * Writes the given Bitmap to outputFile. */ - ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap, + public ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap, final File outputFile) { return CallbackToFutureAdapter.getFuture( (completer) -> { @@ -196,7 +196,7 @@ public class ImageExporter { * @param bitmap the bitmap to export * @return a listenable future result */ - ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, + public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, UserHandle owner, int displayId) { return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, mQuality, /* publish */ true, owner, mFlags, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt index 1f6d2122ebfc..a1481f6d6d2b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -34,6 +34,7 @@ import androidx.appcompat.content.res.AppCompatResources import com.android.internal.logging.UiEventLogger import com.android.systemui.flags.FeatureFlags import com.android.systemui.res.R +import com.android.systemui.screenshot.scroll.ScrollCaptureController import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER import dagger.assisted.Assisted diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java b/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java index 6050c2b90e34..440cf1c344da 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LogConfig.java @@ -16,8 +16,9 @@ package com.android.systemui.screenshot; +/** Stores debug log configuration for screenshots. */ @SuppressWarnings("PointlessBooleanExpression") -class LogConfig { +public class LogConfig { /** Log ALL the things... */ private static final boolean DEBUG_ALL = false; @@ -29,36 +30,37 @@ class LogConfig { private static final boolean TAG_WITH_CLASS_NAME = false; /** Action creation and user selection: Share, Save, Edit, Delete, Smart action, etc */ - static final boolean DEBUG_ACTIONS = DEBUG_ALL || false; + public static final boolean DEBUG_ACTIONS = DEBUG_ALL || false; /** Debug info about animations such as start, complete and cancel */ - static final boolean DEBUG_ANIM = DEBUG_ALL || false; + public static final boolean DEBUG_ANIM = DEBUG_ALL || false; /** Whenever Uri is supplied to consumer, or onComplete runnable is run() */ - static final boolean DEBUG_CALLBACK = DEBUG_ALL || false; + public static final boolean DEBUG_CALLBACK = DEBUG_ALL || false; /** Logs information about dismissing the screenshot tool */ - static final boolean DEBUG_DISMISS = DEBUG_ALL || false; + public static final boolean DEBUG_DISMISS = DEBUG_ALL || false; /** Touch or key event driven action or side effects */ - static final boolean DEBUG_INPUT = DEBUG_ALL || false; + public static final boolean DEBUG_INPUT = DEBUG_ALL || false; /** Scroll capture usage */ - static final boolean DEBUG_SCROLL = DEBUG_ALL || false; + public static final boolean DEBUG_SCROLL = DEBUG_ALL || false; /** Service lifecycle events and callbacks */ - static final boolean DEBUG_SERVICE = DEBUG_ALL || false; + public static final boolean DEBUG_SERVICE = DEBUG_ALL || false; /** Storage related actions, Bitmap.compress, ContentManager, etc */ - static final boolean DEBUG_STORAGE = DEBUG_ALL || false; + public static final boolean DEBUG_STORAGE = DEBUG_ALL || false; /** High level logical UI actions: timeout, onConfigChanged, insets, show actions, reset */ - static final boolean DEBUG_UI = DEBUG_ALL || false; + public static final boolean DEBUG_UI = DEBUG_ALL || false; /** Interactions with Window and WindowManager */ - static final boolean DEBUG_WINDOW = DEBUG_ALL || false; + public static final boolean DEBUG_WINDOW = DEBUG_ALL || false; - static String logTag(Class<?> cls) { + /** Get the appropriate class name */ + public static String logTag(Class<?> cls) { return TAG_WITH_CLASS_NAME ? cls.getSimpleName() : TAG_SS; } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 198a29c4ed5b..c8e13bb8c2fc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -87,6 +87,10 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; +import com.android.systemui.screenshot.scroll.LongScreenshotActivity; +import com.android.systemui.screenshot.scroll.LongScreenshotData; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient; +import com.android.systemui.screenshot.scroll.ScrollCaptureController; import com.android.systemui.util.Assert; import com.google.common.util.concurrent.ListenableFuture; @@ -200,7 +204,7 @@ public class ScreenshotController { void onActionsReady(ScreenshotController.QuickShareData quickShareData); } - interface TransitionDestination { + public interface TransitionDestination { /** * Allows the long screenshot activity to call back with a destination location (the bounds * on screen of the destination for the transitioning view) and a Runnable to be run once diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 1c5a8a1a9fd3..cb2dba00890b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -92,6 +92,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.screenshot.scroll.ScrollCaptureController; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt index 182b8894677a..6be32a97f4e2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -25,6 +25,7 @@ import android.view.ScrollCaptureResponse import android.view.View import android.view.ViewGroup import android.view.WindowInsets +import com.android.systemui.screenshot.scroll.ScrollCaptureController /** Abstraction of the surface between ScreenshotController and ScreenshotView */ interface ScreenshotViewProxy { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java index aa23d6bed5e1..d87d85b93b9d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java @@ -52,7 +52,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger.UiEventEnum; import com.android.settingslib.Utils; import com.android.systemui.res.R; -import com.android.systemui.screenshot.CropView; +import com.android.systemui.screenshot.scroll.CropView; import com.android.systemui.settings.UserTracker; import javax.inject.Inject; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java index 2f411ea3cb33..5e561cfb14a1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.animation.ValueAnimator; import android.content.Context; @@ -265,7 +265,7 @@ public class CropView extends View { Log.w(TAG, "No boundary selected"); break; } - Log.i(TAG, "Updated mCrop: " + mCrop); + Log.i(TAG, "Updated mCrop: " + mCrop); invalidate(); } @@ -385,7 +385,7 @@ public class CropView extends View { /** * @param action either ACTION_DOWN, ACTION_UP or ACTION_MOVE. - * @param x coordinate of the relevant pointer. + * @param x x-coordinate of the relevant pointer. */ private void updateListener(int action, float x) { if (mCropInteractionListener != null && isVertical(mCurrentDraggingBoundary)) { @@ -643,11 +643,13 @@ public class CropView extends View { /** * Listen for crop motion events and state. */ - public interface CropInteractionListener { + interface CropInteractionListener { void onCropDragStarted(CropBoundary boundary, float boundaryPosition, int boundaryPositionPx, float horizontalCenter, float x); + void onCropDragMoved(CropBoundary boundary, float boundaryPosition, int boundaryPositionPx, float horizontalCenter, float x); + void onCropDragComplete(); } @@ -675,8 +677,7 @@ public class CropView extends View { out.writeParcelable(mCrop, 0); } - public static final Parcelable.Creator<SavedState> CREATOR - = new Parcelable.Creator<SavedState>() { + public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java index 7ee7c319799c..df86d69b2527 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageLoader.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.annotation.Nullable; import android.content.ContentResolver; @@ -35,20 +35,20 @@ import java.io.InputStream; import javax.inject.Inject; /** Loads images. */ -public class ImageLoader { +class ImageLoader { private final ContentResolver mResolver; static class Result { - @Nullable Uri uri; - @Nullable File fileName; - @Nullable Bitmap bitmap; + @Nullable Uri mUri; + @Nullable File mFilename; + @Nullable Bitmap mBitmap; @Override public String toString() { final StringBuilder sb = new StringBuilder("Result{"); - sb.append("uri=").append(uri); - sb.append(", fileName=").append(fileName); - sb.append(", bitmap=").append(bitmap); + sb.append("uri=").append(mUri); + sb.append(", fileName=").append(mFilename); + sb.append(", bitmap=").append(mBitmap); sb.append('}'); return sb.toString(); } @@ -69,11 +69,10 @@ public class ImageLoader { return CallbackToFutureAdapter.getFuture(completer -> { Result result = new Result(); try (InputStream in = mResolver.openInputStream(uri)) { - result.uri = uri; - result.bitmap = BitmapFactory.decodeStream(in); + result.mUri = uri; + result.mBitmap = BitmapFactory.decodeStream(in); completer.set(result); - } - catch (IOException e) { + } catch (IOException e) { completer.setException(e); } return "BitmapFactory#decodeStream"; @@ -91,8 +90,8 @@ public class ImageLoader { return CallbackToFutureAdapter.getFuture(completer -> { try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { Result result = new Result(); - result.fileName = file; - result.bitmap = BitmapFactory.decodeStream(in); + result.mFilename = file; + result.mBitmap = BitmapFactory.decodeStream(in); completer.set(result); } catch (IOException e) { completer.setException(e); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java index a95c91bfeceb..c9c297e53bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static android.graphics.ColorSpace.Named.SRGB; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java index 356f67e7ea00..76a72f7e4adf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ImageTileSet.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.annotation.AnyThread; import android.graphics.Bitmap; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java index 00d480a76355..1e1a577ebd4a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.app.Activity; import android.app.ActivityOptions; @@ -47,11 +47,14 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.view.OneShotPreDrawListener; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.res.R; -import com.android.systemui.screenshot.CropView.CropBoundary; -import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot; -import com.android.systemui.settings.UserTracker; +import com.android.systemui.screenshot.ActionIntentCreator; +import com.android.systemui.screenshot.ActionIntentExecutor; +import com.android.systemui.screenshot.ImageExporter; +import com.android.systemui.screenshot.LogConfig; +import com.android.systemui.screenshot.ScreenshotEvent; +import com.android.systemui.screenshot.scroll.CropView.CropBoundary; +import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot; import com.google.common.util.concurrent.ListenableFuture; @@ -81,8 +84,6 @@ public class LongScreenshotActivity extends Activity { private final ImageExporter mImageExporter; private final LongScreenshotData mLongScreenshotHolder; private final ActionIntentExecutor mActionExecutor; - private final FeatureFlags mFeatureFlags; - private final UserTracker mUserTracker; private ImageView mPreview; private ImageView mTransitionView; @@ -113,16 +114,13 @@ public class LongScreenshotActivity extends Activity { @Inject public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter, @Main Executor mainExecutor, @Background Executor bgExecutor, - LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor, - FeatureFlags featureFlags, UserTracker userTracker) { + LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor) { mUiEventLogger = uiEventLogger; mUiExecutor = mainExecutor; mBackgroundExecutor = bgExecutor; mImageExporter = imageExporter; mLongScreenshotHolder = longScreenshotHolder; mActionExecutor = actionExecutor; - mFeatureFlags = featureFlags; - mUserTracker = userTracker; } @@ -265,13 +263,13 @@ public class LongScreenshotActivity extends Activity { private void onCachedImageLoaded(ImageLoader.Result imageResult) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED); - BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap); + BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.mBitmap); mPreview.setImageDrawable(drawable); mPreview.setAlpha(1f); - mMagnifierView.setDrawable(drawable, imageResult.bitmap.getWidth(), - imageResult.bitmap.getHeight()); + mMagnifierView.setDrawable(drawable, imageResult.mBitmap.getWidth(), + imageResult.mBitmap.getHeight()); mCropView.setVisibility(View.VISIBLE); - mSavedImagePath = imageResult.fileName; + mSavedImagePath = imageResult.mFilename; setButtonsEnabled(true); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java index f549faf2414a..ebac5bf2debd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotData.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.screenshot.ScreenshotController; import java.util.concurrent.atomic.AtomicReference; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java index 0c543cd8909c..0a1a74735648 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/MagnifierView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -64,16 +64,16 @@ public class MagnifierView extends View implements CropView.CropInteractionListe private ViewPropertyAnimator mTranslationAnimator; private final Animator.AnimatorListener mTranslationAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - mTranslationAnimator = null; - } - - @Override - public void onAnimationEnd(Animator animation) { - mTranslationAnimator = null; - } - }; + @Override + public void onAnimationCancel(Animator animation) { + mTranslationAnimator = null; + } + + @Override + public void onAnimationEnd(Animator animation) { + mTranslationAnimator = null; + } + }; public MagnifierView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java index e93f737308ba..0e4334314ec2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL; @@ -46,6 +46,7 @@ import androidx.concurrent.futures.CallbackToFutureAdapter.Completer; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.screenshot.LogConfig; import com.google.common.util.concurrent.ListenableFuture; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java index 8a2678c8ab09..f4c77da674b0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.content.Context; import android.graphics.Bitmap; @@ -30,8 +30,10 @@ import androidx.concurrent.futures.CallbackToFutureAdapter.Completer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult; -import com.android.systemui.screenshot.ScrollCaptureClient.Session; +import com.android.systemui.screenshot.LogConfig; +import com.android.systemui.screenshot.ScreenshotEvent; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.CaptureResult; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session; import com.google.common.util.concurrent.ListenableFuture; @@ -85,7 +87,7 @@ public class ScrollCaptureController { mImageTileSet = imageTileSet; } - /** Returns a bitmap containing the combinded result. */ + /** Returns a bitmap containing the combined result. */ public Bitmap toBitmap() { return mImageTileSet.toBitmap(); } @@ -167,7 +169,7 @@ public class ScrollCaptureController { * {@link ScrollCaptureResponse#isConnected() connected}. * @return a future ImageTile set containing the result */ - ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) { + public ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) { mCancelled = false; return CallbackToFutureAdapter.getFuture(completer -> { mCaptureCompleter = completer; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java index 71df369aa7b8..00455bcfbd99 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TiledImageDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/TiledImageDrawable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.annotation.Nullable; import android.graphics.Canvas; diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 8197b660c2fc..db06c6b25500 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -31,12 +31,6 @@ import java.util.function.Consumer * @see NotificationPanelViewController */ interface ShadeViewController { - /** - * Returns whether the shade height is greater than zero or the shade is expecting a synthesized - * down event. - */ - val isPanelExpanded: Boolean - /** Returns whether the shade is tracking touches for expand/collapse of the shade or QS. */ val isTracking: Boolean diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt index 79ffe06f7923..60cb06142a4f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt @@ -43,6 +43,12 @@ interface PanelExpansionInteractor { @Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float> /** + * Returns whether the shade height is greater than zero or the shade is expecting a synthesized + * down event. + */ + @Deprecated("Use ShadeInteractor.isAnyExpanded instead.") val isPanelExpanded: Boolean + + /** * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead. * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we * should be explicit about the what state we're checking. diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt index 3ad2b5607b9b..387767795127 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt @@ -111,4 +111,6 @@ constructor( private fun SceneKey.isExpandable(): Boolean { return this == Scenes.Shade || this == Scenes.QuickSettings } + + override val isPanelExpanded = shadeInteractor.isAnyExpanded.value } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 4275fc6f4097..44068139f66b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -178,6 +178,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT; private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT; private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT; + private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1; @@ -508,6 +509,11 @@ public class CommandQueue extends IStatusBar.Stub implements default void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {} /** + * @see IStatusBar#setSplitscreenFocus + */ + default void setSplitscreenFocus(boolean leftOrTop) {} + + /** * @see IStatusBar#showMediaOutputSwitcher */ default void showMediaOutputSwitcher(String packageName) {} @@ -1349,6 +1355,12 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override + public void setSplitscreenFocus(boolean leftOrTop) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SET_SPLITSCREEN_FOCUS, leftOrTop).sendToTarget(); + } + } + @Override public void showMediaOutputSwitcher(String packageName) { int callingUid = Binder.getCallingUid(); if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { @@ -1919,6 +1931,11 @@ public class CommandQueue extends IStatusBar.Stub implements } break; } + case MSG_SET_SPLITSCREEN_FOCUS: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).setSplitscreenFocus((Boolean) msg.obj); + } + break; case MSG_SHOW_MEDIA_OUTPUT_SWITCHER: args = (SomeArgs) msg.obj; String clientPackageName = (String) args.arg1; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt index 1bebbfd34ff4..ca7308161e14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt @@ -18,12 +18,16 @@ package com.android.systemui.statusbar.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map @@ -46,6 +50,8 @@ constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, powerInteractor: PowerInteractor, + wmLockscreenVisibilityInteractor: WindowManagerLockscreenVisibilityInteractor, + surfaceBehindInteractor: KeyguardSurfaceBehindInteractor, ) { /** Occlusion state to apply whenever a keyguard transition is STARTED, if any. */ private val occlusionStateFromStartedStep: Flow<OccludedState> = @@ -98,11 +104,21 @@ constructor( OccludedState(occluded = occluded, animate = false) } - /** Occlusion state to apply to SKBVM's setOccluded call. */ + /** Occlusion state to apply to SBKVM's setOccluded call. */ val keyguardViewOcclusionState = merge(occlusionStateFromStartedStep, occlusionStateFromFinishedStep) .distinctUntilChangedBy { // Don't switch 'animate' values mid-transition. it.occluded } + + /** Visibility state to apply to SBKVM via show() and hide(). */ + val keyguardViewVisibility = + combine( + wmLockscreenVisibilityInteractor.lockscreenVisibility, + surfaceBehindInteractor.isAnimatingSurface, + ) { lockscreenVisible, animatingSurface -> + lockscreenVisible || animatingSurface + } + .distinctUntilChanged() } 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 7f5acbe534ed..947976299f8e 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 @@ -954,17 +954,25 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable + ": " + mLastUpdateSidePaddingDumpString); } - if (viewWidth == 0 || !mSkinnyNotifsInLandscape) { + if (viewWidth == 0) { + Log.e(TAG, "updateSidePadding: viewWidth is zero"); mSidePaddings = mMinimumPaddings; return; } - // Portrait is easy, just use the dimen for paddings if (orientation == Configuration.ORIENTATION_PORTRAIT) { mSidePaddings = mMinimumPaddings; return; } + if (mShouldUseSplitNotificationShade) { + if (mSkinnyNotifsInLandscape) { + Log.e(TAG, "updateSidePadding: mSkinnyNotifsInLandscape has betrayed us!"); + } + mSidePaddings = mMinimumPaddings; + return; + } + final int innerWidth = viewWidth - mMinimumPaddings * 2; final int qsTileWidth = (innerWidth - mQsTilePadding * 3) / 4; mSidePaddings = mMinimumPaddings + qsTileWidth + mQsTilePadding; @@ -5517,6 +5525,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAmbientState.setUseSplitShade(split); updateDismissBehavior(); updateUseRoundedRectClipping(); + requestLayout(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 1faca0081155..1906b7c6bb01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -59,7 +59,6 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -97,7 +96,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final PowerManager mPowerManager; private final Optional<Vibrator> mVibratorOptional; - private final DisableFlagsLogger mDisableFlagsLogger; private final int mDisplayId; private final UserTracker mUserTracker; private final boolean mVibrateOnOpening; @@ -137,7 +135,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, PowerManager powerManager, Optional<Vibrator> vibratorOptional, - DisableFlagsLogger disableFlagsLogger, @DisplayId int displayId, Lazy<CameraLauncher> cameraLauncherLazy, UserTracker userTracker, @@ -165,7 +162,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mPowerManager = powerManager; mVibratorOptional = vibratorOptional; - mDisableFlagsLogger = disableFlagsLogger; mDisplayId = displayId; mCameraLauncherLazy = cameraLauncherLazy; mUserTracker = userTracker; @@ -489,7 +485,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba @Override public void togglePanel() { - if (mShadeViewController.isPanelExpanded()) { + if (mPanelExpansionInteractor.isPanelExpanded()) { mShadeController.animateCollapseShade(); } else { mShadeController.animateExpandShade(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt index 56b0d598a512..db237e89a683 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FoldStateListener.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone import android.content.Context +import android.hardware.devicestate.DeviceState import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback import com.android.internal.R @@ -45,13 +46,13 @@ internal class FoldStateListener( private var wasFolded: Boolean? = null - override fun onStateChanged(state: Int) { - val isFolded = foldedDeviceStates.contains(state) + override fun onDeviceStateChanged(state: DeviceState) { + val isFolded = foldedDeviceStates.contains(state.identifier) if (wasFolded == isFolded) { return } wasFolded = isFolded - val willGoToSleep = goToSleepDeviceStates.contains(state) + val willGoToSleep = goToSleepDeviceStates.contains(state.identifier) listener.onFoldStateChanged(isFolded, willGoToSleep) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 7dd328a4aad0..f99817aa4aad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -23,7 +23,6 @@ import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConst import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; -import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.content.Context; import android.content.res.ColorStateList; @@ -76,6 +75,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor; import com.android.systemui.keyguard.shared.model.DismissAction; import com.android.systemui.keyguard.shared.model.KeyguardDone; +import com.android.systemui.keyguard.shared.model.KeyguardState; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; @@ -114,8 +115,10 @@ import java.util.Set; import javax.inject.Inject; +import kotlin.Unit; import kotlinx.coroutines.CoroutineDispatcher; import kotlinx.coroutines.ExperimentalCoroutinesApi; +import kotlinx.coroutines.Job; /** * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back @@ -163,6 +166,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final Lazy<ShadeController> mShadeController; private final Lazy<SceneInteractor> mSceneInteractorLazy; + private Job mListenForAlternateBouncerTransitionSteps = null; + private Job mListenForKeyguardAuthenticatedBiometricsHandled = null; + // Local cache of expansion events, to avoid duplicates private float mFraction = -1f; private boolean mTracking = false; @@ -491,20 +497,31 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mDockManager.addListener(mDockEventListener); mIsDocked = mDockManager.isDocked(); } + if (mListenForAlternateBouncerTransitionSteps != null) { + mListenForAlternateBouncerTransitionSteps.cancel(null); + } + mListenForAlternateBouncerTransitionSteps = null; + if (mListenForKeyguardAuthenticatedBiometricsHandled != null) { + mListenForKeyguardAuthenticatedBiometricsHandled.cancel(null); + } + mListenForKeyguardAuthenticatedBiometricsHandled = null; + if (!DeviceEntryUdfpsRefactor.isEnabled()) { + mListenForAlternateBouncerTransitionSteps = mJavaAdapter.alwaysCollectFlow( + mKeyguardTransitionInteractor.transitionStepsFromState( + KeyguardState.ALTERNATE_BOUNCER), + this::consumeFromAlternateBouncerTransitionSteps + ); + + mListenForKeyguardAuthenticatedBiometricsHandled = mJavaAdapter.alwaysCollectFlow( + mPrimaryBouncerInteractor.getKeyguardAuthenticatedBiometricsHandled(), + this::consumeKeyguardAuthenticatedBiometricsHandled + ); + } if (KeyguardWmStateRefactor.isEnabled()) { // Show the keyguard views whenever we've told WM that the lockscreen is visible. mJavaAdapter.alwaysCollectFlow( - combineFlows( - mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(), - mSurfaceBehindInteractor.get().isAnimatingSurface(), - (lockscreenVis, animatingSurface) -> - // TODO(b/322546110): Waiting until we're not animating the - // surface is a workaround to avoid jank. We should actually - // fix the source of the jank, and then hide the keyguard - // view without waiting for the animation to end. - lockscreenVis || animatingSurface - ), + mStatusBarKeyguardViewManagerInteractor.getKeyguardViewVisibility(), this::consumeShowStatusBarKeyguardView); mJavaAdapter.alwaysCollectFlow( @@ -514,6 +531,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } + @VisibleForTesting + void consumeFromAlternateBouncerTransitionSteps(TransitionStep step) { + hideAlternateBouncer(false); + } + + /** + * Required without fix for b/328643370: missing AlternateBouncer (when occluded) => Gone + * transition. + */ + @VisibleForTesting + void consumeKeyguardAuthenticatedBiometricsHandled(Unit handled) { + if (mAlternateBouncerInteractor.isVisibleState()) { + hideAlternateBouncer(false); + } + } + private void consumeShowStatusBarKeyguardView(boolean show) { if (show != mLastShowing) { if (show) { @@ -1458,7 +1491,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedBiometrics(strongAuth); if (mAlternateBouncerInteractor.isVisibleState()) { - hideAlternateBouncer(false); executeAfterKeyguardGoneAction(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 88374d61d240..67d2299a9a3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -12,16 +12,19 @@ import android.view.Display import android.view.Surface import android.view.View import android.view.WindowManager.fixScale +import com.android.app.animation.Interpolators +import com.android.app.tracing.namedRunnable import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.systemui.DejankUtils -import com.android.app.animation.Interpolators +import com.android.systemui.Flags.lightRevealMigration import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.shade.ShadeViewController +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController @@ -32,9 +35,8 @@ import com.android.systemui.statusbar.notification.PropertyAnimator import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.Flags.lightRevealMigration -import com.android.app.tracing.namedRunnable import com.android.systemui.util.settings.GlobalSettings +import dagger.Lazy import javax.inject.Inject /** @@ -60,14 +62,15 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val context: Context, private val wakefulnessLifecycle: WakefulnessLifecycle, private val statusBarStateControllerImpl: StatusBarStateControllerImpl, - private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>, + private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, private val keyguardStateController: KeyguardStateController, - private val dozeParameters: dagger.Lazy<DozeParameters>, + private val dozeParameters: Lazy<DozeParameters>, private val globalSettings: GlobalSettings, - private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>, + private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>, private val interactionJankMonitor: InteractionJankMonitor, private val powerManager: PowerManager, - private val handler: Handler = Handler() + private val panelExpansionInteractorLazy: Lazy<PanelExpansionInteractor>, + private val handler: Handler = Handler(), ) : WakefulnessLifecycle.Observer, ScreenOffAnimation { private lateinit var centralSurfaces: CentralSurfaces private lateinit var shadeViewController: ShadeViewController @@ -346,7 +349,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // already expanded and showing notifications/QS, the animation looks really messy. For now, // disable it if the notification panel is expanded. if ((!this::centralSurfaces.isInitialized || - shadeViewController.isPanelExpanded) && + panelExpansionInteractorLazy.get().isPanelExpanded) && // Status bar might be expanded because we have started // playing the animation already !isAnimationPlaying() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt index f4e3eab8593d..3b2930f78d19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.model import android.os.PersistableBundle import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL +import android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL import androidx.annotation.VisibleForTesting import kotlinx.coroutines.flow.MutableStateFlow @@ -42,10 +43,11 @@ import kotlinx.coroutines.flow.asStateFlow * using the default config for logging purposes. * * NOTE to add new keys to be tracked: - * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] - * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] - * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly - * updated when a new carrier config comes down + * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] or [IntCarrierConfig] + * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] or + * [IntCarrierConfig.config] + * 3. Add the new wrapped public flow to the list of tracked configs, so they are properly updated + * when a new carrier config comes down */ class SystemUiCarrierConfig internal constructor( @@ -66,10 +68,16 @@ internal constructor( /** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */ val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config + private val satelliteHysteresisSeconds = + IntCarrierConfig(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, defaultConfig) + /** Flow tracking the [KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT] config */ + val satelliteConnectionHysteresisSeconds: StateFlow<Int> = satelliteHysteresisSeconds.config + private val trackedConfigs = listOf( inflateSignalStrength, showOperatorName, + satelliteHysteresisSeconds, ) /** Ingest a new carrier config, and switch all of the tracked keys over to the new values */ @@ -90,15 +98,19 @@ internal constructor( override fun toString(): String = trackedConfigs.joinToString { it.toString() } } +interface CarrierConfig { + fun update(config: PersistableBundle) +} + /** Extracts [key] from the carrier config, and stores it in a flow */ private class BooleanCarrierConfig( val key: String, defaultConfig: PersistableBundle, -) { +) : CarrierConfig { private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key)) val config = _configValue.asStateFlow() - fun update(config: PersistableBundle) { + override fun update(config: PersistableBundle) { _configValue.value = config.getBoolean(key) } @@ -106,3 +118,20 @@ private class BooleanCarrierConfig( return "$key=${config.value}" } } + +/** Extracts [key] from the carrier config, and stores it in a flow */ +private class IntCarrierConfig( + val key: String, + defaultConfig: PersistableBundle, +) : CarrierConfig { + private val _configValue = MutableStateFlow(defaultConfig.getInt(key)) + val config = _configValue.asStateFlow() + + override fun update(config: PersistableBundle) { + _configValue.value = config.getInt(key) + } + + override fun toString(): String { + return "$key=${config.value}" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 8f3b0e788dae..8f00b43e79f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -141,6 +141,9 @@ interface MobileConnectionRepository { */ val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> + /** Duration in seconds of the hysteresis to use when losing satellite connection. */ + val satelliteConnectionHysteresisSeconds: StateFlow<Int> + /** * True if this connection is in emergency callback mode. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index 3cb138bd75a9..af34a57c7242 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -206,6 +206,8 @@ class DemoMobileConnectionRepository( override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false) + override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0) + override suspend fun isInEcmMode(): Boolean = false /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index f8858c5037ab..2bc3bcbc8bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -186,6 +186,9 @@ class CarrierMergedConnectionRepository( */ override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow() + /** Non-applicable to carrier merged connections. */ + override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0).asStateFlow() + override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled override suspend fun isInEcmMode(): Boolean = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index a1241965de7a..60b8599ecabd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -334,6 +334,15 @@ class FullMobileConnectionRepository( activeRepo.value.hasPrioritizedNetworkCapabilities.value, ) + override val satelliteConnectionHysteresisSeconds = + activeRepo + .flatMapLatest { it.satelliteConnectionHysteresisSeconds } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + activeRepo.value.satelliteConnectionHysteresisSeconds.value + ) + override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode() class Factory diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 77fd6bef8a33..f01ac0e0a677 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -442,6 +442,9 @@ class MobileConnectionRepositoryImpl( .flowOn(bgDispatcher) .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val satelliteConnectionHysteresisSeconds: StateFlow<Int> = + systemUiCarrierConfig.satelliteConnectionHysteresisSeconds + class Factory @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index adcf736bba01..9d194cfca350 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -35,18 +35,25 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIc import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.util.kotlin.pairwiseBy +import kotlin.time.DurationUnit +import kotlin.time.toDuration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch interface MobileIconInteractor { /** The table log created for this connection */ @@ -262,6 +269,43 @@ class MobileIconInteractorImpl( MutableStateFlow(false).asStateFlow() } + private val hysteresisActive = MutableStateFlow(false) + + private val isNonTerrestrialWithHysteresis: StateFlow<Boolean> = + combine(isNonTerrestrial, hysteresisActive) { isNonTerrestrial, hysteresisActive -> + if (hysteresisActive) { + true + } else { + isNonTerrestrial + } + } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isNonTerrestrialWithHysteresis", + columnPrefix = "", + initialValue = Flags.carrierEnabledSatelliteFlag(), + ) + .stateIn(scope, SharingStarted.Eagerly, Flags.carrierEnabledSatelliteFlag()) + + private val lostSatelliteConnection = + isNonTerrestrial.pairwiseBy { old, new -> hysteresisActive.value = old && !new } + + init { + scope.launch { lostSatelliteConnection.collect() } + scope.launch { + hysteresisActive.collectLatest { + if (it) { + delay( + connectionRepository.satelliteConnectionHysteresisSeconds.value.toDuration( + DurationUnit.SECONDS + ) + ) + hysteresisActive.value = false + } + } + } + } + override val isRoaming: StateFlow<Boolean> = combine( connectionRepository.carrierNetworkChangeActive, @@ -360,7 +404,7 @@ class MobileIconInteractorImpl( showExclamationMark.value, carrierNetworkChangeActive.value, ) - isNonTerrestrial + isNonTerrestrialWithHysteresis .flatMapLatest { ntn -> if (ntn) { satelliteIcon diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index 6aaf5d60ac0b..55a0f59bf461 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -131,21 +131,37 @@ class AvalancheController @Inject constructor() { } /** - * Returns true if given HeadsUpEntry is the last one tracked by AvalancheController. Used by - * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration during active - * avalanche. + * Returns duration based on + * 1) Whether HeadsUpEntry is the last one tracked byAvalancheController + * 2) The priority of the top HUN in the next batch Used by + * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration. */ - fun shortenDuration(entry: HeadsUpEntry): Boolean { + fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int { if (!NotificationThrottleHun.isEnabled) { - // Use default display duration, like we always did before AvalancheController existed - return false + // Use default duration, like we did before AvalancheController existed + return autoDismissMs } val showingList: MutableList<HeadsUpEntry> = mutableListOf() headsUpEntryShowing?.let { showingList.add(it) } - val allEntryList = showingList + nextList - // Shorten duration if not last entry - return allEntryList.indexOf(entry) != allEntryList.size - 1 + val entryList = showingList + nextList + if (entryList.indexOf(entry) == entryList.size - 1) { + // Use default duration if last entry + return autoDismissMs + } + + nextList.sort() + val nextEntry = nextList[0] + + if (nextEntry.compareNonTimeFields(entry) == -1) { + // Next entry is higher priority + return 500 + } else if (nextEntry.compareNonTimeFields(entry) == 0) { + // Next entry is same priority + return 1000 + } else { + return autoDismissMs + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 05cc73edd892..50de3cba6b59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -776,7 +776,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { return mEarliestRemovalTime < mSystemClock.elapsedRealtime(); } - public int compareTo(@NonNull HeadsUpEntry headsUpEntry) { + public int compareNonTimeFields(HeadsUpEntry headsUpEntry) { boolean isPinned = mEntry.isRowPinned(); boolean otherPinned = headsUpEntry.mEntry.isRowPinned(); if (isPinned && !otherPinned) { @@ -806,7 +806,14 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } else if (!mRemoteInputActive && headsUpEntry.mRemoteInputActive) { return 1; } + return 0; + } + public int compareTo(@NonNull HeadsUpEntry headsUpEntry) { + int nonTimeCompareResult = compareNonTimeFields(headsUpEntry); + if (nonTimeCompareResult != 0) { + return nonTimeCompareResult; + } if (mPostTime > headsUpEntry.mPostTime) { return -1; } else if (mPostTime == headsUpEntry.mPostTime) { @@ -929,10 +936,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { int requestedTimeOutMs; if (isStickyForSomeTime()) { requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime; - } else if (mAvalancheController.shortenDuration(this)) { - requestedTimeOutMs = 1000; } else { - requestedTimeOutMs = mAutoDismissTime; + requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime); } final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs); return mPostTime + duration; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java index 422aa4d4aa60..de0eb493c83d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java @@ -16,12 +16,13 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; import android.content.Context; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateUtil; import android.util.SparseIntArray; -import androidx.annotation.NonNull; - import com.android.app.tracing.ListenersTracing; import com.android.internal.R; import com.android.systemui.dagger.SysUISingleton; @@ -42,8 +43,9 @@ public class DevicePostureControllerImpl implements DevicePostureController { /** From androidx.window.common.COMMON_STATE_USE_BASE_STATE */ private static final int COMMON_STATE_USE_BASE_STATE = 1000; private final List<Callback> mListeners = new ArrayList<>(); + private final List<DeviceState> mSupportedStates; + private DeviceState mCurrentDeviceState; private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN; - private int mCurrentBasePosture = DEVICE_POSTURE_UNKNOWN; private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); @@ -76,21 +78,17 @@ public class DevicePostureControllerImpl implements DevicePostureController { mDeviceStateToPostureMap.put(deviceState, posture); } + mSupportedStates = deviceStateManager.getSupportedDeviceStates(); deviceStateManager.registerCallback(executor, new DeviceStateManager.DeviceStateCallback() { @Override - public void onStateChanged(int state) { - Assert.isMainThread(); - mCurrentDevicePosture = - mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); - sendUpdatePosture(); - } - - @Override - public void onBaseStateChanged(int state) { + public void onDeviceStateChanged(@NonNull DeviceState state) { + mCurrentDeviceState = state; Assert.isMainThread(); - mCurrentBasePosture = mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN); - - if (useBaseState()) { + int newDevicePosture = + mDeviceStateToPostureMap.get(state.getIdentifier(), DEVICE_POSTURE_UNKNOWN); + if (newDevicePosture != mCurrentDevicePosture + || newDevicePosture == COMMON_STATE_USE_BASE_STATE) { + mCurrentDevicePosture = newDevicePosture; sendUpdatePosture(); } } @@ -120,7 +118,8 @@ public class DevicePostureControllerImpl implements DevicePostureController { @Override public int getDevicePosture() { if (useBaseState()) { - return mCurrentBasePosture; + return DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState, + mSupportedStates); } else { return mCurrentDevicePosture; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index 3008c866d207..88cf46a0ca07 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -20,6 +20,7 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORE import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; import android.annotation.Nullable; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.os.Trace; import android.util.IndentingPrintWriter; @@ -120,18 +121,18 @@ public final class DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked); } - private void updateDeviceState(int state) { - mLogger.logUpdateDeviceState(mDeviceState, state); - if (Trace.isEnabled()) { - Trace.traceBegin( - Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]"); - } + private void updateDeviceState(@NonNull DeviceState state) { + mLogger.logUpdateDeviceState(mDeviceState, state.getIdentifier()); try { - if (mDeviceState == state) { + if (Trace.isEnabled()) { + Trace.traceBegin(Trace.TRACE_TAG_APP, + "updateDeviceState [state=" + state.getIdentifier() + "]"); + } + if (mDeviceState == state.getIdentifier()) { return; } - readPersistedSetting("updateDeviceState", state); + readPersistedSetting("updateDeviceState", state.getIdentifier()); } finally { Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt index a32b75adc748..298ca67d92a8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt @@ -18,8 +18,9 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo -import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.media.BluetoothMediaDevice +import com.android.settingslib.media.MediaDevice +import com.android.settingslib.media.PhoneMediaDevice import com.android.settingslib.media.domain.interactor.SpatializerInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel @@ -54,12 +55,9 @@ constructor( private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> = mediaOutputInteractor.currentConnectedDevice .map { mediaDevice -> - mediaDevice ?: return@map null - val btDevice: CachedBluetoothDevice = - (mediaDevice as? BluetoothMediaDevice)?.cachedDevice ?: return@map null - btDevice.getAudioDeviceAttributes() + if (mediaDevice == null) builtinSpeaker else mediaDevice.getAudioDeviceAttributes() } - .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null) + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker) /** * Returns spatial audio availability model. It can be: @@ -137,34 +135,50 @@ constructor( changes.emit(Unit) } - private suspend fun CachedBluetoothDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? { - return listOf( - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_HEADSET, - address - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_SPEAKER, - address - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_BROADCAST, - address - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - address - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_HEARING_AID, - address - ) + private suspend fun MediaDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? { + when (this) { + is PhoneMediaDevice -> return builtinSpeaker + is BluetoothMediaDevice -> { + val device = cachedDevice ?: return null + return listOf( + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + device.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + device.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_BROADCAST, + device.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + device.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + device.address, + ) + ) + .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) } + } + else -> return null + } + } + + private companion object { + val builtinSpeaker = + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + "" ) - .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) } } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 7674fe988255..7931fab91f46 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -259,6 +259,11 @@ public final class WMShell implements public void moveFocusedTaskToFullscreen(int displayId) { splitScreen.goToFullscreenFromSplit(); } + + @Override + public void setSplitscreenFocus(boolean leftOrTop) { + splitScreen.setSplitscreenFocus(leftOrTop); + } }); } diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 5882b56fff6a..572a6c12f3e1 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -113,7 +113,7 @@ android:excludeFromRecents="true" /> - <activity android:name="com.android.systemui.screenshot.ScrollViewActivity" + <activity android:name="com.android.systemui.screenshot.scroll.ScrollViewActivity" android:exported="false" /> <activity android:name="com.android.systemui.screenshot.RecyclerViewActivity" diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 90587d7386ce..f1dfdf42ed0e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -20,16 +20,18 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; -import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; @@ -46,6 +48,7 @@ import org.mockito.MockitoAnnotations; public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { + private KosmosJavaAdapter mKosmos; @Mock protected KeyguardStatusView mKeyguardStatusView; @Mock protected KeyguardSliceViewController mKeyguardSliceViewController; @@ -57,7 +60,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected ScreenOffAnimationController mScreenOffAnimationController; @Mock protected KeyguardLogger mKeyguardLogger; @Mock protected KeyguardStatusViewController mControllerMock; - @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; @Mock protected DumpManager mDumpManager; protected FakeKeyguardRepository mFakeKeyguardRepository; @@ -71,6 +73,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Before public void setup() { + mKosmos = new KosmosJavaAdapter(this); MockitoAnnotations.initMocks(this); KeyguardInteractorFactory.WithDependencies deps = KeyguardInteractorFactory.create(); @@ -87,7 +90,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mDozeParameters, mScreenOffAnimationController, mKeyguardLogger, - mInteractionJankMonitor, + mKosmos.getInteractionJankMonitor(), deps.getKeyguardInteractor(), mDumpManager, PowerInteractorFactory.create( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 336a97ef09f4..fde45d34a4fd 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -135,6 +135,7 @@ import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStat import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus; import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus; import com.android.systemui.dump.DumpManager; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; @@ -195,7 +196,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID, TEST_CARRIER_ID, PROFILE_CLASS_PROVISIONING); private static final int FINGERPRINT_SENSOR_ID = 1; - + private KosmosJavaAdapter mKosmos; @Mock private UserTracker mUserTracker; @Mock @@ -240,7 +241,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private AuthController mAuthController; @Mock private TelephonyListenerManager mTelephonyListenerManager; - @Mock private InteractionJankMonitor mInteractionJankMonitor; @Mock private LatencyTracker mLatencyTracker; @@ -300,6 +300,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Before public void setup() throws RemoteException { + mKosmos = new KosmosJavaAdapter(this); + mInteractionJankMonitor = mKosmos.getInteractionJankMonitor(); MockitoAnnotations.initMocks(this); when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index d742da743a12..1c773512e590 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -147,6 +147,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java index b478d5cc9d50..c67429492180 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; @@ -38,7 +39,7 @@ import com.android.systemui.recents.Recents; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; -import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -65,7 +66,7 @@ public class SystemActionsTest extends SysuiTestCase { @Mock private ShadeController mShadeController; @Mock - private ShadeViewController mShadeViewController; + private PanelExpansionInteractor mPanelExpansionInteractor; @Mock private Optional<Recents> mRecentsOptional; @Mock @@ -87,7 +88,7 @@ public class SystemActionsTest extends SysuiTestCase { mNotificationShadeController, mKeyguardStateController, mShadeController, - () -> mShadeViewController, + () -> mPanelExpansionInteractor, mRecentsOptional, mDisplayTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt index 96ce3abebeaf..b73e4e6ab015 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt @@ -18,6 +18,8 @@ import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.DecorView import com.android.systemui.SysuiTestCase +import com.android.systemui.jank.interactionJankMonitor +import com.android.systemui.kosmos.Kosmos import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse @@ -31,7 +33,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @@ -43,7 +44,7 @@ class DialogTransitionAnimatorTest : SysuiTestCase() { private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator private val attachedViews = mutableSetOf<View>() - @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + val interactionJankMonitor = Kosmos().interactionJankMonitor @get:Rule val rule = MockitoJUnit.rule() @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 2b4e9ec4a017..072569d0e69b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -39,6 +39,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository @@ -375,6 +376,7 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testShowBiometricUI() { + mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) val container = initializeFingerprintContainer() waitForIdleSync() @@ -397,6 +399,7 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test @Ignore("b/302735104") fun testShowCredentialUI_withCustomBp() { + mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) val container = initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, isUsingContentView = true diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt index 21b8aca363ca..c79cbab87576 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt @@ -85,7 +85,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_FOLDED) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_FOLDED) + ) assertThat(state()).isEqualTo(DeviceState.FOLDED) } @@ -95,7 +97,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_HALF_FOLDED) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_HALF_FOLDED) + ) assertThat(state()).isEqualTo(DeviceState.HALF_FOLDED) } @@ -105,7 +109,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_UNFOLDED) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_UNFOLDED) + ) assertThat(state()).isEqualTo(DeviceState.UNFOLDED) } @@ -115,7 +121,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_REAR_DISPLAY) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_REAR_DISPLAY) + ) assertThat(state()).isEqualTo(DeviceState.REAR_DISPLAY) } @@ -125,7 +133,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(TEST_CONCURRENT_DISPLAY) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(TEST_CONCURRENT_DISPLAY) + ) assertThat(state()).isEqualTo(DeviceState.CONCURRENT_DISPLAY) } @@ -135,7 +145,9 @@ class DeviceStateRepositoryTest : SysuiTestCase() { testScope.runTest { val state = displayState() - deviceStateManagerListener.value.onStateChanged(123456) + deviceStateManagerListener.value.onDeviceStateChanged( + getDeviceStateForIdentifier(123456) + ) assertThat(state()).isEqualTo(DeviceState.UNKNOWN) } @@ -152,6 +164,13 @@ class DeviceStateRepositoryTest : SysuiTestCase() { private fun Int.toIntArray() = listOf(this).toIntArray() + private fun getDeviceStateForIdentifier(id: Int): android.hardware.devicestate.DeviceState { + return android.hardware.devicestate.DeviceState( + android.hardware.devicestate.DeviceState.Configuration.Builder(id, /* name= */ "") + .build() + ) + } + private companion object { // Used to fake the ids in the test. Note that there is no guarantees different devices will // have the same ids (that's why the ones in this test start from 41) 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 184924596341..272b48876a37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -75,7 +75,6 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.foldables.FoldGracePeriodProvider; -import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.InstanceId; import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.LockPatternUtils; @@ -181,7 +180,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock NotificationShadeDepthController mNotificationShadeDepthController; private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private @Mock ScreenOffAnimationController mScreenOffAnimationController; - private @Mock InteractionJankMonitor mInteractionJankMonitor; private @Mock ScreenOnCoordinator mScreenOnCoordinator; private @Mock KeyguardTransitions mKeyguardTransitions; private @Mock ShadeController mShadeController; @@ -235,8 +233,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager); when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class)); when(mPowerManager.isInteractive()).thenReturn(true); - when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true); - when(mInteractionJankMonitor.end(anyInt())).thenReturn(true); mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager); final ViewRootImpl testViewRoot = mock(ViewRootImpl.class); when(testViewRoot.getView()).thenReturn(mock(View.class)); @@ -1245,7 +1241,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mNotificationShadeDepthController, mScreenOnCoordinator, mKeyguardTransitions, - mInteractionJankMonitor, + mKosmos.getInteractionJankMonitor(), mDreamOverlayStateController, mJavaAdapter, mWallpaperRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt index 7ee8963aaa15..1839d8d15b0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt @@ -37,8 +37,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.selectedUserInteractor import junit.framework.Assert.assertEquals -import junit.framework.Assert.assertTrue -import junit.framework.Assert.fail import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent @@ -144,73 +142,6 @@ class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() { } @Test - fun testSurfaceBehindModel() = - testScope.runTest { - val values by collectValues(underTest.surfaceBehindModel) - - transitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.LOCKSCREEN, - ) - ) - runCurrent() - - assertEquals( - listOf( - null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params. - ), - values - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.GONE, - ) - ) - runCurrent() - - transitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.RUNNING, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.GONE, - value = 0.01f, - ) - ) - runCurrent() - - transitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.RUNNING, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.GONE, - value = 0.99f, - ) - ) - runCurrent() - - assertEquals(3, values.size) - val model1percent = values[1] - val model99percent = values[2] - - try { - // We should initially have an alpha of 0f when unlocking, so the surface is not - // visible - // while lockscreen UI animates out. - assertEquals(0f, model1percent!!.alpha) - - // By the end it should probably be visible. - assertTrue(model99percent!!.alpha > 0f) - } catch (e: NullPointerException) { - fail("surfaceBehindModel was unexpectedly null.") - } - } - - @Test @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testReturnToLockscreen_whenBouncerHides() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index 09c56b09bb91..58273d6805b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -107,6 +107,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { @Test fun addViewsConditionally_migrateFlagOff() { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val constraintLayout = ConstraintLayout(context, null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt index 87391cce9136..d410dac1b2e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -36,6 +37,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.Mockito.verify @ExperimentalCoroutinesApi @RunWith(JUnit4::class) @@ -48,6 +50,20 @@ class AlternateBouncerViewModelTest : SysuiTestCase() { private val underTest = kosmos.alternateBouncerViewModel @Test + fun showPrimaryBouncer() = + testScope.runTest { + underTest.showPrimaryBouncer() + verify(statusBarKeyguardViewManager).showPrimaryBouncer(any()) + } + + @Test + fun hideAlternateBouncer() = + testScope.runTest { + underTest.hideAlternateBouncer() + verify(statusBarKeyguardViewManager).hideAlternateBouncer(any()) + } + + @Test fun transitionToAlternateBouncer_scrimAlphaUpdate() = testScope.runTest { val scrimAlphas by collectValues(underTest.scrimAlpha) diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index d75553fe57ab..b593def283ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -81,7 +81,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { @Test fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() { givenRecentTasks( - createSingleTask(taskId = 1), + createSingleTask(taskId = 1, isVisible = true), ) val result = runBlocking { recentTaskListProvider.loadRecentTasks() } @@ -90,10 +90,10 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { } @Test - fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() { + fun loadRecentTasks_multipleTasks_returnsSecondVisibleTaskAsForegroundTask() { givenRecentTasks( createSingleTask(taskId = 1), - createSingleTask(taskId = 2), + createSingleTask(taskId = 2, isVisible = true), createSingleTask(taskId = 3), ) @@ -103,10 +103,25 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { } @Test - fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() { + fun loadRecentTasks_multipleTasks_returnsSecondInvisibleTaskAsNotForegroundTask() { givenRecentTasks( createSingleTask(taskId = 1), - createTaskPair(taskId1 = 2, taskId2 = 3), + createSingleTask(taskId = 2, isVisible = false), + createSingleTask(taskId = 3), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }) + .containsExactly(false, false, false) + .inOrder() + } + + @Test + fun loadRecentTasks_secondTaskIsGroupedAndVisible_marksBothGroupedTasksAsForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + createTaskPair(taskId1 = 2, taskId2 = 3, isVisible = true), createSingleTask(taskId = 4), ) @@ -117,6 +132,21 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { .inOrder() } + @Test + fun loadRecentTasks_secondTaskIsGroupedAndInvisible_marksBothGroupedTasksAsNotForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + createTaskPair(taskId1 = 2, taskId2 = 3, isVisible = false), + createSingleTask(taskId = 4), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }) + .containsExactly(false, false, false, false) + .inOrder() + } + @Suppress("UNCHECKED_CAST") private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) { whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer { @@ -136,11 +166,23 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { isForegroundTask = false, ) - private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo = - GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId)) - - private fun createTaskPair(taskId1: Int, taskId2: Int): GroupedRecentTaskInfo = - GroupedRecentTaskInfo.forSplitTasks(createTaskInfo(taskId1), createTaskInfo(taskId2), null) + private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo = + GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, isVisible)) + + private fun createTaskPair( + taskId1: Int, + taskId2: Int, + isVisible: Boolean = false + ): GroupedRecentTaskInfo = + GroupedRecentTaskInfo.forSplitTasks( + createTaskInfo(taskId1, isVisible), + createTaskInfo(taskId2, isVisible), + null + ) - private fun createTaskInfo(taskId: Int) = RecentTaskInfo().apply { this.taskId = taskId } + private fun createTaskInfo(taskId: Int, isVisible: Boolean = false) = + RecentTaskInfo().apply { + this.taskId = taskId + this.isVisible = isVisible + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java index dc211303e52c..f88a5a0d9f41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java @@ -19,7 +19,6 @@ package com.android.systemui.reardisplay; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotSame; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.reset; @@ -28,6 +27,7 @@ import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.content.res.Resources; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -38,9 +38,7 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.res.R; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.res.R; import com.android.systemui.statusbar.CommandQueue; @@ -176,6 +174,6 @@ public class RearDisplayDialogControllerTest extends SysuiTestCase { DeviceStateManager.DeviceStateCallback { @Override - public void onStateChanged(int state) { } + public void onDeviceStateChanged(DeviceState state) { } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java deleted file mode 100644 index 9ea30d676dc5..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED; -import static com.android.systemui.statusbar.phone.CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.app.PendingIntent; -import android.content.Intent; -import android.os.Bundle; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.settings.FakeDisplayTracker; -import com.android.systemui.shared.system.ActivityManagerWrapper; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class ActionProxyReceiverTest extends SysuiTestCase { - @Mock - private ActivityManagerWrapper mMockActivityManagerWrapper; - @Mock - private ScreenshotSmartActions mMockScreenshotSmartActions; - @Mock - private PendingIntent mMockPendingIntent; - @Mock - private ActivityStarter mActivityStarter; - - private Intent mIntent; - private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); - - @Before - public void setup() throws InterruptedException, ExecutionException, TimeoutException { - MockitoAnnotations.initMocks(this); - mIntent = new Intent(mContext, ActionProxyReceiver.class) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, mMockPendingIntent); - } - - @Test - public void testPendingIntentSentWithStatusBar() throws PendingIntent.CanceledException { - ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(); - // ensure that the pending intent call is passed through - doAnswer((Answer<Object>) invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }).when(mActivityStarter).executeRunnableDismissingKeyguard( - any(Runnable.class), isNull(), anyBoolean(), anyBoolean(), anyBoolean()); - - actionProxyReceiver.onReceive(mContext, mIntent); - - verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT); - verify(mActivityStarter).executeRunnableDismissingKeyguard( - any(Runnable.class), isNull(), eq(true), eq(true), eq(true)); - verify(mMockPendingIntent).send( - eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class)); - } - - @Test - public void testSmartActionsNotNotifiedByDefault() { - ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(); - - actionProxyReceiver.onReceive(mContext, mIntent); - - verify(mMockScreenshotSmartActions, never()) - .notifyScreenshotAction(anyString(), anyString(), anyBoolean(), - any(Intent.class)); - } - - @Test - public void testSmartActionsNotifiedIfEnabled() { - ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(); - mIntent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true); - String testId = "testID"; - mIntent.putExtra(EXTRA_ID, testId); - - actionProxyReceiver.onReceive(mContext, mIntent); - - verify(mMockScreenshotSmartActions).notifyScreenshotAction( - testId, ACTION_TYPE_SHARE, false, null); - } - - private ActionProxyReceiver constructActionProxyReceiver() { - return new ActionProxyReceiver( - mMockActivityManagerWrapper, - mMockScreenshotSmartActions, - mDisplayTracker, - mActivityStarter - ); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java deleted file mode 100644 index d58f47a0469a..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_DELETE; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID; -import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED; -import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_URI_ID; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Environment; -import android.provider.MediaStore; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.File; -import java.util.concurrent.Executor; - -@RunWith(AndroidTestingRunner.class) -@SmallTest -public class DeleteScreenshotReceiverTest extends SysuiTestCase { - - @Mock - private ScreenshotSmartActions mMockScreenshotSmartActions; - @Mock - private Executor mMockExecutor; - - private DeleteScreenshotReceiver mDeleteScreenshotReceiver; - private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mDeleteScreenshotReceiver = - new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mMockExecutor); - } - - @Test - public void testNoUriProvided() { - Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class); - - mDeleteScreenshotReceiver.onReceive(mContext, intent); - - verify(mMockExecutor, never()).execute(any(Runnable.class)); - verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction( - any(String.class), any(String.class), anyBoolean(), - any(Intent.class)); - } - - @Test - public void testFileDeleted() { - DeleteScreenshotReceiver deleteScreenshotReceiver = - new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mFakeExecutor); - ContentResolver contentResolver = mContext.getContentResolver(); - final Uri testUri = contentResolver.insert( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI, getFakeContentValues()); - assertNotNull(testUri); - - try { - Cursor cursor = - contentResolver.query(testUri, null, null, null, null); - assertEquals(1, cursor.getCount()); - Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class) - .putExtra(SCREENSHOT_URI_ID, testUri.toString()); - - deleteScreenshotReceiver.onReceive(mContext, intent); - int runCount = mFakeExecutor.runAllReady(); - - assertEquals(1, runCount); - cursor = - contentResolver.query(testUri, null, null, null, null); - assertEquals(0, cursor.getCount()); - } finally { - contentResolver.delete(testUri, null, null); - } - - // ensure smart actions not called by default - verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction( - any(String.class), any(String.class), anyBoolean(), any(Intent.class)); - } - - @Test - public void testNotifyScreenshotAction() { - Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class); - String uriString = "testUri"; - String testId = "testID"; - intent.putExtra(SCREENSHOT_URI_ID, uriString); - intent.putExtra(EXTRA_ID, testId); - intent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true); - - mDeleteScreenshotReceiver.onReceive(mContext, intent); - - verify(mMockExecutor).execute(any(Runnable.class)); - verify(mMockScreenshotSmartActions).notifyScreenshotAction(testId, - ACTION_TYPE_DELETE, false, null); - } - - private static ContentValues getFakeContentValues() { - final ContentValues values = new ContentValues(); - values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES - + File.separator + Environment.DIRECTORY_SCREENSHOTS); - values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test_screenshot"); - values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); - values.put(MediaStore.MediaColumns.DATE_ADDED, 0); - values.put(MediaStore.MediaColumns.DATE_MODIFIED, 0); - return values; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java index 4c8a4b0f8f61..aad461392cf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeSessionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/FakeSessionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static com.google.common.util.concurrent.Futures.getUnchecked; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java index 670a130d610a..10232602b655 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static org.junit.Assert.assertEquals; @@ -37,8 +37,8 @@ import android.view.ScrollCaptureResponse; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult; -import com.android.systemui.screenshot.ScrollCaptureClient.Session; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.CaptureResult; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session; import com.google.common.util.concurrent.ListenableFuture; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java index 6f081c759df7..f39f5439d4ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static com.google.common.util.concurrent.Futures.getUnchecked; import static com.google.common.util.concurrent.Futures.immediateFuture; @@ -36,7 +36,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; -import com.android.systemui.screenshot.ScrollCaptureClient.Session; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient.Session; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java index de97bc36be56..5699cfc96c8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureFrameworkSmokeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollCaptureFrameworkSmokeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java index 4c84df2769a0..04aba1133a78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollViewActivity.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/scroll/ScrollViewActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.app.Activity; import android.os.Bundle; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index c31c625dff50..1ee26db81826 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -196,7 +196,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { new ConfigurationInteractor(configurationRepository), shadeRepository, keyguardTransitionInteractor, - () -> sceneInteractor); + () -> sceneInteractor, + () -> mKosmos.getFromGoneTransitionInteractor()); CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index a077164ba18e..b9451bafec90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -17,7 +17,6 @@ package com.android.systemui.shade; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -223,7 +222,8 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { new ConfigurationInteractor(configurationRepository), mShadeRepository, keyguardTransitionInteractor, - () -> sceneInteractor); + () -> sceneInteractor, + () -> mKosmos.getFromGoneTransitionInteractor()); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = @@ -289,10 +289,6 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { when(mNotificationRemoteInputManager.isRemoteInputActive()) .thenReturn(false); - when(mInteractionJankMonitor.begin(any(), anyInt())) - .thenReturn(true); - when(mInteractionJankMonitor.end(anyInt())) - .thenReturn(true); when(mPanelView.getParent()).thenReturn(mPanelViewParent); when(mQs.getHeader()).thenReturn(mQsHeader); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 103dcb7dda4b..dcd000aaa011 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -45,6 +45,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor @@ -153,6 +154,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { shadeRepository, keyguardTransitionInteractor, { kosmos.sceneInteractor }, + { kosmos.fromGoneTransitionInteractor }, ) whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(MutableStateFlow(false)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index dcbb93aa496b..d5c40538586e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -49,9 +49,8 @@ import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; -import com.android.systemui.shade.domain.interactor.PanelExpansionInteractorImpl; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -79,7 +78,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private CommandQueue mCommandQueue; @Mock private QuickSettingsController mQuickSettingsController; @Mock private ShadeViewController mShadeViewController; - @Mock private PanelExpansionInteractorImpl mPanelExpansionInteractor; + @Mock private PanelExpansionInteractor mPanelExpansionInteractor; @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final MetricsLogger mMetricsLogger = new FakeMetricsLogger(); @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -129,7 +128,6 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mStatusBarHideIconsForBouncerManager, mPowerManager, Optional.of(mVibrator), - new DisableFlagsLogger(), DEFAULT_DISPLAY, mCameraLauncherLazy, mUserTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index c8c54dbd4ac2..611cf91813ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -49,6 +49,7 @@ import android.app.WallpaperManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.IntentFilter; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.fingerprint.FingerprintManager; @@ -1113,10 +1114,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } private void setDeviceState(int state) { + DeviceState deviceState = new DeviceState( + new DeviceState.Configuration.Builder(state, "TEST").build()); ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor = ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class); verify(mDeviceStateManager).registerCallback(any(), callbackCaptor.capture()); - callbackCaptor.getValue().onStateChanged(state); + callbackCaptor.getValue().onDeviceStateChanged(deviceState); } private void setGoToSleepStates(int... states) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt index 649dc235f398..5d42d5167c27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.phone +import android.hardware.devicestate.DeviceState import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.R @@ -40,52 +41,52 @@ class FoldStateListenerTest : SysuiTestCase() { @Before fun setUp() { initMocks(this) - setFoldedStates(DEVICE_STATE_FOLDED) - setGoToSleepStates(DEVICE_STATE_FOLDED) + setFoldedStates(DEVICE_STATE_FOLDED.identifier) + setGoToSleepStates(DEVICE_STATE_FOLDED.identifier) sut = FoldStateListener(mContext, listener) } @Test fun onStateChanged_stateFolded_notifiesWithFoldedAndGoingToSleep() { - sut.onStateChanged(DEVICE_STATE_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_FOLDED) verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) } @Test fun onStateChanged_stateHalfFolded_notifiesWithNotFoldedAndNotGoingToSleep() { - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) } @Test fun onStateChanged_stateUnfolded_notifiesWithNotFoldedAndNotGoingToSleep() { - sut.onStateChanged(DEVICE_STATE_UNFOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED) verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) } @Test fun onStateChanged_stateUnfoldedThenHalfFolded_notifiesOnce() { - sut.onStateChanged(DEVICE_STATE_UNFOLDED) - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) } @Test fun onStateChanged_stateHalfFoldedThenUnfolded_notifiesOnce() { - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) - sut.onStateChanged(DEVICE_STATE_UNFOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_UNFOLDED) verify(listener, times(1)).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) } @Test fun onStateChanged_stateHalfFoldedThenFolded_notifiesTwice() { - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) - sut.onStateChanged(DEVICE_STATE_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_FOLDED) val inOrder = Mockito.inOrder(listener) inOrder.verify(listener).onFoldStateChanged(NOT_FOLDED, WILL_NOT_SLEEP) @@ -94,8 +95,8 @@ class FoldStateListenerTest : SysuiTestCase() { @Test fun onStateChanged_stateFoldedThenHalfFolded_notifiesTwice() { - sut.onStateChanged(DEVICE_STATE_FOLDED) - sut.onStateChanged(DEVICE_STATE_HALF_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_FOLDED) + sut.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) val inOrder = Mockito.inOrder(listener) inOrder.verify(listener).onFoldStateChanged(FOLDED, WILL_GO_TO_SLEEP) @@ -117,9 +118,18 @@ class FoldStateListenerTest : SysuiTestCase() { } companion object { - private const val DEVICE_STATE_FOLDED = 123 - private const val DEVICE_STATE_HALF_FOLDED = 456 - private const val DEVICE_STATE_UNFOLDED = 789 + private val DEVICE_STATE_FOLDED = DeviceState( + DeviceState.Configuration.Builder(123 /* id */, "FOLDED" /* name */) + .build() + ) + private val DEVICE_STATE_HALF_FOLDED = DeviceState( + DeviceState.Configuration.Builder(456 /* id */, "HALF_FOLDED" /* name */) + .build() + ) + private val DEVICE_STATE_UNFOLDED = DeviceState( + DeviceState.Configuration.Builder(789 /* id */, "UNFOLDED" /* name */) + .build() + ) private const val FOLDED = true private const val NOT_FOLDED = false 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 1748cffcddda..d9e9c596ee4a 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 @@ -177,7 +177,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { new ConfigurationInteractor(new FakeConfigurationRepository()), new FakeShadeRepository(), keyguardTransitionInteractor, - () -> mKosmos.getSceneInteractor()); + () -> mKosmos.getSceneInteractor(), + () -> mKosmos.getFromGoneTransitionInteractor()); mViewModel = new KeyguardStatusBarViewModel( mTestScope.getBackgroundScope(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 562aa6a4f497..b0b9bec4f721 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -82,6 +82,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInte import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor; +import com.android.systemui.keyguard.shared.model.KeyguardState; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; import com.android.systemui.plugins.ActivityStarter; @@ -103,6 +106,8 @@ import com.android.systemui.util.kotlin.JavaAdapter; import com.google.common.truth.Truth; +import kotlin.Unit; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -1045,4 +1050,35 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { verify(mCentralSurfaces, never()).hideKeyguard(); verify(mPrimaryBouncerInteractor, never()).show(true); } + + @Test + public void altBouncerNotVisible_keyguardAuthenticatedBiometricsHandled() { + clearInvocations(mAlternateBouncerInteractor); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); + mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE); + verify(mAlternateBouncerInteractor, never()).hide(); + } + + @Test + public void altBouncerVisible_keyguardAuthenticatedBiometricsHandled() { + clearInvocations(mAlternateBouncerInteractor); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + mStatusBarKeyguardViewManager.consumeKeyguardAuthenticatedBiometricsHandled(Unit.INSTANCE); + verify(mAlternateBouncerInteractor).hide(); + } + + @Test + public void fromAlternateBouncerTransitionStep() { + clearInvocations(mAlternateBouncerInteractor); + mStatusBarKeyguardViewManager.consumeFromAlternateBouncerTransitionSteps( + new TransitionStep( + /* from */ KeyguardState.ALTERNATE_BOUNCER, + /* to */ KeyguardState.GONE, + /* value */ 1f, + TransitionState.FINISHED, + "StatusBarKeyguardViewManagerTest" + ) + ); + verify(mAlternateBouncerInteractor).hide(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 57a89b29b77c..de1891355f29 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.shade.ShadeViewController +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarStateControllerImpl @@ -67,6 +68,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var shadeViewController: ShadeViewController @Mock + private lateinit var panelExpansionInteractor: PanelExpansionInteractor + @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController @Mock private lateinit var lightRevealScrim: LightRevealScrim @@ -87,17 +90,18 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) controller = UnlockedScreenOffAnimationController( - context, - wakefulnessLifecycle, - statusBarStateController, - { keyguardViewMediator }, - keyguardStateController, - { dozeParameters }, - globalSettings, - { notifShadeWindowController }, - interactionJankMonitor, - powerManager, - handler = handler + context, + wakefulnessLifecycle, + statusBarStateController, + { keyguardViewMediator }, + keyguardStateController, + { dozeParameters }, + globalSettings, + { notifShadeWindowController }, + interactionJankMonitor, + powerManager, + { panelExpansionInteractor }, + handler, ) controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index 1fb6e2c7a232..c13e830afac7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY @@ -679,6 +680,9 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { telephonyManager: TelephonyManager, ): MobileConnectionRepositoryImpl { whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID) + val systemUiCarrierConfigMock: SystemUiCarrierConfig = mock() + whenever(systemUiCarrierConfigMock.satelliteConnectionHysteresisSeconds) + .thenReturn(MutableStateFlow(0)) val realRepo = MobileConnectionRepositoryImpl( @@ -689,7 +693,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { SEP, connectivityManager, telephonyManager, - systemUiCarrierConfig = mock(), + systemUiCarrierConfig = systemUiCarrierConfigMock, fakeBroadcastDispatcher, mobileMappingsProxy = mock(), testDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 49953a1176fd..c49fcf88ecaa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -43,12 +43,15 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -675,6 +678,32 @@ class MobileIconInteractorTest : SysuiTestCase() { assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) } + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun satBasedIcon_hasHysteresisWhenDisabled() = + testScope.runTest { + val latest by collectLastValue(underTest.signalLevelIcon) + + val hysteresisDuration = 5.seconds + connectionRepository.satelliteConnectionHysteresisSeconds.value = + hysteresisDuration.toInt(DurationUnit.SECONDS) + + connectionRepository.isNonTerrestrial.value = true + + assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) + + // Disable satellite + connectionRepository.isNonTerrestrial.value = false + + // Satellite icon should still be visible + assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) + + // Wait for the icon to change + advanceTimeBy(hysteresisDuration) + + assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java) + } + private fun createInteractor( overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() ) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt index ce471705ed85..c606511456fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DevicePostureControllerImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy +import android.hardware.devicestate.DeviceState import android.hardware.devicestate.DeviceStateManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -30,6 +31,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POST import com.android.systemui.statusbar.policy.DevicePostureController.SUPPORTED_POSTURES_SIZE import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -72,6 +74,18 @@ class DevicePostureControllerImplTest : SysuiTestCase() { com.android.internal.R.array.config_device_state_postures, deviceStateToPostureMapping ) + whenever(deviceStateManager.supportedDeviceStates) + .thenReturn( + listOf( + DEVICE_STATE_CLOSED, + DEVICE_STATE_HALF_FOLDED, + DEVICE_STATE_OPENED, + DEVICE_STATE_FLIPPED, + DEVICE_STATE_UNKNOWN, + DEVICE_STATE_USE_BASE_STATE + ) + ) + underTest = DevicePostureControllerImpl( context, @@ -86,20 +100,20 @@ class DevicePostureControllerImplTest : SysuiTestCase() { var posture = -1 underTest.addCallback { posture = it } - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_UNKNOWN) - assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN) - - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_CLOSED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_CLOSED) assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED) - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_OPENED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_OPENED) assertThat(posture).isEqualTo(DEVICE_POSTURE_OPENED) - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_FLIPPED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_FLIPPED) assertThat(posture).isEqualTo(DEVICE_POSTURE_FLIPPED) + + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_UNKNOWN) + assertThat(posture).isEqualTo(DEVICE_POSTURE_UNKNOWN) } @Test @@ -107,15 +121,26 @@ class DevicePostureControllerImplTest : SysuiTestCase() { var posture = -1 underTest.addCallback { posture = it } - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - // base state change doesn't change the posture - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED) + val physicalProperties = + setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED) + val updatedState = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_STATE_HALF_FOLDED.identifier, + DEVICE_STATE_HALF_FOLDED.name + ) + .setPhysicalProperties(physicalProperties) + .build() + ) + // state change with updated physical properties shouldn't cause a posture change + deviceStateCallback.value.onDeviceStateChanged(updatedState) assertThat(posture).isEqualTo(DEVICE_POSTURE_HALF_OPENED) - // WHEN the display state maps to using the base state, then posture updates - deviceStateCallback.value.onStateChanged(useBaseStateDeviceState) + // WHEN the display state maps to the physical state, then posture updates + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_USE_BASE_STATE) assertThat(posture).isEqualTo(DEVICE_POSTURE_CLOSED) } @@ -124,20 +149,97 @@ class DevicePostureControllerImplTest : SysuiTestCase() { var numPostureChanges = 0 underTest.addCallback { numPostureChanges++ } - deviceStateCallback.value.onStateChanged(DEVICE_POSTURE_HALF_OPENED) + deviceStateCallback.value.onDeviceStateChanged(DEVICE_STATE_HALF_FOLDED) assertThat(numPostureChanges).isEqualTo(1) - // base state changes doesn't send another posture update since the device state isn't - // useBaseStateDeviceState - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_CLOSED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_HALF_OPENED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_FLIPPED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_OPENED) - deviceStateCallback.value.onBaseStateChanged(DEVICE_POSTURE_UNKNOWN) + // update to physical properties doesn't send another posture update since the device state + // isn't useBaseStateDeviceState + deviceStateCallback.value.onDeviceStateChanged( + getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_CLOSED) + ) + deviceStateCallback.value.onDeviceStateChanged( + getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_HALF_FOLDED) + ) + deviceStateCallback.value.onDeviceStateChanged( + getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_OPENED) + ) + deviceStateCallback.value.onDeviceStateChanged( + getStateUpdatedPhysicalProperties(DEVICE_STATE_HALF_FOLDED, DEVICE_STATE_UNKNOWN) + ) assertThat(numPostureChanges).isEqualTo(1) } private fun verifyRegistersForDeviceStateCallback() { verify(deviceStateManager).registerCallback(eq(mainExecutor), deviceStateCallback.capture()) } + + private fun getStateUpdatedPhysicalProperties( + currentState: DeviceState, + physicalState: DeviceState + ): DeviceState { + return DeviceState( + DeviceState.Configuration.Builder(currentState.identifier, currentState.name) + .setSystemProperties(currentState.configuration.systemProperties) + .setPhysicalProperties(physicalState.configuration.physicalProperties) + .build() + ) + } + + companion object { + val DEVICE_STATE_CLOSED = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_CLOSED /* id */, + "CLOSED" /* name */ + ) + .setPhysicalProperties( + setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED) + ) + .build() + ) + val DEVICE_STATE_HALF_FOLDED = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_HALF_OPENED /* id */, + "HALF_FOLDED" /* name */ + ) + .setPhysicalProperties( + setOf( + DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN + ) + ) + .build() + ) + val DEVICE_STATE_OPENED = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_OPENED /* id */, + "OPENED" /* name */ + ) + .setPhysicalProperties( + setOf(DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN) + ) + .build() + ) + val DEVICE_STATE_FLIPPED = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_FLIPPED /* id */, + "FLIPPED" /* name */ + ) + .build() + ) + val DEVICE_STATE_UNKNOWN = + DeviceState( + DeviceState.Configuration.Builder( + DEVICE_POSTURE_UNKNOWN /* id */, + "UNKNOWN" /* name */ + ) + .build() + ) + val DEVICE_STATE_USE_BASE_STATE = + DeviceState( + DeviceState.Configuration.Builder(SUPPORTED_POSTURES_SIZE, "USE_BASE_STATE").build() + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index 4ccbd1b739f3..2955162f80c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.os.UserHandle; import android.provider.Settings; @@ -119,11 +120,11 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateCallback.onStateChanged(1); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); // Settings only exist for state 0 and 1 - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); } @@ -134,10 +135,10 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - mDeviceStateCallback.onStateChanged(1); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); } @@ -147,7 +148,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mFakeRotationPolicy.setRotationLock(true); // State 2 -> Ignored -> Fall back to state 1 which is unlocked - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); } @@ -161,7 +162,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mFakeRotationPolicy.setRotationLock(false); // State 2 -> Ignored -> Fall back to state 1 which is locked - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); } @@ -173,7 +174,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mSettingsManager.onPersistedSettingsChanged(); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( @@ -189,10 +190,10 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase @Test public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() { - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); } @@ -202,10 +203,10 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateCallback.onStateChanged(1); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - mDeviceStateCallback.onStateChanged(8); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( @@ -225,7 +226,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(false); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); @@ -241,7 +242,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase initializeSettingsWith( 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); - mDeviceStateCallback.onStateChanged(0); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(0)); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( /* rotationLocked= */ false, @@ -262,7 +263,7 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 2, DEVICE_STATE_ROTATION_LOCK_IGNORED); - mDeviceStateCallback.onStateChanged(2); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(2)); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( /* rotationLocked= */ true, @@ -283,8 +284,8 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 8, DEVICE_STATE_ROTATION_LOCK_IGNORED); - mDeviceStateCallback.onStateChanged(1); - mDeviceStateCallback.onStateChanged(8); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(1)); + mDeviceStateCallback.onDeviceStateChanged(createDeviceStateForIdentifier(8)); mDeviceStateRotationLockSettingController.onRotationLockStateChanged( /* rotationLocked= */ true, @@ -320,6 +321,10 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mSettingsManager.onPersistedSettingsChanged(); } + private DeviceState createDeviceStateForIdentifier(int id) { + return new DeviceState(new DeviceState.Configuration.Builder(id, "" /* name */).build()); + } + private static class FakeRotationPolicy implements RotationPolicyWrapper { private boolean mRotationLock; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index 76913e8e15ee..e4b9f102c51c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.kosmos.testScope @@ -63,9 +64,9 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { ConfigurationInteractor(FakeConfigurationRepository()), FakeShadeRepository(), kosmos.keyguardTransitionInteractor, - ) { - kosmos.sceneInteractor - } + { kosmos.sceneInteractor }, + { kosmos.fromGoneTransitionInteractor }, + ) private val keyguardStatusBarInteractor = KeyguardStatusBarInteractor( FakeKeyguardStatusBarRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 19f31d55d4a4..ec27f48f9570 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -441,7 +441,8 @@ public class BubblesTest extends SysuiTestCase { new ConfigurationInteractor(configurationRepository), shadeRepository, keyguardTransitionInteractor, - () -> sceneInteractor); + () -> sceneInteractor, + () -> mKosmos.getFromGoneTransitionInteractor()); mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt index 5c5016daf029..e2b5869fce99 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/jank/InteractionJankMonitorKosmos.kt @@ -16,9 +16,24 @@ package com.android.systemui.jank +import android.os.HandlerThread import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.jank.InteractionJankMonitor.Configuration.Builder import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.spy -val Kosmos.interactionJankMonitor by Fixture<InteractionJankMonitor> { mock() } +val Kosmos.interactionJankMonitor by + Fixture<InteractionJankMonitor> { + spy(InteractionJankMonitor(HandlerThread("InteractionJankMonitor-Kosmos"))).apply { + doReturn(true).`when`(this).shouldMonitor() + doReturn(true).`when`(this).begin(any(), anyInt()) + doReturn(true).`when`(this).begin(any<Builder>()) + doReturn(true).`when`(this).end(anyInt()) + doReturn(true).`when`(this).cancel(anyInt()) + doReturn(true).`when`(this).cancel(anyInt(), anyInt()) + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index 3893a9b74b2a..00cdc337bc06 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -51,6 +51,7 @@ object KeyguardInteractorFactory { configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(), shadeRepository: FakeShadeRepository = FakeShadeRepository(), sceneInteractor: SceneInteractor = mock(), + fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(), powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor, ): WithDependencies { // Mock this until the class is replaced by kosmos @@ -77,6 +78,7 @@ object KeyguardInteractorFactory { sceneInteractorProvider = { sceneInteractor }, keyguardTransitionInteractor = keyguardTransitionInteractor, powerInteractor = powerInteractor, + fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, ), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt index 5140a9f5c2ba..d61bc9f559bb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt @@ -26,7 +26,7 @@ import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.statusbar.commandQueue -val Kosmos.keyguardInteractor by +val Kosmos.keyguardInteractor: KeyguardInteractor by Kosmos.Fixture { KeyguardInteractor( repository = keyguardRepository, @@ -38,5 +38,6 @@ val Kosmos.keyguardInteractor by shadeRepository = shadeRepository, keyguardTransitionInteractor = keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, + fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt index d84988da7cc3..29167d64d1f1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt @@ -27,6 +27,7 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by surfaceBehindInteractor = keyguardSurfaceBehindInteractor, fromLockscreenInteractor = fromLockscreenTransitionInteractor, fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor, + fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 3fc5af1a50ab..e861892252fa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -34,6 +34,7 @@ import com.android.systemui.globalactions.domain.interactor.globalActionsInterac import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor @@ -92,6 +93,7 @@ class KosmosJavaAdapter( val fromPrimaryBouncerTransitionInteractor by lazy { kosmos.fromPrimaryBouncerTransitionInteractor } + val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor } val globalActionsInteractor by lazy { kosmos.globalActionsInteractor } val sceneDataSource by lazy { kosmos.sceneDataSource } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java index 63f7c9755782..ea59c0a24cf8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeScrollCaptureConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import android.content.pm.ActivityInfo; import android.graphics.Canvas; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java index 478658eb232d..3b7b158264cf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/FakeSession.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/screenshot/scroll/FakeSession.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.screenshot; +package com.android.systemui.screenshot.scroll; import static android.util.MathUtils.constrain; @@ -32,6 +32,8 @@ import android.hardware.HardwareBuffer; import android.media.Image; import android.util.Log; +import com.android.systemui.screenshot.scroll.ScrollCaptureClient; + import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractorKosmos.kt index 9e34fe8d7c61..2ed56ce0bf24 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractorKosmos.kt @@ -16,7 +16,9 @@ package com.android.systemui.statusbar.domain.interactor +import com.android.systemui.keyguard.domain.interactor.keyguardSurfaceBehindInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.power.domain.interactor.powerInteractor @@ -26,5 +28,7 @@ val Kosmos.statusBarKeyguardViewManagerInteractor by keyguardTransitionInteractor = this.keyguardTransitionInteractor, keyguardOcclusionInteractor = this.keyguardOcclusionInteractor, powerInteractor = this.powerInteractor, + wmLockscreenVisibilityInteractor = windowManagerLockscreenVisibilityInteractor, + surfaceBehindInteractor = keyguardSurfaceBehindInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 32d572ef9dee..2d5a3612ff6a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -63,6 +63,8 @@ class FakeMobileConnectionRepository( override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false) + override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0) + private var isInEcmMode: Boolean = false override suspend fun isInEcmMode(): Boolean = isInEcmMode diff --git a/services/Android.bp b/services/Android.bp index 87096928af7c..98a7979de30a 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -119,6 +119,7 @@ filegroup { ":services.companion-sources", ":services.contentcapture-sources", ":services.contentsuggestions-sources", + ":services.contextualsearch-sources", ":services.coverage-sources", ":services.credentials-sources", ":services.devicepolicy-sources", @@ -208,6 +209,7 @@ java_library { "services.companion", "services.contentcapture", "services.contentsuggestions", + "services.contextualsearch", "services.coverage", "services.credentials", "services.devicepolicy", diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 2d531e73c3fd..4e14dee8acba 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -935,34 +935,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } final AccessibilityUserState userState = getUserStateLocked(userId); - if (Flags.disableContinuousShortcutOnForceStop()) { - if (doit && onPackagesForceStoppedLocked(packages, userState)) { - onUserStateChangedLocked(userState); - return false; - } else { - return true; - } - } else { - final Iterator<ComponentName> it = userState.mEnabledServices.iterator(); - while (it.hasNext()) { - final ComponentName comp = it.next(); - final String compPkg = comp.getPackageName(); - for (String pkg : packages) { - if (compPkg.equals(pkg)) { - if (!doit) { - return true; - } - it.remove(); - userState.getBindingServicesLocked().remove(comp); - userState.getCrashedServicesLocked().remove(comp); - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - userState.mEnabledServices, userId); - onUserStateChangedLocked(userState); - } - } - } + if (doit && onPackagesForceStoppedLocked(packages, userState)) { + onUserStateChangedLocked(userState); return false; + } else { + return true; } } } @@ -5225,18 +5202,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private int mSystemUiUid = 0; AccessibilityDisplayListener(Context context, Handler handler) { - if (Flags.addWindowTokenWithoutLock()) { - // Avoid concerns about one thread adding displays while another thread removes - // them by ensuring the looper is the main looper and the DisplayListener - // callbacks are always executed on the one main thread. - final boolean isMainHandler = handler.getLooper() == Looper.getMainLooper(); - final String errorMessage = - "AccessibilityDisplayListener must use the main handler"; - if (Build.IS_USERDEBUG || Build.IS_ENG) { - Preconditions.checkArgument(isMainHandler, errorMessage); - } else if (!isMainHandler) { - Slog.e(LOG_TAG, errorMessage); - } + // Avoid concerns about one thread adding displays while another thread removes + // them by ensuring the looper is the main looper and the DisplayListener + // callbacks are always executed on the one main thread. + final boolean isMainHandler = handler.getLooper() == Looper.getMainLooper(); + final String errorMessage = + "AccessibilityDisplayListener must use the main handler"; + if (Build.IS_USERDEBUG || Build.IS_ENG) { + Preconditions.checkArgument(isMainHandler, errorMessage); + } else if (!isMainHandler) { + Slog.e(LOG_TAG, errorMessage); } mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); @@ -5280,14 +5255,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onDisplayAdded(int displayId) { - if (Flags.addWindowTokenWithoutLock()) { - final boolean isMainThread = Looper.getMainLooper().isCurrentThread(); - final String errorMessage = "onDisplayAdded must be called from the main thread"; - if (Build.IS_USERDEBUG || Build.IS_ENG) { - Preconditions.checkArgument(isMainThread, errorMessage); - } else if (!isMainThread) { - Slog.e(LOG_TAG, errorMessage); - } + final boolean isMainThread = Looper.getMainLooper().isCurrentThread(); + final String errorMessage = "onDisplayAdded must be called from the main thread"; + if (Build.IS_USERDEBUG || Build.IS_ENG) { + Preconditions.checkArgument(isMainThread, errorMessage); + } else if (!isMainThread) { + Slog.e(LOG_TAG, errorMessage); } final Display display = mDisplayManager.getDisplay(displayId); if (!isValidDisplay(display)) { @@ -5303,41 +5276,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mInputFilter.onDisplayAdded(display); } AccessibilityUserState userState = getCurrentUserStateLocked(); - if (Flags.addWindowTokenWithoutLock()) { - services = new ArrayList<>(userState.mBoundServices); - } else { - services = userState.mBoundServices; - if (displayId != Display.DEFAULT_DISPLAY) { - for (int i = 0; i < services.size(); i++) { - AccessibilityServiceConnection boundClient = services.get(i); - boundClient.addWindowTokenForDisplay(displayId); - } - } - } + services = new ArrayList<>(userState.mBoundServices); updateMagnificationLocked(userState); updateWindowsForAccessibilityCallbackLocked(userState); notifyClearAccessibilityCacheLocked(); } - if (Flags.addWindowTokenWithoutLock()) { - if (displayId != Display.DEFAULT_DISPLAY) { - for (int i = 0; i < services.size(); i++) { - AccessibilityServiceConnection boundClient = services.get(i); - boundClient.addWindowTokenForDisplay(displayId); - } + if (displayId != Display.DEFAULT_DISPLAY) { + for (int i = 0; i < services.size(); i++) { + AccessibilityServiceConnection boundClient = services.get(i); + boundClient.addWindowTokenForDisplay(displayId); } } } @Override public void onDisplayRemoved(int displayId) { - if (Flags.addWindowTokenWithoutLock()) { - final boolean isMainThread = Looper.getMainLooper().isCurrentThread(); - final String errorMessage = "onDisplayRemoved must be called from the main thread"; - if (Build.IS_USERDEBUG || Build.IS_ENG) { - Preconditions.checkArgument(isMainThread, errorMessage); - } else if (!isMainThread) { - Slog.e(LOG_TAG, errorMessage); - } + final boolean isMainThread = Looper.getMainLooper().isCurrentThread(); + final String errorMessage = "onDisplayRemoved must be called from the main thread"; + if (Build.IS_USERDEBUG || Build.IS_ENG) { + Preconditions.checkArgument(isMainThread, errorMessage); + } else if (!isMainThread) { + Slog.e(LOG_TAG, errorMessage); } synchronized (mLock) { if (!removeDisplayFromList(displayId)) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index b90a66a24442..fb2805574ff0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -220,7 +220,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public void onServiceConnected(ComponentName componentName, IBinder service) { AccessibilityUserState userState = mUserStateWeakReference.get(); - if (userState != null && Flags.addWindowTokenWithoutLock()) { + if (userState != null) { addWindowTokensForAllDisplays(); } synchronized (mLock) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 4b128f75f4d2..9a1d3793e447 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -243,9 +243,6 @@ class AccessibilityUserState { void addServiceLocked(AccessibilityServiceConnection serviceConnection) { if (!mBoundServices.contains(serviceConnection)) { - if (!Flags.addWindowTokenWithoutLock()) { - serviceConnection.addWindowTokensForAllDisplays(); - } mBoundServices.add(serviceConnection); mComponentNameToServiceMap.put(serviceConnection.getComponentName(), serviceConnection); mServiceInfoChangeListener.onServiceInfoChangedLocked(this); diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index f69104db7c10..aad9e24ee8cc 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -136,9 +136,6 @@ class UiAutomationManager { return; } - if (!Flags.addWindowTokenWithoutLock()) { - mUiAutomationService.addWindowTokensForAllDisplays(); - } // UiAutomationService#connectServiceUnknownThread posts to a handler // so this call should return immediately. mUiAutomationService.connectServiceUnknownThread(); @@ -286,9 +283,7 @@ class UiAutomationManager { // If the serviceInterface is null, the UiAutomation has been shut down on // another thread. if (serviceInterface != null) { - if (Flags.addWindowTokenWithoutLock()) { - mUiAutomationService.addWindowTokensForAllDisplays(); - } + mUiAutomationService.addWindowTokensForAllDisplays(); if (mTrace.isA11yTracingEnabledForTypes( AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { mTrace.logTrace("UiAutomationService.connectServiceUnknownThread", diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 3645c40aeda2..9c84b123a435 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -545,6 +545,25 @@ public final class PresentationStatsEventLogger { }); } + /** + * Set views_fillable_total_count as long as mEventInternal presents. + */ + public void maybeSetViewFillableCounts(int totalFillableCount) { + mEventInternal.ifPresent(event -> { + event.mViewFillableTotalCount = totalFillableCount; + }); + } + + /** + * Set views_filled_failure_count using failure count as long as mEventInternal + * presents. + */ + public void maybeSetViewFillFailureCounts(int failureCount) { + mEventInternal.ifPresent(event -> { + event.mViewFillFailureCount = failureCount; + }); + } + public void logAndEndEvent() { if (!mEventInternal.isPresent()) { Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same " @@ -587,7 +606,9 @@ public final class PresentationStatsEventLogger { + " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId + " mAppPackageUid=" + mCallingAppUid + " mIsCredentialRequest=" + event.mIsCredentialRequest - + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential); + + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential + + " mViewFillableTotalCount=" + event.mViewFillableTotalCount + + " mViewFillFailureCount=" + event.mViewFillFailureCount); } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. @@ -628,7 +649,9 @@ public final class PresentationStatsEventLogger { event.mFieldClassificationRequestId, mCallingAppUid, event.mIsCredentialRequest, - event.mWebviewRequestedCredential); + event.mWebviewRequestedCredential, + event.mViewFillableTotalCount, + event.mViewFillFailureCount); mEventInternal = Optional.empty(); } @@ -664,6 +687,8 @@ public final class PresentationStatsEventLogger { int mFieldClassificationRequestId = -1; boolean mIsCredentialRequest = false; boolean mWebviewRequestedCredential = false; + int mViewFillableTotalCount = -1; + int mViewFillFailureCount = -1; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index a55b8d008a68..d006cf6d703a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -170,8 +170,8 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import android.view.KeyEvent; -import android.view.autofill.AutofillId; import android.view.autofill.AutofillFeatureFlags; +import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.AutofillCommitReason; import android.view.autofill.AutofillManager.SmartSuggestionMode; @@ -597,6 +597,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private boolean mIgnoreViewStateResetToEmpty; + /* + * Id of the previous view that was entered. Once set, it would only be replaced by non-null + * view ids. + * When a user focuses on a field, autofill request is sent. When the keyboard pops up, or the + * autofill dialog shows up, this field loses focus. After selecting a suggestion, focus goes + * back to the same field. This field allows to ignore focus loss when autofill dialog comes up. + * TODO(b/319872477): Note that there maybe some cases where we incorrectly detect focus loss. + */ + @GuardedBy("mLock") + private @Nullable AutofillId mPreviousNonNullEnteredViewId; + void onSwitchInputMethodLocked() { // One caveat is that for the case where the focus is on a field for which regular autofill // returns null, and augmented autofill is triggered, and then the user switches the input @@ -4390,6 +4401,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState case ACTION_START_SESSION: // View is triggering autofill. mCurrentViewId = viewState.id; + mPreviousNonNullEnteredViewId = viewState.id; viewState.update(value, virtualBounds, flags); startNewEventForPresentationStatsEventLogger(); mPresentationStatsEventLogger.maybeSetIsNewRequest(true); @@ -4459,6 +4471,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (value != null) { viewState.setCurrentValue(value); } + // isSameViewEntered has some limitations, where it isn't considered same view when + // autofill suggestions pop up, user selects, and the focus lands back on the view. + // isSameViewAgain tries to overcome that situation. + final boolean isSameViewAgain = isSameViewEntered + || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId); + if (mCurrentViewId != null) { + mPreviousNonNullEnteredViewId = mCurrentViewId; + } boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0; if (shouldRequestSecondaryProvider(flags)) { if (requestNewFillResponseOnViewEnteredIfNecessaryLocked( @@ -4510,7 +4530,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // With Fill Dialog, request starts prior to view getting entered. So, we can't end // the event at this moment, otherwise we will be wrongly attributing fill dialog // event as concluded. - if (!wasPreviouslyFillDialog) { + if (!wasPreviouslyFillDialog && !isSameViewAgain) { + // TODO(b/319872477): Re-consider this logic below mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); mPresentationStatsEventLogger.logAndEndEvent(); @@ -4588,10 +4609,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mCurrentViewId = null; } - + // It's not necessary that there's no more presentation for this view. It could + // be that the user chose some suggestion, in which case, view exits. mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); - mPresentationStatsEventLogger.logAndEndEvent(); } break; default: @@ -5327,6 +5348,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ @GuardedBy("mLock") void setAutofillFailureLocked(@NonNull List<AutofillId> ids) { + if (sVerbose && !ids.isEmpty()) { + Slog.v(TAG, "Total views that failed to populate: " + ids.size()); + } for (int i = 0; i < ids.size(); i++) { final AutofillId id = ids.get(i); final ViewState viewState = mViewStates.get(id); @@ -5341,6 +5365,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.v(TAG, "Changed state of " + id + " to " + viewState.getStateAsString()); } } + mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size()); + mPresentationStatsEventLogger.logAndEndEvent(); } @GuardedBy("mLock") @@ -6526,8 +6552,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (waitingDatasetAuth) { mUi.hideFillUi(this); } + if (sVerbose) { + Slog.v(TAG, "Total views to be autofilled: " + ids.size()); + } + mPresentationStatsEventLogger.maybeSetViewFillableCounts(ids.size()); if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); - mClient.autofill(id, ids, values, hideHighlight); if (dataset.getId() != null) { if (mSelectedDatasetIds == null) { diff --git a/services/contextualsearch/Android.bp b/services/contextualsearch/Android.bp new file mode 100644 index 000000000000..b4dd20ec5b7c --- /dev/null +++ b/services/contextualsearch/Android.bp @@ -0,0 +1,22 @@ +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.contextualsearch-sources", + srcs: ["java/**/*.java"], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.contextualsearch", + defaults: ["platform_service_defaults"], + srcs: [":services.contextualsearch-sources"], + libs: ["services.core"], +} diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java new file mode 100644 index 000000000000..b28bc1ffb611 --- /dev/null +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2024 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.contextualsearch; + +import static android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH; +import static android.content.Context.CONTEXTUAL_SEARCH_SERVICE; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; +import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; +import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.app.ActivityOptions; +import android.app.admin.DevicePolicyManagerInternal; +import android.app.contextualsearch.ContextualSearchManager; +import android.app.contextualsearch.ContextualSearchState; +import android.app.contextualsearch.IContextualSearchCallback; +import android.app.contextualsearch.IContextualSearchManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelableException; +import android.os.Process; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.util.Log; +import android.util.Slog; +import android.window.ScreenCapture; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.wm.ActivityAssistInfo; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import java.io.FileDescriptor; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class ContextualSearchManagerService extends SystemService { + + private static final int MSG_RESET_TEMPORARY_PACKAGE = 0; + private static final int MAX_TEMP_PACKAGE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes + private final Context mContext; + private final ActivityTaskManagerInternal mAtmInternal; + private final WindowManagerInternal mWmInternal; + private final DevicePolicyManagerInternal mDpmInternal; + + private Handler mTemporaryHandler; + + @GuardedBy("this") + private String mTemporaryPackage = null; + private static final String TAG = ContextualSearchManagerService.class.getSimpleName(); + + public ContextualSearchManagerService(@NonNull Context context) { + super(context); + if (DEBUG_USER) Log.d(TAG, "ContextualSearchManagerService created"); + mContext = context; + mAtmInternal = Objects.requireNonNull( + LocalServices.getService(ActivityTaskManagerInternal.class)); + mWmInternal = Objects.requireNonNull(LocalServices.getService(WindowManagerInternal.class)); + mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); + } + + @Override + public void onStart() { + publishBinderService(CONTEXTUAL_SEARCH_SERVICE, new ContextualSearchManagerStub()); + } + + void resetTemporaryPackage() { + synchronized (this) { + enforceOverridingPermission("resetTemporaryPackage"); + if (mTemporaryHandler != null) { + mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE); + mTemporaryHandler = null; + } + if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage reset."); + mTemporaryPackage = null; + } + } + + void setTemporaryPackage(@NonNull String temporaryPackage, int durationMs) { + synchronized (this) { + enforceOverridingPermission("setTemporaryPackage"); + final int maxDurationMs = MAX_TEMP_PACKAGE_DURATION_MS; + if (durationMs > maxDurationMs) { + throw new IllegalArgumentException( + "Max duration is " + maxDurationMs + " (called with " + durationMs + ")"); + } + if (mTemporaryHandler == null) { + mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_RESET_TEMPORARY_PACKAGE) { + synchronized (this) { + resetTemporaryPackage(); + } + } else { + Slog.wtf(TAG, "invalid handler msg: " + msg); + } + } + }; + } else { + mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE); + } + mTemporaryPackage = temporaryPackage; + mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_PACKAGE, durationMs); + if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage); + } + } + + private Intent getResolvedLaunchIntent() { + synchronized (this) { + // If mTemporaryPackage is not null, use it to get the ContextualSearch intent. + String csPkgName = mTemporaryPackage != null ? mTemporaryPackage : mContext + .getResources().getString(R.string.config_defaultContextualSearchPackageName); + if (csPkgName.isEmpty()) { + // Return null if csPackageName is not specified. + return null; + } + Intent launchIntent = new Intent( + ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH); + launchIntent.setPackage(csPkgName); + ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity( + launchIntent, MATCH_FACTORY_ONLY); + if (resolveInfo == null) { + return null; + } + ComponentName componentName = resolveInfo.getComponentInfo().getComponentName(); + if (componentName == null) { + return null; + } + launchIntent.setComponent(componentName); + return launchIntent; + } + } + + private Intent getContextualSearchIntent(int entrypoint, IBinder mToken) { + final Intent launchIntent = getResolvedLaunchIntent(); + if (launchIntent == null) { + return null; + } + + if (DEBUG_USER) Log.d(TAG, "Launch component: " + launchIntent.getComponent()); + launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION + | FLAG_ACTIVITY_NO_USER_ACTION); + launchIntent.putExtra(ContextualSearchManager.EXTRA_ENTRYPOINT, entrypoint); + launchIntent.putExtra(ContextualSearchManager.EXTRA_TOKEN, mToken); + boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed(); + final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities(); + ArrayList<String> visiblePackageNames = new ArrayList<>(); + boolean isManagedProfileVisible = false; + for (ActivityAssistInfo record : records) { + // Add the package name to the list only if assist data is allowed. + if (isAssistDataAllowed) { + visiblePackageNames.add(record.getComponentName().getPackageName()); + } + if (mDpmInternal != null + && mDpmInternal.isUserOrganizationManaged(record.getUserId())) { + isManagedProfileVisible = true; + } + } + final ScreenCapture.ScreenshotHardwareBuffer shb; + if (mWmInternal != null) { + shb = mWmInternal.takeAssistScreenshot(); + } else { + shb = null; + } + final Bitmap bm = shb != null ? shb.asBitmap() : null; + // Now that everything is fetched, putting it in the launchIntent. + if (bm != null) { + launchIntent.putExtra(ContextualSearchManager.EXTRA_FLAG_SECURE_FOUND, + shb.containsSecureLayers()); + // Only put the screenshot if assist data is allowed + if (isAssistDataAllowed) { + launchIntent.putExtra(ContextualSearchManager.EXTRA_SCREENSHOT, bm.asShared()); + } + } + launchIntent.putExtra(ContextualSearchManager.EXTRA_IS_MANAGED_PROFILE_VISIBLE, + isManagedProfileVisible); + // Only put the list of visible package names if assist data is allowed + if (isAssistDataAllowed) { + launchIntent.putExtra(ContextualSearchManager.EXTRA_VISIBLE_PACKAGE_NAMES, + visiblePackageNames); + } + return launchIntent; + } + + @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) + private int invokeContextualSearchIntent(Intent launchIntent) { + // Contextual search starts with a frozen screen - so we launch without + // any system animations or starting window. + final ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(mContext, + /* enterResId= */ 0, /* exitResId= */ 0, null, null, null); + opts.setDisableStartingWindow(true); + return mAtmInternal.startActivityWithScreenshot(launchIntent, + mContext.getPackageName(), Binder.getCallingUid(), Binder.getCallingPid(), null, + opts.toBundle(), Binder.getCallingUserHandle().getIdentifier()); + } + + private void enforcePermission(@NonNull final String func) { + Context ctx = getContext(); + if (!(ctx.checkCallingPermission(ACCESS_CONTEXTUAL_SEARCH) == PERMISSION_GRANTED + || isCallerTemporary())) { + String msg = "Permission Denial: Cannot call " + func + " from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid(); + throw new SecurityException(msg); + } + } + + private void enforceOverridingPermission(@NonNull final String func) { + if (!(Binder.getCallingUid() == Process.SHELL_UID + || Binder.getCallingUid() == Process.ROOT_UID + || Binder.getCallingUid() == Process.SYSTEM_UID)) { + String msg = "Permission Denial: Cannot override Contextual Search. Called " + func + + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid(); + throw new SecurityException(msg); + } + } + + private boolean isCallerTemporary() { + synchronized (this) { + return mTemporaryPackage != null + && mTemporaryPackage.equals( + getContext().getPackageManager().getNameForUid(Binder.getCallingUid())); + } + } + + private class ContextualSearchManagerStub extends IContextualSearchManager.Stub { + private @Nullable IBinder mToken; + + @Override + public void startContextualSearch(int entrypoint) { + synchronized (this) { + if (DEBUG_USER) Log.d(TAG, "startContextualSearch"); + enforcePermission("startContextualSearch"); + mToken = new Binder(); + // We get the launch intent with the system server's identity because the system + // server has READ_FRAME_BUFFER permission to get the screenshot and because only + // the system server can invoke non-exported activities. + Binder.withCleanCallingIdentity(() -> { + Intent launchIntent = getContextualSearchIntent(entrypoint, mToken); + if (launchIntent != null) { + int result = invokeContextualSearchIntent(launchIntent); + if (DEBUG_USER) Log.d(TAG, "Launch result: " + result); + } + }); + } + } + + @Override + public void getContextualSearchState( + @NonNull IBinder token, + @NonNull IContextualSearchCallback callback) { + if (DEBUG_USER) { + Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback); + } + if (mToken == null || !mToken.equals(token)) { + if (DEBUG_USER) { + Log.e(TAG, "getContextualSearchState: invalid token, returning error"); + } + try { + callback.onError( + new ParcelableException(new IllegalArgumentException("Invalid token"))); + } catch (RemoteException e) { + Log.e(TAG, "Could not invoke onError callback", e); + } + return; + } + mToken = null; + // Process data request + try { + callback.onResult(new ContextualSearchState(null, null, Bundle.EMPTY)); + } catch (RemoteException e) { + Log.e(TAG, "Could not invoke onResult callback", e); + } + } + + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, @NonNull String[] args, + @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) { + new ContextualSearchManagerShellCommand(ContextualSearchManagerService.this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + } +} diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.java new file mode 100644 index 000000000000..5777e1d154de --- /dev/null +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerShellCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 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.contextualsearch; + +import android.annotation.NonNull; +import android.os.ShellCommand; + +import java.io.PrintWriter; + +public class ContextualSearchManagerShellCommand extends ShellCommand { + + private final ContextualSearchManagerService mService; + + ContextualSearchManagerShellCommand(@NonNull ContextualSearchManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "set": { + final String what = getNextArgRequired(); + switch (what) { + case "temporary-package": { + String packageName = getNextArg(); + if (packageName == null) { + mService.resetTemporaryPackage(); + pw.println("ContextualSearchManagerService reset."); + return 0; + } + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryPackage(packageName, duration); + pw.println("ContextualSearchManagerService temporarily set to " + + packageName + " for " + duration + "ms"); + break; + } + } + } + break; + default: + return handleDefaultCommands(cmd); + } + return 0; + } + + @Override + public void onHelp() { + try (PrintWriter pw = getOutPrintWriter()) { + pw.println("ContextualSearchService commands:"); + pw.println(" help"); + pw.println(" Prints this help text."); + pw.println(""); + pw.println(" set temporary-package [PACKAGE_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the Contextual Search " + + "implementation."); + pw.println(" To reset, call without any arguments."); + pw.println(""); + } + } +} diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0012b3d86552..133a77df3573 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -4658,6 +4658,8 @@ public final class ActiveServices { INVALID_UID /* sdkSandboxClientAppUid */, null /* sdkSandboxClientAppPackage */, false /* inSharedIsolatedProcess */); + r.foregroundId = fgsDelegateOptions.mClientNotificationId; + r.foregroundNoti = fgsDelegateOptions.mClientNotification; res.setService(r); smap.mServicesByInstanceName.put(cn, r); smap.mServicesByIntent.put(filter, r); @@ -8171,7 +8173,7 @@ public final class ActiveServices { * @param targetProcess the process of the service to start. * @return {@link ReasonCode} */ - private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, + @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, int callingPid, int callingUid, @Nullable ProcessRecord targetProcess, BackgroundStartPrivileges backgroundStartPrivileges) { int ret = REASON_DENIED; @@ -9046,6 +9048,10 @@ public final class ActiveServices { }); } signalForegroundServiceObserversLocked(r); + if (r.foregroundId != 0 && r.foregroundNoti != null) { + r.foregroundNoti.flags |= Notification.FLAG_FOREGROUND_SERVICE; + r.postNotification(true); + } return true; } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index ff837974bf3c..272e84b80870 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -51,6 +51,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Slog; +import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -436,6 +437,18 @@ final class ActivityManagerConstants extends ContentObserver { private static final String KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS = "max_service_connections_per_process"; + private static final String KEY_PROC_STATE_DEBUG_UIDS = "proc_state_debug_uids"; + + /** + * UIDs we want to print detailed info in OomAdjuster. + * It's only used for debugging, and it's almost never updated, so we just create a new + * array when it's changed to avoid synchronization. + */ + volatile SparseBooleanArray mProcStateDebugUids = new SparseBooleanArray(0); + volatile boolean mEnableProcStateStacktrace = false; + volatile int mProcStateDebugSetProcStateDelay = 0; + volatile int mProcStateDebugSetUidStateDelay = 0; + // Maximum number of cached processes we will allow. public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; @@ -1339,6 +1352,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_PSS_TO_RSS_THRESHOLD_MODIFIER: updatePssToRssThresholdModifier(); break; + case KEY_PROC_STATE_DEBUG_UIDS: + updateProcStateDebugUids(); + break; default: updateFGSPermissionEnforcementFlagsIfNecessary(name); break; @@ -2039,6 +2055,76 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_MAX_PREVIOUS_TIME); } + private void updateProcStateDebugUids() { + final String val = DeviceConfig.getString( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_PROC_STATE_DEBUG_UIDS, + "").trim(); + + // Parse KEY_PROC_STATE_DEBUG_UIDS as comma-separated values. Each values can be: + // Number: Enable debugging on the given UID. + // "stack": Enable stack trace when updating proc/uid-states.s + // "u" + delay-ms: Enable sleep when updating uid-state + // "p" + delay-ms: Enable sleep when updating procstate + // + // Example: + // device_config put activity_manager proc_state_debug_uids '10177,10202,stack,p500,u100' + // means: + // - Monitor UID 10177 and 10202 + // - Also enable stack trace + // - Sleep 500 ms when updating the procstate. + // - Sleep 100 ms when updating the UID state. + + mEnableProcStateStacktrace = false; + mProcStateDebugSetProcStateDelay = 0; + mProcStateDebugSetUidStateDelay = 0; + if (val.length() == 0) { + mProcStateDebugUids = new SparseBooleanArray(0); + return; + } + final String[] uids = val.split(","); + + final SparseBooleanArray newArray = new SparseBooleanArray(0); + + for (String token : uids) { + if (token.length() == 0) { + continue; + } + // "stack" -> enable stacktrace. + if ("stack".equals(token)) { + mEnableProcStateStacktrace = true; + continue; + } + boolean isUid = true; + char prefix = token.charAt(0); + if ('a' <= prefix && prefix <= 'z') { + // If the token starts with an alphabet, it's not a UID. + isUid = false; + token = token.substring(1); + } + + int value = -1; + try { + value = Integer.parseInt(token.trim()); + } catch (NumberFormatException e) { + Slog.w(TAG, "Invalid number " + token + " in " + val); + continue; + } + if (isUid) { + newArray.put(value, true); + } else if (prefix == 'p') { + // Enable delay in set-proc-state + mProcStateDebugSetProcStateDelay = value; + } else if (prefix == 'u') { + // Enable delay in set-uid-state + mProcStateDebugSetUidStateDelay = value; + } else { + Slog.w(TAG, "Invalid prefix " + prefix + " in " + val); + } + } + mProcStateDebugUids = newArray; + } + private void updateMinAssocLogDuration() { MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION, @@ -2178,6 +2264,28 @@ final class ActivityManagerConstants extends ContentObserver { mDefaultPssToRssThresholdModifier); } + boolean shouldDebugUidForProcState(int uid) { + SparseBooleanArray ar = mProcStateDebugUids; + final var size = ar.size(); + if (size == 0) { // Most common case. + return false; + } + // If the array is small (also common), avoid the binary search. + if (size <= 8) { + for (int i = 0; i < size; i++) { + if (ar.keyAt(i) == uid) { + return ar.valueAt(i); + } + } + return false; + } + return ar.get(uid, false); + } + + boolean shouldEnableProcStateDebug() { + return mProcStateDebugUids.size() > 0; + } + @NeverCompile // Avoid size overhead of debugging code. void dump(PrintWriter pw) { pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) " @@ -2393,5 +2501,12 @@ final class ActivityManagerConstants extends ContentObserver { pw.print(" OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK); pw.print(" ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION="); pw.println(mEnableWaitForFinishAttachApplication); + + synchronized (mProcStateDebugUids) { + pw.print(" "); pw.print(KEY_PROC_STATE_DEBUG_UIDS); + pw.print("="); pw.println(mProcStateDebugUids); + pw.print(" uid-state-delay="); pw.println(mProcStateDebugSetUidStateDelay); + pw.print(" proc-state-delay="); pw.println(mProcStateDebugSetProcStateDelay); + } } } diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 3e633ccf798c..ddf1d5f5ab71 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -211,11 +211,17 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - if (!mInProgRecords.containsKey(id)) { + int index = mInProgRecords.indexOfKey(id); + if (index < 0) { return; } - mInProgRecords.get(id).setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR); - mInProgRecords.remove(id); + ApplicationStartInfo info = mInProgRecords.valueAt(index); + if (info == null) { + mInProgRecords.removeAt(index); + return; + } + info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR); + mInProgRecords.removeAt(index); } } @@ -224,16 +230,24 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - if (!mInProgRecords.containsKey(id)) { + int index = mInProgRecords.indexOfKey(id); + if (index < 0) { return; } - if (app != null) { - ApplicationStartInfo info = mInProgRecords.get(id); - info.setStartType((int) temperature); - addBaseFieldsFromProcessRecord(info, app); - mInProgRecords.put(id, addStartInfoLocked(info)); + ApplicationStartInfo info = mInProgRecords.valueAt(index); + if (info == null || app == null) { + mInProgRecords.removeAt(index); + return; + } + info.setStartType((int) temperature); + addBaseFieldsFromProcessRecord(info, app); + ApplicationStartInfo newInfo = addStartInfoLocked(info); + if (newInfo == null) { + // newInfo can be null if records are added before load from storage is + // complete. In this case the newly added record will be lost. + mInProgRecords.removeAt(index); } else { - mInProgRecords.remove(id); + mInProgRecords.setValueAt(index, newInfo); } } } @@ -243,12 +257,17 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - if (!mInProgRecords.containsKey(id)) { + int index = mInProgRecords.indexOfKey(id); + if (index < 0) { + return; + } + ApplicationStartInfo info = mInProgRecords.valueAt(index); + if (info == null) { + mInProgRecords.removeAt(index); return; } - ApplicationStartInfo info = mInProgRecords.get(id); info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR); - mInProgRecords.remove(id); + mInProgRecords.removeAt(index); } } @@ -258,10 +277,15 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - if (!mInProgRecords.containsKey(id)) { + int index = mInProgRecords.indexOfKey(id); + if (index < 0) { + return; + } + ApplicationStartInfo info = mInProgRecords.valueAt(index); + if (info == null) { + mInProgRecords.removeAt(index); return; } - ApplicationStartInfo info = mInProgRecords.get(id); info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); info.setLaunchMode(launchMode); checkCompletenessAndCallback(info); @@ -273,13 +297,18 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } - if (!mInProgRecords.containsKey(id)) { + int index = mInProgRecords.indexOfKey(id); + if (index < 0) { + return; + } + ApplicationStartInfo info = mInProgRecords.valueAt(index); + if (info == null) { + mInProgRecords.removeAt(index); return; } - ApplicationStartInfo info = mInProgRecords.get(id); info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN, timestampNanos); - mInProgRecords.remove(id); + mInProgRecords.removeAt(index); } } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 0a6e9d3198cb..5521381e8908 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -286,6 +286,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // when the flag is fused on. private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8; + // TODO: Use the trunk stable flag. + private static final boolean DEFER_FROZEN_OUTGOING_BCASTS = false; + private void enqueueUpdateRunningList() { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST); @@ -763,7 +766,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the // outgoing broadcasts. // TODO: Add traces/logs for the enqueueing outgoing broadcasts logic. - if (Flags.deferOutgoingBcasts() && isProcessFreezable(r.callerApp)) { + if (DEFER_FROZEN_OUTGOING_BCASTS && isProcessFreezable(r.callerApp)) { final BroadcastProcessQueue queue = getOrCreateProcessQueue( r.callerApp.processName, r.callerApp.uid); if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) { diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 80387322b038..b142781418e8 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -135,3 +135,9 @@ option java_package com.android.server.am # Caller information to clear application data 30120 am_clear_app_data_caller (pid|1),(uid|1),(package|3) + +30111 am_uid_state_changed (UID|1|5),(Seq|1|5),(UidState|1|5),(OldUidState|1|5),(Capability|1|5),(OldCapability|1|5),(Flags|1|5),(reason|3) +30112 am_proc_state_changed (UID|1|5),(PID|1|5),(Seq|1|5),(ProcState|1|5),(OldProcState|1|5),(OomAdj|1|5),(OldOomAdj|1|5),(reason|3) + +# "Misc" events. See OomAdjusterDebugLogger.java +30113 am_oom_adj_misc (Event|1|5),(UID|1|5),(PID|1|5),(Seq|1|5),(Arg1|1|5),(Arg2|1|5),(reason|3) diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 7f6d62c29648..1a7629f3182d 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -401,6 +401,14 @@ public class OomAdjuster { @GuardedBy("mService") private boolean mPendingFullOomAdjUpdate = false; + /** + * Most recent reason string. We update it in sync with the trace. + */ + @OomAdjReason + protected int mLastReason; + + private final OomAdjusterDebugLogger mLogger; + /** Overrideable by a test */ @VisibleForTesting protected boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, @@ -433,6 +441,8 @@ public class OomAdjuster { mCachedAppOptimizer = new CachedAppOptimizer(mService); mCacheOomRanker = new CacheOomRanker(service); + mLogger = new OomAdjusterDebugLogger(this, mService.mConstants); + mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> { final int pid = msg.arg1; final int group = msg.arg2; @@ -636,6 +646,7 @@ public class OomAdjuster { protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { final ProcessRecord topApp = mService.getTopApp(); + mLastReason = oomAdjReason; Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); mService.mOomAdjProfiler.oomAdjStarted(); mAdjSeq++; @@ -916,6 +927,7 @@ public class OomAdjuster { protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) { final ProcessRecord topApp = mService.getTopApp(); + mLastReason = oomAdjReason; Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); mService.mOomAdjProfiler.oomAdjStarted(); @@ -958,6 +970,7 @@ public class OomAdjuster { } } + mLastReason = oomAdjReason; if (startProfiling) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); mService.mOomAdjProfiler.oomAdjStarted(); @@ -1491,6 +1504,7 @@ public class OomAdjuster { || uidRec.isSetAllowListed() != uidRec.isCurAllowListed() || uidRec.getProcAdjChanged()) { int uidChange = 0; + final boolean shouldLog = mLogger.shouldLog(uidRec.getUid()); if (DEBUG_UID_OBSERVERS) { Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec + ": proc state from " + uidRec.getSetProcState() + " to " @@ -1511,14 +1525,21 @@ public class OomAdjuster { || uidRec.isSetAllowListed() || uidRec.getLastBackgroundTime() == 0) { uidRec.setLastBackgroundTime(nowElapsed); + if (shouldLog) { + mLogger.logSetLastBackgroundTime(uidRec.getUid(), nowElapsed); + } if (mService.mDeterministicUidIdle || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) { // Note: the background settle time is in elapsed realtime, while // the handler time base is uptime. All this means is that we may // stop background uids later than we had intended, but that only // happens because the device was sleeping so we are okay anyway. + if (shouldLog) { + mLogger.logScheduleUidIdle1(uidRec.getUid(), + mConstants.BACKGROUND_SETTLE_TIME); + } mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, - mConstants.BACKGROUND_SETTLE_TIME); + mConstants.BACKGROUND_SETTLE_TIME); // XXX } } if (uidRec.isIdle() && !uidRec.isSetIdle()) { @@ -1536,6 +1557,9 @@ public class OomAdjuster { } uidRec.setLastBackgroundTime(0); uidRec.setLastIdleTime(0); + if (shouldLog) { + mLogger.logClearLastBackgroundTime(uidRec.getUid()); + } } final boolean wasCached = uidRec.getSetProcState() > ActivityManager.PROCESS_STATE_RECEIVER; @@ -1555,11 +1579,25 @@ public class OomAdjuster { if (uidRec.getProcAdjChanged()) { uidChange |= UidRecord.CHANGE_PROCADJ; } + int oldProcState = uidRec.getSetProcState(); + int oldCapability = uidRec.getSetCapability(); uidRec.setSetProcState(uidRec.getCurProcState()); uidRec.setSetCapability(uidRec.getCurCapability()); uidRec.setSetAllowListed(uidRec.isCurAllowListed()); uidRec.setSetIdle(uidRec.isIdle()); uidRec.clearProcAdjChanged(); + if (shouldLog + && ((uidRec.getSetProcState() != oldProcState) + || (uidRec.getSetCapability() != oldCapability))) { + int flags = 0; + if (uidRec.isSetAllowListed()) { + flags |= 1; + } + mLogger.logUidStateChanged(uidRec.getUid(), + uidRec.getSetProcState(), oldProcState, + uidRec.getSetCapability(), oldCapability, + flags); + } if ((uidChange & UidRecord.CHANGE_PROCSTATE) != 0 || (uidChange & UidRecord.CHANGE_CAPABILITY) != 0) { mService.mAtmInternal.onUidProcStateChanged( @@ -3300,6 +3338,7 @@ public class OomAdjuster { mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app); } + final int oldOomAdj = state.getSetAdj(); if (state.getCurAdj() != state.getSetAdj()) { ProcessList.setOomAdj(app.getPid(), app.uid, state.getCurAdj()); if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) { @@ -3457,6 +3496,7 @@ public class OomAdjuster { mService.mAppProfiler.updateNextPssTimeLPf( state.getCurProcState(), app.mProfile, now, forceUpdatePssTime); } + int oldProcState = state.getSetProcState(); if (state.getSetProcState() != state.getCurProcState()) { if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) { String msg = "Proc state change of " + app.processName @@ -3556,6 +3596,11 @@ public class OomAdjuster { // Kick off the delayed checkup message if needed. if (mService.mDeterministicUidIdle || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) { + if (mLogger.shouldLog(app.uid)) { + mLogger.logScheduleUidIdle2( + uidRec.getUid(), app.getPid(), + mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs); + } mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, mConstants.mKillBgRestrictedAndCachedIdleSettleTimeMs); } @@ -3563,6 +3608,12 @@ public class OomAdjuster { } state.setSetCached(state.isCached()); state.setSetNoKillOnBgRestrictedAndIdle(state.shouldNotKillOnBgRestrictedAndIdle()); + if (((oldProcState != state.getSetProcState()) || (oldOomAdj != state.getSetAdj())) + && mLogger.shouldLog(app.uid)) { + mLogger.logProcStateChanged(app.uid, app.getPid(), + state.getSetProcState(), oldProcState, + state.getSetAdj(), oldOomAdj); + } return success; } @@ -3704,6 +3755,7 @@ public class OomAdjuster { if (mService.mLocalPowerManager != null) { mService.mLocalPowerManager.startUidChanges(); } + boolean shouldLogMisc = false; for (int i = N - 1; i >= 0; i--) { final UidRecord uidRec = mActiveUids.valueAt(i); final long bgTime = uidRec.getLastBackgroundTime(); @@ -3721,6 +3773,9 @@ public class OomAdjuster { if (nextTime == 0 || nextTime > bgTime) { nextTime = bgTime; } + if (mLogger.shouldLog(uidRec.getUid())) { + shouldLogMisc = true; + } } } } @@ -3742,8 +3797,11 @@ public class OomAdjuster { } } if (nextTime > 0) { - mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, - nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed); + long delay = nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed; + if (shouldLogMisc) { + mLogger.logScheduleUidIdle3(delay); + } + mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG, delay); } } diff --git a/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java b/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java new file mode 100644 index 000000000000..1294a4d25c44 --- /dev/null +++ b/services/core/java/com/android/server/am/OomAdjusterDebugLogger.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 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.am; + +import android.app.StackTrace; +import android.util.Slog; + +/** + * Helper for writing debug log about proc/uid state changes. + */ +class OomAdjusterDebugLogger { + // Use the "am_" tag to make it similar to event logs. + private static final String STACK_TRACE_TAG = "am_stack"; + + private final OomAdjuster mOomAdjuster; + private final ActivityManagerConstants mConstants; + + private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_1 = 1; + private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_2 = 2; + private static final int MISC_SCHEDULE_IDLE_UIDS_MSG_3 = 3; + + private static final int MISC_SET_LAST_BG_TIME = 10; + private static final int MISC_CLEAR_LAST_BG_TIME = 11; + + OomAdjusterDebugLogger(OomAdjuster oomAdjuster, ActivityManagerConstants constants) { + mOomAdjuster = oomAdjuster; + mConstants = constants; + } + + boolean shouldLog(int uid) { + return mConstants.shouldDebugUidForProcState(uid); + } + + private void maybeLogStacktrace(String msg) { + if (!mConstants.mEnableProcStateStacktrace) { + return; + } + Slog.i(STACK_TRACE_TAG, + msg + ": " + OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason), + new StackTrace("Called here")); + } + + private void maybeSleep(int millis) { + if (millis == 0) { + return; + } + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + } + } + + void logUidStateChanged(int uid, int uidstate, int olduidstate, + int capability, int oldcapability, int flags) { + EventLogTags.writeAmUidStateChanged( + uid, mOomAdjuster.mAdjSeq, uidstate, olduidstate, capability, oldcapability, flags, + OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason)); + maybeLogStacktrace("uidStateChanged"); + maybeSleep(mConstants.mProcStateDebugSetUidStateDelay); + } + + void logProcStateChanged(int uid, int pid, int procstate, int oldprocstate, + int oomadj, int oldoomadj) { + EventLogTags.writeAmProcStateChanged( + uid, pid, mOomAdjuster.mAdjSeq, procstate, oldprocstate, oomadj, oldoomadj, + OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason)); + maybeLogStacktrace("procStateChanged"); + maybeSleep(mConstants.mProcStateDebugSetProcStateDelay); + } + + void logScheduleUidIdle1(int uid, long delay) { + EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_1, + uid, 0, mOomAdjuster.mAdjSeq, (int) delay, 0, ""); + } + + void logScheduleUidIdle2(int uid, int pid, long delay) { + EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_2, + uid, pid, mOomAdjuster.mAdjSeq, (int) delay, 0, ""); + } + + void logScheduleUidIdle3(long delay) { + EventLogTags.writeAmOomAdjMisc(MISC_SCHEDULE_IDLE_UIDS_MSG_3, + 0, 0, mOomAdjuster.mAdjSeq, (int) delay, 0, ""); + } + + void logSetLastBackgroundTime(int uid, long time) { + EventLogTags.writeAmOomAdjMisc(MISC_SET_LAST_BG_TIME, + uid, 0, mOomAdjuster.mAdjSeq, (int) time, 0, + OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason)); + } + + void logClearLastBackgroundTime(int uid) { + EventLogTags.writeAmOomAdjMisc(MISC_CLEAR_LAST_BG_TIME, + uid, 0, mOomAdjuster.mAdjSeq, 0, 0, + OomAdjuster.oomAdjReasonToString(mOomAdjuster.mLastReason)); + } +} diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 46bdfe892040..5feac1ff92cb 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -741,6 +741,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { mPendingProcessSet.clear(); mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false; + mLastReason = oomAdjReason; Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); mService.mOomAdjProfiler.oomAdjStarted(); @@ -761,6 +762,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { @GuardedBy("mService") @Override protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) { + mLastReason = oomAdjReason; Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); mService.mOomAdjProfiler.oomAdjStarted(); diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index b1823b4ee38f..e955b00566b8 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -60,10 +60,3 @@ flag { purpose: PURPOSE_BUGFIX } } - -flag { - namespace: "backstage_power" - name: "defer_outgoing_bcasts" - description: "Defer outgoing broadcasts from processes in freezable state" - bug: "327496592" -} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index d4f04b5ad760..e8c05c6d9899 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -7124,7 +7124,8 @@ public class AudioService extends IAudioService.Stub switch (mPlatformType) { case AudioSystem.PLATFORM_VOICE: - if (isInCommunication()) { + if (isInCommunication() + || mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) { if (mDeviceBroker.isBluetoothScoActive()) { // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO..."); return AudioSystem.STREAM_BLUETOOTH_SCO; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 177c345686b9..e2ae3def0b1b 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -76,7 +76,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -397,11 +396,10 @@ public final class DeviceStateManagerService extends SystemService { @NonNull private DeviceStateInfo getDeviceStateInfoLocked() { final List<DeviceState> supportedStates = getSupportedStatesLocked(); - final DeviceState baseState = mBaseState.orElse(null); - final DeviceState currentState = mCommittedState.orElse(null); + final DeviceState baseState = mBaseState.orElse(INVALID_DEVICE_STATE); + final DeviceState currentState = mCommittedState.orElse(INVALID_DEVICE_STATE); - return new DeviceStateInfo(supportedStates, - baseState != null ? baseState : INVALID_DEVICE_STATE, + return new DeviceStateInfo(supportedStates, baseState, createMergedDeviceState(currentState, baseState)); } @@ -412,7 +410,7 @@ public final class DeviceStateManagerService extends SystemService { */ private DeviceState createMergedDeviceState(@Nullable DeviceState committedState, @Nullable DeviceState baseState) { - if (committedState == null) { + if (committedState.equals(INVALID_DEVICE_STATE)) { return INVALID_DEVICE_STATE; } @@ -420,8 +418,7 @@ public final class DeviceStateManagerService extends SystemService { committedState.getConfiguration().getSystemProperties(); Set<@DeviceState.DeviceStateProperties Integer> physicalProperties = - baseState != null ? baseState.getConfiguration().getPhysicalProperties() - : Collections.emptySet(); + baseState.getConfiguration().getPhysicalProperties(); DeviceState.Configuration deviceStateConfiguration = new DeviceState.Configuration.Builder( committedState.getIdentifier(), committedState.getName()) diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index a7748f4fae98..0ebb2a30685c 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -158,8 +158,6 @@ abstract class DisplayDevice { @Nullable public Point getDisplaySurfaceDefaultSizeLocked() { DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked(); - final boolean isRotated = mCurrentOrientation == ROTATION_90 - || mCurrentOrientation == ROTATION_270; var width = displayDeviceInfo.width; var height = displayDeviceInfo.height; if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0 @@ -170,7 +168,7 @@ abstract class DisplayDevice { width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi + 0.5); } } - return isRotated ? new Point(height, width) : new Point(width, height); + return isRotatedLocked() ? new Point(height, width) : new Point(width, height); } /** @@ -394,8 +392,7 @@ abstract class DisplayDevice { viewport.physicalFrame.setEmpty(); } - boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90 - || mCurrentOrientation == ROTATION_270); + final boolean isRotated = isRotatedLocked(); DisplayDeviceInfo info = getDisplayDeviceInfoLocked(); viewport.deviceWidth = isRotated ? info.height : info.width; viewport.deviceHeight = isRotated ? info.width : info.height; @@ -425,6 +422,13 @@ abstract class DisplayDevice { pw.println("mCurrentSurface=" + mCurrentSurface); } + /** + * @return whether the orientation is {@link ROTATION_90} or {@link ROTATION_270}. + */ + boolean isRotatedLocked() { + return mCurrentOrientation == ROTATION_90 || mCurrentOrientation == ROTATION_270; + } + private DisplayDeviceConfig loadDisplayDeviceConfig() { return DisplayDeviceConfig.create(mContext, /* useConfigXml= */ false, mDisplayAdapter.getFeatureFlags()); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 9b2dcc53f456..411666942b6d 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -57,6 +57,9 @@ import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HighBrightnessMode; +import com.android.server.display.config.IdleScreenRefreshRateTimeout; +import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; +import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds; import com.android.server.display.config.IntegerArray; import com.android.server.display.config.LuxThrottling; import com.android.server.display.config.NitsMap; @@ -553,6 +556,18 @@ import javax.xml.datatype.DatatypeConfigurationException; * <minorVersion>0</minorVersion> * </usiVersion> * <screenBrightnessCapForWearBedtimeMode>0.1</screenBrightnessCapForWearBedtimeMode> + * <idleScreenRefreshRateTimeout> + * <luxThresholds> + * <point> + * <lux>6</lux> + * <timeout>1000</timeout> + * </point> + * <point> + * <lux>10</lux> + * <timeout>800</timeout> + * </point> + * </luxThresholds> + * </idleScreenRefreshRateTimeout> * </displayConfiguration> * } * </pre> @@ -843,6 +858,14 @@ public class DisplayDeviceConfig { private final Map<BrightnessLimitMapType, Map<Float, Float>> mLuxThrottlingData = new HashMap<>(); + /** + * The idle screen timeout configuration for switching to lower refresh rate + */ + @NonNull + private List<IdleScreenRefreshRateTimeoutLuxThresholdPoint> + mIdleScreenRefreshRateTimeoutLuxThresholds = new ArrayList<>(); + + @Nullable private HostUsiVersion mHostUsiVersion; @@ -1999,6 +2022,7 @@ public class DisplayDeviceConfig { loadUsiVersion(config); mHdrBrightnessData = HdrBrightnessData.loadConfig(config); loadBrightnessCapForWearBedtimeMode(config); + loadIdleScreenRefreshRateTimeoutConfigs(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } @@ -2024,6 +2048,7 @@ public class DisplayDeviceConfig { loadAutoBrightnessAvailableFromConfigXml(); loadRefreshRateSetting(null); loadBrightnessCapForWearBedtimeModeFromConfigXml(); + loadIdleScreenRefreshRateTimeoutConfigs(null); mLoadedFrom = "<config.xml>"; } @@ -3326,6 +3351,47 @@ public class DisplayDeviceConfig { } } + private void loadIdleScreenRefreshRateTimeoutConfigs(@Nullable DisplayConfiguration config) { + if (mFlags.isIdleScreenRefreshRateTimeoutEnabled() + && config != null && config.getIdleScreenRefreshRateTimeout() != null) { + validateIdleScreenRefreshRateTimeoutConfig( + config.getIdleScreenRefreshRateTimeout()); + mIdleScreenRefreshRateTimeoutLuxThresholds = config + .getIdleScreenRefreshRateTimeout().getLuxThresholds().getPoint(); + } + } + + private void validateIdleScreenRefreshRateTimeoutConfig( + IdleScreenRefreshRateTimeout idleScreenRefreshRateTimeoutConfig) { + IdleScreenRefreshRateTimeoutLuxThresholds idleScreenRefreshRateTimeoutLuxThresholds = + idleScreenRefreshRateTimeoutConfig.getLuxThresholds(); + + if (idleScreenRefreshRateTimeoutLuxThresholds != null) { + int previousLux = -1; + // Validate that the lux values are in the increasing order + for (IdleScreenRefreshRateTimeoutLuxThresholdPoint point : + idleScreenRefreshRateTimeoutLuxThresholds.getPoint()) { + int newLux = point.getLux().intValue(); + if (previousLux >= newLux) { + throw new RuntimeException("Lux values should be in ascending order in the" + + " idle screen refresh rate timeout config"); + } + previousLux = newLux; + } + } + } + + /** + * Gets the idle screen refresh rate timeout(in ms) configuration list. For each entry, the lux + * value represent the lower bound of the lux range, and the value of the lux in the next + * point(INF if not present) represents the upper bound for the corresponding timeout(in ms) + */ + @NonNull + public List<IdleScreenRefreshRateTimeoutLuxThresholdPoint> + getIdleScreenRefreshRateTimeoutLuxThresholdPoint() { + return mIdleScreenRefreshRateTimeoutLuxThresholds; + } + /** * Extracts a float array from the specified {@link TypedArray}. * diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index ce7c22438d54..84eebe838954 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT; import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_DISPLAYS; +import static android.Manifest.permission.RESTRICT_DISPLAY_MODES; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.hardware.display.DisplayManager.EventsMask; @@ -75,6 +76,7 @@ import android.graphics.Point; import android.hardware.OverlayProperties; import android.hardware.Sensor; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManagerInternal; import android.hardware.display.AmbientBrightnessDayStats; @@ -4530,6 +4532,14 @@ public final class DisplayManagerService extends SystemService { disableConnectedDisplay_enforcePermission(); DisplayManagerService.this.enableConnectedDisplay(displayId, false); } + + @EnforcePermission(RESTRICT_DISPLAY_MODES) + @Override // Binder call + public void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) { + requestDisplayModes_enforcePermission(); + DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes( + token, displayId, modeIds); + } } private static boolean isValidBrightness(float brightness) { @@ -5034,30 +5044,22 @@ public final class DisplayManagerService extends SystemService { * Listens to changes in device state and reports the state to LogicalDisplayMapper. */ class DeviceStateListener implements DeviceStateManager.DeviceStateCallback { - // Base state corresponds to the physical state of the device - private int mBaseState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; @Override - public void onStateChanged(int deviceState) { - boolean isDeviceStateOverrideActive = deviceState != mBaseState; + public void onDeviceStateChanged(DeviceState deviceState) { synchronized (mSyncRoot) { // Notify WindowManager that we are about to handle new device state, this should // be sent before any work related to the device state in DisplayManager, so // WindowManager could do implement that depends on the device state and display // changes (serializes device state update and display change events) Message msg = mHandler.obtainMessage(MSG_RECEIVED_DEVICE_STATE); - msg.arg1 = deviceState; + msg.arg1 = deviceState.getIdentifier(); mHandler.sendMessage(msg); mLogicalDisplayMapper - .setDeviceStateLocked(deviceState, isDeviceStateOverrideActive); + .setDeviceStateLocked(deviceState.getIdentifier()); } } - - @Override - public void onBaseStateChanged(int state) { - mBaseState = state; - } }; private class BrightnessPair { diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 5eaaf3504e85..22e4bc502131 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -204,6 +204,13 @@ final class LogicalDisplay { new SparseArray<>(); /** + * If enabled, will not check for {@link Display#FLAG_ROTATES_WITH_CONTENT} in LogicalDisplay + * and simply use the {@link DisplayInfo#rotation} supplied by WindowManager via + * {@link #setDisplayInfoOverrideFromWindowManagerLocked} + */ + private boolean mAlwaysRotateDisplayDeviceEnabled; + + /** * If the aspect ratio of the resolution of the display does not match the physical aspect * ratio of the display, then without this feature enabled, picture would appear stretched to * the user. This is because applications assume that they are rendered on square pixels @@ -220,11 +227,11 @@ final class LogicalDisplay { private final boolean mIsAnisotropyCorrectionEnabled; LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { - this(displayId, layerStack, primaryDisplayDevice, false); + this(displayId, layerStack, primaryDisplayDevice, false, false); } LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice, - boolean isAnisotropyCorrectionEnabled) { + boolean isAnisotropyCorrectionEnabled, boolean isAlwaysRotateDisplayDeviceEnabled) { mDisplayId = displayId; mLayerStack = layerStack; mPrimaryDisplayDevice = primaryDisplayDevice; @@ -236,6 +243,7 @@ final class LogicalDisplay { mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId; mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled; + mAlwaysRotateDisplayDeviceEnabled = isAlwaysRotateDisplayDeviceEnabled; } public void setDevicePositionLocked(int position) { @@ -672,7 +680,12 @@ final class LogicalDisplay { // The orientation specifies how the physical coordinate system of the display // is rotated when the contents of the logical display are rendered. int orientation = Surface.ROTATION_0; - if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) { + + // FLAG_ROTATES_WITH_CONTENT is now handled in DisplayContent. When the flag + // mAlwaysRotateDisplayDeviceEnabled is removed, we should also remove this check for + // ROTATES_WITH_CONTENT here and always set the orientation. + if ((displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0 + || mAlwaysRotateDisplayDeviceEnabled) { orientation = displayInfo.rotation; } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index e092fdae7cc7..f727eac71be8 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -441,7 +441,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mVirtualDeviceDisplayMapping.put(displayDevice.getUniqueId(), virtualDeviceUniqueId); } - void setDeviceStateLocked(int state, boolean isOverrideActive) { + void setDeviceStateLocked(int state) { if (!mBootCompleted) { // The boot animation might still be in progress, we do not want to switch states now // as the boot animation would end up with an incorrect size. @@ -465,7 +465,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState, mInteractive, mBootCompleted); final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState, - isOverrideActive, mInteractive, mBootCompleted); + mInteractive, mBootCompleted); // If all displays are off already, we can just transition here, unless we are trying to // wake or sleep the device as part of this transition. In that case defer the final @@ -513,8 +513,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mBootCompleted = true; if (mDeviceStateToBeAppliedAfterBoot != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) { - setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot, - /* isOverrideActive= */ false); + setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot); } } } @@ -560,7 +559,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { * * @param pendingState device state we are moving to * @param currentState device state we are currently in - * @param isOverrideActive if a device state override is currently active or not * @param isInteractive if the device is in an interactive state * @param isBootCompleted is the device fully booted * @@ -568,13 +566,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { * @see #setDeviceStateLocked */ @VisibleForTesting - boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive, - boolean isInteractive, boolean isBootCompleted) { + boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isInteractive, + boolean isBootCompleted) { return currentState != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER && mDeviceStatesOnWhichToSleep.get(pendingState) && !mDeviceStatesOnWhichToSleep.get(currentState) - && !isOverrideActive - && isInteractive && isBootCompleted + && isInteractive + && isBootCompleted && !mFoldSettingProvider.shouldStayAwakeOnFold(); } @@ -1152,7 +1150,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) { final int layerStack = assignLayerStackLocked(displayId); final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device, - mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled()); + mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled(), + mFlags.isAlwaysRotateDisplayDeviceEnabled()); display.updateLocked(mDisplayDeviceRepo); final DisplayInfo info = display.getDisplayInfoLocked(); diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index ec5ad7de11b3..bcdb442c3ad3 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -366,7 +366,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter { if (mSurface == null) { return null; } - return mSurface.getDefaultSize(); + final Point surfaceSize = mSurface.getDefaultSize(); + return isRotatedLocked() ? new Point(surfaceSize.y, surfaceSize.x) : surfaceSize; } @VisibleForTesting diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 15ee9372b46a..e1a166ec95f5 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -92,9 +92,9 @@ public class DisplayManagerFlags { Flags.FLAG_BRIGHTNESS_INT_RANGE_USER_PERCEPTION, Flags::brightnessIntRangeUserPerception); - private final FlagState mVsyncProximityVote = new FlagState( - Flags.FLAG_ENABLE_EXTERNAL_VSYNC_PROXIMITY_VOTE, - Flags::enableExternalVsyncProximityVote); + private final FlagState mRestrictDisplayModes = new FlagState( + Flags.FLAG_ENABLE_RESTRICT_DISPLAY_MODES, + Flags::enableRestrictDisplayModes); private final FlagState mVsyncLowPowerVote = new FlagState( Flags.FLAG_ENABLE_VSYNC_LOW_POWER_VOTE, @@ -116,6 +116,10 @@ public class DisplayManagerFlags { Flags.FLAG_FAST_HDR_TRANSITIONS, Flags::fastHdrTransitions); + private final FlagState mAlwaysRotateDisplayDevice = new FlagState( + Flags.FLAG_ALWAYS_ROTATE_DISPLAY_DEVICE, + Flags::alwaysRotateDisplayDevice); + private final FlagState mRefreshRateVotingTelemetry = new FlagState( Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY, Flags::refreshRateVotingTelemetry @@ -131,6 +135,11 @@ public class DisplayManagerFlags { Flags::sensorBasedBrightnessThrottling ); + private final FlagState mIdleScreenRefreshRateTimeout = new FlagState( + Flags.FLAG_IDLE_SCREEN_REFRESH_RATE_TIMEOUT, + Flags::idleScreenRefreshRateTimeout + ); + private final FlagState mRefactorDisplayPowerController = new FlagState( Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER, @@ -233,8 +242,8 @@ public class DisplayManagerFlags { return mBrightnessIntRangeUserPerceptionFlagState.isEnabled(); } - public boolean isVsyncProximityVoteEnabled() { - return mVsyncProximityVote.isEnabled(); + public boolean isRestrictDisplayModesEnabled() { + return mRestrictDisplayModes.isEnabled(); } public boolean isVsyncLowPowerVoteEnabled() { @@ -260,6 +269,10 @@ public class DisplayManagerFlags { return mFastHdrTransitions.isEnabled(); } + public boolean isAlwaysRotateDisplayDeviceEnabled() { + return mAlwaysRotateDisplayDevice.isEnabled(); + } + public boolean isRefreshRateVotingTelemetryEnabled() { return mRefreshRateVotingTelemetry.isEnabled(); } @@ -272,6 +285,10 @@ public class DisplayManagerFlags { return mSensorBasedBrightnessThrottling.isEnabled(); } + public boolean isIdleScreenRefreshRateTimeoutEnabled() { + return mIdleScreenRefreshRateTimeout.isEnabled(); + } + public boolean isRefactorDisplayPowerControllerEnabled() { return mRefactorDisplayPowerController.isEnabled(); } @@ -294,13 +311,15 @@ public class DisplayManagerFlags { pw.println(" " + mPowerThrottlingClamperFlagState); pw.println(" " + mSmallAreaDetectionFlagState); pw.println(" " + mBrightnessIntRangeUserPerceptionFlagState); - pw.println(" " + mVsyncProximityVote); + pw.println(" " + mRestrictDisplayModes); pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState); pw.println(" " + mAutoBrightnessModesFlagState); pw.println(" " + mFastHdrTransitions); + pw.println(" " + mAlwaysRotateDisplayDevice); pw.println(" " + mRefreshRateVotingTelemetry); pw.println(" " + mPixelAnisotropyCorrectionEnabled); pw.println(" " + mSensorBasedBrightnessThrottling); + pw.println(" " + mIdleScreenRefreshRateTimeout); pw.println(" " + mRefactorDisplayPowerController); } diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 9bf36e4e605f..a5f241f4d68e 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -130,9 +130,9 @@ flag { } flag { - name: "enable_external_vsync_proximity_vote" + name: "enable_restrict_display_modes" namespace: "display_manager" - description: "Feature flag for external vsync proximity vote" + description: "Feature flag for restriction display modes api" bug: "284866750" is_fixed_read_only: true } @@ -178,6 +178,17 @@ flag { } flag { + name: "always_rotate_display_device" + namespace: "display_manager" + description: "Use rotation from WindowManager no matter whether FLAG_ROTATES_WITH_CONTENT is set or not" + bug: "302326003" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "refresh_rate_voting_telemetry" namespace: "display_manager" description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager" @@ -208,3 +219,11 @@ flag { bug: "294444204" is_fixed_read_only: true } + +flag { + name: "idle_screen_refresh_rate_timeout" + namespace: "display_manager" + description: "Feature flag for reducing the refresh rate when the screen is idle after a timeout" + bug: "310026579" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java index c53823139ffe..6d750c0aa3cb 100644 --- a/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java +++ b/services/core/java/com/android/server/display/mode/BaseModeRefreshRateVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class BaseModeRefreshRateVote implements Vote { @@ -31,7 +33,7 @@ class BaseModeRefreshRateVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { if (summary.appRequestBaseModeRefreshRate == 0f && mAppRequestBaseModeRefreshRate > 0f) { summary.appRequestBaseModeRefreshRate = mAppRequestBaseModeRefreshRate; diff --git a/services/core/java/com/android/server/display/mode/CombinedVote.java b/services/core/java/com/android/server/display/mode/CombinedVote.java index 4b68791268e9..3cd16bf5c640 100644 --- a/services/core/java/com/android/server/display/mode/CombinedVote.java +++ b/services/core/java/com/android/server/display/mode/CombinedVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Collections; import java.util.List; import java.util.Objects; @@ -28,7 +30,7 @@ class CombinedVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { mVotes.forEach(vote -> vote.updateSummary(summary)); } diff --git a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java index 7f5740690c7f..7abb518ec494 100644 --- a/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java +++ b/services/core/java/com/android/server/display/mode/DisableRefreshRateSwitchingVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class DisableRefreshRateSwitchingVote implements Vote { @@ -31,7 +33,7 @@ class DisableRefreshRateSwitchingVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.disableRefreshRateSwitching = summary.disableRefreshRateSwitching || mDisableRefreshRateSwitching; } diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 64cbd5488d90..495ae87fe0b9 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -41,6 +41,7 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.net.Uri; import android.os.Handler; +import android.os.IBinder; import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.Looper; @@ -80,7 +81,6 @@ import com.android.server.display.utils.AmbientFilterFactory; import com.android.server.display.utils.DeviceConfigParsingUtils; import com.android.server.display.utils.SensorUtils; import com.android.server.sensors.SensorManagerInternal; -import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener; import com.android.server.statusbar.StatusBarManagerInternal; import java.io.PrintWriter; @@ -128,9 +128,12 @@ public class DisplayModeDirector { private final SettingsObserver mSettingsObserver; private final DisplayObserver mDisplayObserver; private final UdfpsObserver mUdfpsObserver; - private final SensorObserver mSensorObserver; + private final ProximitySensorObserver mSensorObserver; private final HbmObserver mHbmObserver; private final SkinThermalStatusObserver mSkinThermalStatusObserver; + + @Nullable + private final SystemRequestObserver mSystemRequestObserver; private final DeviceConfigParameterProvider mConfigParameterProvider; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @@ -203,6 +206,7 @@ public class DisplayModeDirector { .isDisplaysRefreshRatesSynchronizationEnabled(); mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled = displayManagerFlags .isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled(); + mContext = context; mHandler = new DisplayModeDirectorHandler(handler.getLooper()); mInjector = injector; @@ -222,10 +226,15 @@ public class DisplayModeDirector { mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked, mVotesStatsReporter); mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage); - mSensorObserver = new SensorObserver(context, mVotesStorage, injector); + mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector); mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage); mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(), mDeviceConfigDisplaySettings); + if (mDvrrSupported && displayManagerFlags.isRestrictDisplayModesEnabled()) { + mSystemRequestObserver = new SystemRequestObserver(mVotesStorage); + } else { + mSystemRequestObserver = null; + } mAlwaysRespectAppRequest = false; mSupportsFrameRateOverride = injector.supportsFrameRateOverride(); } @@ -520,6 +529,15 @@ public class DisplayModeDirector { } /** + * Delegates requestDisplayModes call to SystemRequestObserver + */ + public void requestDisplayModes(IBinder token, int displayId, int[] modeIds) { + if (mSystemRequestObserver != null) { + mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds); + } + } + + /** * Print the object's state and debug information into the given stream. * * @param pw The stream to dump information to. @@ -970,10 +988,10 @@ public class DisplayModeDirector { Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0; final Vote vote; if (inLowPowerMode && mVsynLowPowerVoteEnabled) { - vote = Vote.forSupportedModes(List.of( - new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f, + vote = Vote.forSupportedRefreshRates(List.of( + new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, /* vsyncRate= */ 240f), - new SupportedModesVote.SupportedMode(/* peakRefreshRate= */ 60f, + new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f) )); } else if (inLowPowerMode) { @@ -2158,11 +2176,11 @@ public class DisplayModeDirector { } if (mVsyncLowLightBlockingVoteEnabled) { - refreshRateSwitchingVote = Vote.forSupportedModesAndDisableRefreshRateSwitching( + refreshRateSwitchingVote = Vote.forSupportedRefreshRatesAndDisableSwitching( List.of( - new SupportedModesVote.SupportedMode( + new SupportedRefreshRatesVote.RefreshRates( /* peakRefreshRate= */ 60f, /* vsyncRate= */ 60f), - new SupportedModesVote.SupportedMode( + new SupportedRefreshRatesVote.RefreshRates( /* peakRefreshRate= */120f, /* vsyncRate= */ 120f))); } else { refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); @@ -2498,116 +2516,6 @@ public class DisplayModeDirector { } } - protected static final class SensorObserver implements ProximityActiveListener, - DisplayManager.DisplayListener { - private final String mProximitySensorName = null; - private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY; - - private final VotesStorage mVotesStorage; - private final Context mContext; - private final Injector mInjector; - @GuardedBy("mSensorObserverLock") - private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray(); - private final Object mSensorObserverLock = new Object(); - - private DisplayManager mDisplayManager; - private DisplayManagerInternal mDisplayManagerInternal; - @GuardedBy("mSensorObserverLock") - private boolean mIsProxActive = false; - - SensorObserver(Context context, VotesStorage votesStorage, Injector injector) { - mContext = context; - mVotesStorage = votesStorage; - mInjector = injector; - } - - @Override - public void onProximityActive(boolean isActive) { - synchronized (mSensorObserverLock) { - if (mIsProxActive != isActive) { - mIsProxActive = isActive; - recalculateVotesLocked(); - } - } - } - - public void observe() { - mDisplayManager = mContext.getSystemService(DisplayManager.class); - mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); - - final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal(); - sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); - - synchronized (mSensorObserverLock) { - for (Display d : mInjector.getDisplays()) { - mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); - } - } - mInjector.registerDisplayListener(this, BackgroundThread.getHandler(), - DisplayManager.EVENT_FLAG_DISPLAY_ADDED - | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED - | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); - } - - private void recalculateVotesLocked() { - final Display[] displays = mInjector.getDisplays(); - for (Display d : displays) { - int displayId = d.getDisplayId(); - Vote vote = null; - if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) { - final RefreshRateRange rate = - mDisplayManagerInternal.getRefreshRateForDisplayAndSensor( - displayId, mProximitySensorName, mProximitySensorType); - if (rate != null) { - vote = Vote.forPhysicalRefreshRates(rate.min, rate.max); - } - } - mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote); - } - } - - void dump(PrintWriter pw) { - pw.println(" SensorObserver"); - synchronized (mSensorObserverLock) { - pw.println(" mIsProxActive=" + mIsProxActive); - pw.println(" mDozeStateByDisplay:"); - for (int i = 0; i < mDozeStateByDisplay.size(); i++) { - final int id = mDozeStateByDisplay.keyAt(i); - final boolean dozed = mDozeStateByDisplay.valueAt(i); - pw.println(" " + id + " -> " + dozed); - } - } - } - - @Override - public void onDisplayAdded(int displayId) { - boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId)); - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.put(displayId, isDozeState); - recalculateVotesLocked(); - } - } - - @Override - public void onDisplayChanged(int displayId) { - boolean wasDozeState = mDozeStateByDisplay.get(displayId); - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.put(displayId, - mInjector.isDozeState(mInjector.getDisplay(displayId))); - if (wasDozeState != mDozeStateByDisplay.get(displayId)) { - recalculateVotesLocked(); - } - } - } - - @Override - public void onDisplayRemoved(int displayId) { - synchronized (mSensorObserverLock) { - mDozeStateByDisplay.delete(displayId); - recalculateVotesLocked(); - } - } - } /** * Listens to DisplayManager for HBM status and applies any refresh-rate restrictions for diff --git a/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java new file mode 100644 index 000000000000..11418c147caa --- /dev/null +++ b/services/core/java/com/android/server/display/mode/ProximitySensorObserver.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 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.mode; + +import android.hardware.Sensor; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.util.SparseBooleanArray; +import android.view.Display; +import android.view.SurfaceControl; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; +import com.android.server.sensors.SensorManagerInternal; + +import java.io.PrintWriter; + +class ProximitySensorObserver implements + SensorManagerInternal.ProximityActiveListener, + DisplayManager.DisplayListener { + private final String mProximitySensorName = null; + private final String mProximitySensorType = Sensor.STRING_TYPE_PROXIMITY; + + private final VotesStorage mVotesStorage; + private final DisplayModeDirector.Injector mInjector; + @GuardedBy("mSensorObserverLock") + private final SparseBooleanArray mDozeStateByDisplay = new SparseBooleanArray(); + private final Object mSensorObserverLock = new Object(); + private DisplayManagerInternal mDisplayManagerInternal; + @GuardedBy("mSensorObserverLock") + private boolean mIsProxActive = false; + + ProximitySensorObserver(VotesStorage votesStorage, DisplayModeDirector.Injector injector) { + mVotesStorage = votesStorage; + mInjector = injector; + } + + @Override + public void onProximityActive(boolean isActive) { + synchronized (mSensorObserverLock) { + if (mIsProxActive != isActive) { + mIsProxActive = isActive; + recalculateVotesLocked(); + } + } + } + + void observe() { + mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); + + final SensorManagerInternal sensorManager = mInjector.getSensorManagerInternal(); + sensorManager.addProximityActiveListener(BackgroundThread.getExecutor(), this); + + synchronized (mSensorObserverLock) { + for (Display d : mInjector.getDisplays()) { + mDozeStateByDisplay.put(d.getDisplayId(), mInjector.isDozeState(d)); + } + } + mInjector.registerDisplayListener(this, BackgroundThread.getHandler(), + DisplayManager.EVENT_FLAG_DISPLAY_ADDED + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); + } + + @GuardedBy("mSensorObserverLock") + private void recalculateVotesLocked() { + final Display[] displays = mInjector.getDisplays(); + for (Display d : displays) { + int displayId = d.getDisplayId(); + Vote vote = null; + if (mIsProxActive && !mDozeStateByDisplay.get(displayId)) { + final SurfaceControl.RefreshRateRange rate = + mDisplayManagerInternal.getRefreshRateForDisplayAndSensor( + displayId, mProximitySensorName, mProximitySensorType); + if (rate != null) { + vote = Vote.forPhysicalRefreshRates(rate.min, rate.max); + } + } + mVotesStorage.updateVote(displayId, Vote.PRIORITY_PROXIMITY, vote); + } + } + + void dump(PrintWriter pw) { + pw.println(" SensorObserver"); + synchronized (mSensorObserverLock) { + pw.println(" mIsProxActive=" + mIsProxActive); + pw.println(" mDozeStateByDisplay:"); + for (int i = 0; i < mDozeStateByDisplay.size(); i++) { + final int id = mDozeStateByDisplay.keyAt(i); + final boolean dozed = mDozeStateByDisplay.valueAt(i); + pw.println(" " + id + " -> " + dozed); + } + } + } + + @Override + public void onDisplayAdded(int displayId) { + boolean isDozeState = mInjector.isDozeState(mInjector.getDisplay(displayId)); + synchronized (mSensorObserverLock) { + mDozeStateByDisplay.put(displayId, isDozeState); + recalculateVotesLocked(); + } + } + + @Override + public void onDisplayChanged(int displayId) { + synchronized (mSensorObserverLock) { + boolean wasDozeState = mDozeStateByDisplay.get(displayId); + mDozeStateByDisplay.put(displayId, + mInjector.isDozeState(mInjector.getDisplay(displayId))); + if (wasDozeState != mDozeStateByDisplay.get(displayId)) { + recalculateVotesLocked(); + } + } + } + + @Override + public void onDisplayRemoved(int displayId) { + synchronized (mSensorObserverLock) { + mDozeStateByDisplay.delete(displayId); + recalculateVotesLocked(); + } + } +} diff --git a/services/core/java/com/android/server/display/mode/RefreshRateVote.java b/services/core/java/com/android/server/display/mode/RefreshRateVote.java index 670b8a13da4d..b96ab3b6be3d 100644 --- a/services/core/java/com/android/server/display/mode/RefreshRateVote.java +++ b/services/core/java/com/android/server/display/mode/RefreshRateVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; @@ -64,7 +66,7 @@ abstract class RefreshRateVote implements Vote { * Vote: min(ignored) min(applied) min(applied+physical) max(applied) max(ignored) */ @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate, mMinRefreshRate); summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, mMaxRefreshRate); // Physical refresh rate cannot be lower than the minimal render frame rate. @@ -97,7 +99,7 @@ abstract class RefreshRateVote implements Vote { * Vote: min(ignored) min(applied) max(applied+render) max(applied) max(ignored) */ @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate, mMinRefreshRate); summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate, diff --git a/services/core/java/com/android/server/display/mode/SizeVote.java b/services/core/java/com/android/server/display/mode/SizeVote.java index f2f8dc451098..f5a5abea9d9e 100644 --- a/services/core/java/com/android/server/display/mode/SizeVote.java +++ b/services/core/java/com/android/server/display/mode/SizeVote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.Objects; class SizeVote implements Vote { @@ -48,7 +50,7 @@ class SizeVote implements Vote { } @Override - public void updateSummary(VoteSummary summary) { + public void updateSummary(@NonNull VoteSummary summary) { if (mHeight > 0 && mWidth > 0) { // For display size, disable refresh rate switching and base mode refresh rate use // only the first vote we come across (i.e. the highest priority vote that includes diff --git a/services/core/java/com/android/server/display/mode/SupportedModesVote.java b/services/core/java/com/android/server/display/mode/SupportedModesVote.java index 7eebcc050b16..0cf8311128d0 100644 --- a/services/core/java/com/android/server/display/mode/SupportedModesVote.java +++ b/services/core/java/com/android/server/display/mode/SupportedModesVote.java @@ -16,77 +16,42 @@ package com.android.server.display.mode; -import java.util.ArrayList; +import android.annotation.NonNull; + import java.util.Collections; import java.util.List; import java.util.Objects; -class SupportedModesVote implements Vote { +public class SupportedModesVote implements Vote { - final List<SupportedMode> mSupportedModes; + final List<Integer> mModeIds; - SupportedModesVote(List<SupportedMode> supportedModes) { - mSupportedModes = Collections.unmodifiableList(supportedModes); + SupportedModesVote(List<Integer> modeIds) { + mModeIds = Collections.unmodifiableList(modeIds); } - - /** - * Summary should have subset of supported modes. - * If Vote1.supportedModes=(A,B), Vote2.supportedModes=(B,C) then summary.supportedModes=(B) - * If summary.supportedModes==null then there is no restriction on supportedModes - */ @Override - public void updateSummary(VoteSummary summary) { - if (summary.supportedModes == null) { - summary.supportedModes = new ArrayList<>(mSupportedModes); + public void updateSummary(@NonNull VoteSummary summary) { + if (summary.supportedModeIds == null) { + summary.supportedModeIds = mModeIds; } else { - summary.supportedModes.retainAll(mSupportedModes); + summary.supportedModeIds.retainAll(mModeIds); } } @Override + public String toString() { + return "SupportedModesVote{ mModeIds=" + mModeIds + " }"; + } + + @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof SupportedModesVote that)) return false; - return mSupportedModes.equals(that.mSupportedModes); + return mModeIds.equals(that.mModeIds); } @Override public int hashCode() { - return Objects.hash(mSupportedModes); - } - - @Override - public String toString() { - return "SupportedModesVote{ mSupportedModes=" + mSupportedModes + " }"; - } - - static class SupportedMode { - final float mPeakRefreshRate; - final float mVsyncRate; - - - SupportedMode(float peakRefreshRate, float vsyncRate) { - mPeakRefreshRate = peakRefreshRate; - mVsyncRate = vsyncRate; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SupportedMode that)) return false; - return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0 - && Float.compare(that.mVsyncRate, mVsyncRate) == 0; - } - - @Override - public int hashCode() { - return Objects.hash(mPeakRefreshRate, mVsyncRate); - } - - @Override - public String toString() { - return "SupportedMode{ mPeakRefreshRate=" + mPeakRefreshRate - + ", mVsyncRate=" + mVsyncRate + " }"; - } + return Objects.hash(mModeIds); } } diff --git a/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java new file mode 100644 index 000000000000..5305487b2ddd --- /dev/null +++ b/services/core/java/com/android/server/display/mode/SupportedRefreshRatesVote.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 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.mode; + +import android.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +class SupportedRefreshRatesVote implements Vote { + + final List<RefreshRates> mRefreshRates; + + SupportedRefreshRatesVote(List<RefreshRates> refreshRates) { + mRefreshRates = Collections.unmodifiableList(refreshRates); + } + + /** + * Summary should have subset of supported modes. + * If Vote1.refreshRates=(A,B), Vote2.refreshRates=(B,C) + * then summary.supportedRefreshRates=(B) + * If summary.supportedRefreshRates==null then there is no restriction on supportedRefreshRates + */ + @Override + public void updateSummary(@NonNull VoteSummary summary) { + if (summary.supportedRefreshRates == null) { + summary.supportedRefreshRates = new ArrayList<>(mRefreshRates); + } else { + summary.supportedRefreshRates.retainAll(mRefreshRates); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SupportedRefreshRatesVote that)) return false; + return mRefreshRates.equals(that.mRefreshRates); + } + + @Override + public int hashCode() { + return Objects.hash(mRefreshRates); + } + + @Override + public String toString() { + return "SupportedRefreshRatesVote{ mSupportedModes=" + mRefreshRates + " }"; + } + + static class RefreshRates { + final float mPeakRefreshRate; + final float mVsyncRate; + + RefreshRates(float peakRefreshRate, float vsyncRate) { + mPeakRefreshRate = peakRefreshRate; + mVsyncRate = vsyncRate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof RefreshRates that)) return false; + return Float.compare(that.mPeakRefreshRate, mPeakRefreshRate) == 0 + && Float.compare(that.mVsyncRate, mVsyncRate) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mPeakRefreshRate, mVsyncRate); + } + + @Override + public String toString() { + return "RefreshRates{ mPeakRefreshRate=" + mPeakRefreshRate + + ", mVsyncRate=" + mVsyncRate + " }"; + } + } +} diff --git a/services/core/java/com/android/server/display/mode/SystemRequestObserver.java b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java new file mode 100644 index 000000000000..15f19cca99db --- /dev/null +++ b/services/core/java/com/android/server/display/mode/SystemRequestObserver.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2024 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.mode; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * SystemRequestObserver responsible for handling system requests to filter allowable display + * modes + */ +class SystemRequestObserver { + private final VotesStorage mVotesStorage; + + private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + // noop, binderDied(@NonNull IBinder who) is overridden + } + @Override + public void binderDied(@NonNull IBinder who) { + removeSystemRequestedVotes(who); + who.unlinkToDeath(mDeathRecipient, 0); + } + }; + + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final Map<IBinder, SparseArray<List<Integer>>> mDisplaysRestrictions = new HashMap<>(); + + SystemRequestObserver(VotesStorage storage) { + mVotesStorage = storage; + } + + void requestDisplayModes(IBinder token, int displayId, @Nullable int[] modeIds) { + if (modeIds == null) { + removeSystemRequestedVote(token, displayId); + } else { + addSystemRequestedVote(token, displayId, modeIds); + } + } + + private void addSystemRequestedVote(IBinder token, int displayId, @NonNull int[] modeIds) { + try { + boolean needLinkToDeath = false; + List<Integer> modeIdsList = new ArrayList<>(); + for (int mode: modeIds) { + modeIdsList.add(mode); + } + synchronized (mLock) { + SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token); + if (modesByDisplay == null) { + needLinkToDeath = true; + modesByDisplay = new SparseArray<>(); + mDisplaysRestrictions.put(token, modesByDisplay); + } + + modesByDisplay.put(displayId, modeIdsList); + updateStorageLocked(displayId); + } + if (needLinkToDeath) { + token.linkToDeath(mDeathRecipient, 0); + } + } catch (RemoteException re) { + removeSystemRequestedVotes(token); + } + } + + private void removeSystemRequestedVote(IBinder token, int displayId) { + boolean needToUnlink = false; + synchronized (mLock) { + SparseArray<List<Integer>> modesByDisplay = mDisplaysRestrictions.get(token); + if (modesByDisplay != null) { + modesByDisplay.remove(displayId); + needToUnlink = modesByDisplay.size() == 0; + updateStorageLocked(displayId); + } + } + if (needToUnlink) { + token.unlinkToDeath(mDeathRecipient, 0); + } + } + + private void removeSystemRequestedVotes(IBinder token) { + synchronized (mLock) { + SparseArray<List<Integer>> removed = mDisplaysRestrictions.remove(token); + if (removed != null) { + for (int i = 0; i < removed.size(); i++) { + updateStorageLocked(removed.keyAt(i)); + } + } + } + } + + @GuardedBy("mLock") + private void updateStorageLocked(int displayId) { + List<Integer> modeIds = new ArrayList<>(); + boolean[] modesFound = new boolean[1]; + + mDisplaysRestrictions.forEach((key, value) -> { + List<Integer> modesForDisplay = value.get(displayId); + if (modesForDisplay != null) { + if (!modesFound[0]) { + modeIds.addAll(modesForDisplay); + modesFound[0] = true; + } else { + modeIds.retainAll(modesForDisplay); + } + } + }); + + mVotesStorage.updateVote(displayId, Vote.PRIORITY_SYSTEM_REQUESTED_MODES, + modesFound[0] ? Vote.forSupportedModes(modeIds) : null); + } +} diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index e8d5a194f8f4..5b987f491a45 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -16,6 +16,8 @@ package com.android.server.display.mode; +import android.annotation.NonNull; + import java.util.List; interface Vote { @@ -91,26 +93,29 @@ interface Vote { // For concurrent displays we want to limit refresh rate on all displays int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12; + // For internal application to limit display modes to specific ids + int PRIORITY_SYSTEM_REQUESTED_MODES = 13; + // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - int PRIORITY_LOW_POWER_MODE = 13; + int PRIORITY_LOW_POWER_MODE = 14; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14; + int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 15; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - int PRIORITY_SKIN_TEMPERATURE = 15; + int PRIORITY_SKIN_TEMPERATURE = 16; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - int PRIORITY_PROXIMITY = 16; + int PRIORITY_PROXIMITY = 17; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - int PRIORITY_UDFPS = 17; + int PRIORITY_UDFPS = 18; // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. @@ -128,7 +133,7 @@ interface Vote { */ int INVALID_SIZE = -1; - void updateSummary(VoteSummary summary); + void updateSummary(@NonNull VoteSummary summary); static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) { return new CombinedVote( @@ -166,15 +171,22 @@ interface Vote { return new BaseModeRefreshRateVote(baseModeRefreshRate); } - static Vote forSupportedModes(List<SupportedModesVote.SupportedMode> supportedModes) { - return new SupportedModesVote(supportedModes); + static Vote forSupportedRefreshRates( + List<SupportedRefreshRatesVote.RefreshRates> refreshRates) { + return new SupportedRefreshRatesVote(refreshRates); } + static Vote forSupportedModes(List<Integer> modeIds) { + return new SupportedModesVote(modeIds); + } + + - static Vote forSupportedModesAndDisableRefreshRateSwitching( - List<SupportedModesVote.SupportedMode> supportedModes) { + static Vote forSupportedRefreshRatesAndDisableSwitching( + List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) { return new CombinedVote( - List.of(forDisableRefreshRateSwitching(), forSupportedModes(supportedModes))); + List.of(forDisableRefreshRateSwitching(), + forSupportedRefreshRates(supportedRefreshRates))); } static String priorityToString(int priority) { diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java index 5fc36b589d38..d4ce892eeba9 100644 --- a/services/core/java/com/android/server/display/mode/VoteSummary.java +++ b/services/core/java/com/android/server/display/mode/VoteSummary.java @@ -16,6 +16,7 @@ package com.android.server.display.mode; +import android.annotation.Nullable; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -39,7 +40,11 @@ final class VoteSummary { public boolean disableRefreshRateSwitching; public float appRequestBaseModeRefreshRate; - public List<SupportedModesVote.SupportedMode> supportedModes; + @Nullable + public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates; + + @Nullable + public List<Integer> supportedModeIds; final boolean mIsDisplayResolutionRangeVotingEnabled; @@ -112,6 +117,9 @@ final class VoteSummary { boolean missingBaseModeRefreshRate = appRequestBaseModeRefreshRate > 0f; for (Display.Mode mode : modes) { + if (!validateRefreshRatesSupported(mode)) { + continue; + } if (!validateModeSupported(mode)) { continue; } @@ -253,21 +261,37 @@ final class VoteSummary { } private boolean validateModeSupported(Display.Mode mode) { - if (supportedModes == null || !mSupportedModesVoteEnabled) { + if (supportedModeIds == null || !mSupportedModesVoteEnabled) { + return true; + } + if (supportedModeIds.contains(mode.getModeId())) { return true; } - for (SupportedModesVote.SupportedMode supportedMode : supportedModes) { - if (equalsWithinFloatTolerance(mode.getRefreshRate(), supportedMode.mPeakRefreshRate) - && equalsWithinFloatTolerance(mode.getVsyncRate(), supportedMode.mVsyncRate)) { + if (mLoggingEnabled) { + Slog.w(TAG, "Discarding mode " + mode.getModeId() + + ", supportedMode not found" + + ": mode.modeId=" + mode.getModeId() + + ", supportedModeIds=" + supportedModeIds); + } + return false; + } + + private boolean validateRefreshRatesSupported(Display.Mode mode) { + if (supportedRefreshRates == null || !mSupportedModesVoteEnabled) { + return true; + } + for (SupportedRefreshRatesVote.RefreshRates refreshRates : this.supportedRefreshRates) { + if (equalsWithinFloatTolerance(mode.getRefreshRate(), refreshRates.mPeakRefreshRate) + && equalsWithinFloatTolerance(mode.getVsyncRate(), refreshRates.mVsyncRate)) { return true; } } if (mLoggingEnabled) { Slog.w(TAG, "Discarding mode " + mode.getModeId() - + ", supportedMode not found" + + ", supportedRefreshRates not found" + ": mode.refreshRate=" + mode.getRefreshRate() + ", mode.vsyncRate=" + mode.getVsyncRate() - + ", supportedModes=" + supportedModes); + + ", supportedRefreshRates=" + supportedRefreshRates); } return false; } @@ -298,7 +322,8 @@ final class VoteSummary { return false; } - if (supportedModes != null && mSupportedModesVoteEnabled && supportedModes.isEmpty()) { + if (supportedRefreshRates != null && mSupportedModesVoteEnabled + && supportedRefreshRates.isEmpty()) { if (mLoggingEnabled) { Slog.w(TAG, "Vote summary resulted in empty set (empty supportedModes)"); } @@ -345,7 +370,8 @@ final class VoteSummary { minHeight = 0; disableRefreshRateSwitching = false; appRequestBaseModeRefreshRate = 0f; - supportedModes = null; + supportedRefreshRates = null; + supportedModeIds = null; if (mLoggingEnabled) { Slog.i(TAG, "Summary reset: " + this); } @@ -367,7 +393,8 @@ final class VoteSummary { + ", minHeight=" + minHeight + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate - + ", supportedModes=" + supportedModes + + ", supportedRefreshRates=" + supportedRefreshRates + + ", supportedModeIds=" + supportedModeIds + ", mIsDisplayResolutionRangeVotingEnabled=" + mIsDisplayResolutionRangeVotingEnabled + ", mSupportedModesVoteEnabled=" + mSupportedModesVoteEnabled diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java index e80b9451dd14..7562a525b5f6 100644 --- a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java +++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java @@ -117,11 +117,11 @@ class VotesStatsReporter { maxRefreshRate = (int) physicalVote.mMaxRefreshRate; } else if (!ignoreRenderRate && (vote instanceof RefreshRateVote.RenderVote renderVote)) { maxRefreshRate = (int) renderVote.mMaxRefreshRate; - } else if (vote instanceof SupportedModesVote supportedModesVote) { - // SupportedModesVote limits mode by specific refreshRates, so highest rr is allowed + } else if (vote instanceof SupportedRefreshRatesVote refreshRatesVote) { + // SupportedRefreshRatesVote limits mode by refreshRates, so highest rr is allowed maxRefreshRate = 0; - for (SupportedModesVote.SupportedMode mode : supportedModesVote.mSupportedModes) { - maxRefreshRate = Math.max(maxRefreshRate, (int) mode.mPeakRefreshRate); + for (SupportedRefreshRatesVote.RefreshRates rr : refreshRatesVote.mRefreshRates) { + maxRefreshRate = Math.max(maxRefreshRate, (int) rr.mPeakRefreshRate); } } else if (vote instanceof CombinedVote combinedVote) { for (Vote subVote: combinedVote.mVotes) { diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java index 56c7c18c0a11..6becf1c46d05 100644 --- a/services/core/java/com/android/server/display/mode/VotesStorage.java +++ b/services/core/java/com/android/server/display/mode/VotesStorage.java @@ -18,6 +18,7 @@ package com.android.server.display.mode; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.IntArray; import android.util.Slog; import android.util.SparseArray; @@ -124,6 +125,44 @@ class VotesStorage { } } + /** removes all votes with certain priority from vote storage */ + void removeAllVotesForPriority(int priority) { + if (mLoggingEnabled) { + Slog.i(TAG, "removeAllVotesForPriority(priority=" + + Vote.priorityToString(priority) + ")"); + } + if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) { + Slog.w(TAG, "Received an invalid priority, ignoring:" + + " priority=" + Vote.priorityToString(priority)); + return; + } + IntArray removedVotesDisplayIds = new IntArray(); + synchronized (mStorageLock) { + int size = mVotesByDisplay.size(); + for (int i = 0; i < size; i++) { + SparseArray<Vote> votes = mVotesByDisplay.valueAt(i); + if (votes.get(priority) != null) { + votes.remove(priority); + removedVotesDisplayIds.add(mVotesByDisplay.keyAt(i)); + } + } + } + if (mLoggingEnabled) { + Slog.i(TAG, "Removed votes with priority=" + priority + + " for displays=" + removedVotesDisplayIds); + } + int removedVotesSize = removedVotesDisplayIds.size(); + if (removedVotesSize > 0) { + if (mVotesStatsReporter != null) { + for (int i = 0; i < removedVotesSize; i++) { + mVotesStatsReporter.reportVoteChanged( + removedVotesDisplayIds.get(i), priority, null); + } + } + mListener.onChanged(); + } + } + /** dump class values, for debugging */ void dump(@NonNull PrintWriter pw) { SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>(); diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java index b8ae737919d9..f21fd4132f0f 100644 --- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java +++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java @@ -146,6 +146,10 @@ public final class KeyboardMetricsCollector { SPLIT_SCREEN_NAVIGATION( FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION, "SPLIT_SCREEN_NAVIGATION"), + + CHANGE_SPLITSCREEN_FOCUS( + FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS, + "CHANGE_SPLITSCREEN_FOCUS"), TRIGGER_BUG_REPORT( FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT, "TRIGGER_BUG_REPORT"), diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index a100fe06c407..29cccdf9e2c6 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -74,7 +74,6 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod; @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID; @GuardedBy("ImfLock.class") @Nullable private IBinder mCurToken; - @GuardedBy("ImfLock.class") private int mCurSeq; @GuardedBy("ImfLock.class") private boolean mVisibleBound; @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw; @GuardedBy("ImfLock.class") private boolean mSupportsConnectionlessStylusHw; @@ -195,27 +194,6 @@ final class InputMethodBindingController { } /** - * The current binding sequence number, incremented every time there is - * a new bind performed. - */ - @GuardedBy("ImfLock.class") - int getSequenceNumber() { - return mCurSeq; - } - - /** - * Increase the current binding sequence number by one. - * Reset to 1 on overflow. - */ - @GuardedBy("ImfLock.class") - void advanceSequenceNumber() { - mCurSeq += 1; - if (mCurSeq <= 0) { - mCurSeq = 1; - } - } - - /** * If non-null, this is the input method service we are currently connected * to. */ @@ -435,9 +413,11 @@ final class InputMethodBindingController { mLastBindTime = SystemClock.uptimeMillis(); addFreshWindowToken(); + final UserData monitor = UserData.getOrCreate( + mService.getCurrentImeUserIdLocked()); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, - null, null, null, mCurId, mCurSeq, false); + null, null, null, mCurId, monitor.mSequence.getSequenceNumber(), false); } Slog.w(InputMethodManagerService.TAG, diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index dd2474bc0553..d0a83a66dfba 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -285,12 +285,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final Resources mRes; private final Handler mHandler; - /** - * TODO(b/329163064): Remove this field. - */ - @NonNull @MultiUserUnawareField - private InputMethodSettings mSettings; + @UserIdInt + @GuardedBy("ImfLock.class") + private int mCurrentUserId; + @MultiUserUnawareField final SettingsObserver mSettingsObserver; final WindowManagerInternal mWindowManagerInternal; @@ -480,7 +479,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ @GuardedBy("ImfLock.class") private int getSequenceNumberLocked() { - return mBindingController.getSequenceNumber(); + final UserData monitor = UserData.getOrCreate(mCurrentUserId); + return monitor.mSequence.getSequenceNumber(); } /** @@ -489,13 +489,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ @GuardedBy("ImfLock.class") private void advanceSequenceNumberLocked() { - mBindingController.advanceSequenceNumber(); + final UserData monitor = UserData.getOrCreate(mCurrentUserId); + monitor.mSequence.advanceSequenceNumber(); } @GuardedBy("ImfLock.class") @Nullable InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) { - return mSettings.getMethodMap().get(imeId); + return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId); } /** @@ -559,10 +560,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private InputMethodSubtype mCurrentSubtype; /** - * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController} + * Map of window perceptible states indexed by their associated window tokens. + * + * The value {@code true} indicates that IME has not been mostly hidden via + * {@link android.view.InsetsController} for the given window. */ - @MultiUserUnawareField - private boolean mCurPerceptible; + @GuardedBy("ImfLock.class") + private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>(); /** * Set to true if our ServiceConnection is currently actively bound to @@ -813,7 +817,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches(); } else { boolean enabledChanged = false; - String newEnabled = mSettings.getEnabledInputMethodsStr(); + String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId) + .getEnabledInputMethodsStr(); if (!mLastEnabled.equals(newEnabled)) { mLastEnabled = newEnabled; enabledChanged = true; @@ -845,9 +850,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // sender userId can be a real user ID or USER_ALL. final int senderUserId = pendingResult.getSendingUserId(); if (senderUserId != UserHandle.USER_ALL) { - if (senderUserId != mSettings.getUserId()) { - // A background user is trying to hide the dialog. Ignore. - return; + synchronized (ImfLock.class) { + if (senderUserId != mCurrentUserId) { + // A background user is trying to hide the dialog. Ignore. + return; + } } } mMenuController.hideInputMethodMenu(); @@ -878,9 +885,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, settings); - if (userId == mSettings.getUserId()) { - mSettings = settings; - } } postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */); // If the locale is changed, needs to reset the default ime @@ -942,7 +946,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean isChangingPackagesOfCurrentUserLocked() { final int userId = getChangingUserId(); - final boolean retval = userId == mSettings.getUserId(); + final boolean retval = userId == mCurrentUserId; if (DEBUG) { if (!retval) { Slog.d(TAG, "--- ignore this call back from a background user: " + userId); @@ -957,8 +961,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!isChangingPackagesOfCurrentUserLocked()) { return false; } - String curInputMethodId = mSettings.getSelectedInputMethod(); - final List<InputMethodInfo> methodList = mSettings.getMethodList(); + final InputMethodSettings settings = + InputMethodSettingsRepository.get(mCurrentUserId); + String curInputMethodId = settings.getSelectedInputMethod(); + final List<InputMethodInfo> methodList = settings.getMethodList(); final int numImes = methodList.size(); if (curInputMethodId != null) { for (int i = 0; i < numImes; i++) { @@ -1075,7 +1081,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private void onFinishPackageChangesInternal() { synchronized (ImfLock.class) { final int userId = getChangingUserId(); - final boolean isCurrentUser = (userId == mSettings.getUserId()); + final boolean isCurrentUser = (userId == mCurrentUserId); final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); @@ -1133,7 +1139,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!isCurrentUser) { return; } - mSettings = newSettings; postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); boolean changed = false; @@ -1287,8 +1292,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub void onUnlockUser(@UserIdInt int userId) { synchronized (ImfLock.class) { if (DEBUG) { - Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" - + mSettings.getUserId()); + Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId); } if (!mSystemReady) { return; @@ -1296,8 +1300,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, newSettings); - if (mSettings.getUserId() == userId) { - mSettings = newSettings; + if (mCurrentUserId == userId) { // We need to rebuild IMEs. postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); updateInputMethodsFromSettingsLocked(true /* enabledChanged */); @@ -1368,17 +1371,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked InputMethodSettingsRepository.initialize(mHandler, mContext); AdditionalSubtypeMapRepository.initialize(mHandler, mContext); + UserData.initialize(mHandler); - final int userId = mActivityManagerInternal.getCurrentUserId(); + mCurrentUserId = mActivityManagerInternal.getCurrentUserId(); - mSettings = InputMethodSettingsRepository.get(userId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(context, - mSettings.getMethodMap(), userId); + settings.getMethodMap(), settings.getUserId()); mHardwareKeyboardShortcutController = - new HardwareKeyboardShortcutController(mSettings.getMethodMap(), - mSettings.getUserId()); + new HardwareKeyboardShortcutController(settings.getMethodMap(), + settings.getUserId()); mMenuController = new InputMethodMenuController(this); mBindingController = bindingControllerForTesting != null @@ -1411,7 +1415,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") @UserIdInt int getCurrentImeUserIdLocked() { - return mSettings.getUserId(); + return mCurrentUserId; } private final class InkWindowInitializer implements Runnable { @@ -1447,12 +1451,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private void resetDefaultImeLocked(Context context) { // Do not reset the default (current) IME when it is a 3rd-party IME String selectedMethodId = getSelectedMethodIdLocked(); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); if (selectedMethodId != null - && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) { + && !settings.getMethodMap().get(selectedMethodId).isSystem()) { return; } final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes( - context, mSettings.getEnabledInputMethodList()); + context, settings.getEnabledInputMethodList()); if (suitableImes.isEmpty()) { Slog.i(TAG, "No default found"); return; @@ -1508,7 +1513,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub IInputMethodClientInvoker clientToBeReset) { if (DEBUG) { Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId - + " currentUserId=" + mSettings.getUserId()); + + " currentUserId=" + mCurrentUserId); } maybeInitImeNavbarConfigLocked(newUserId); @@ -1516,8 +1521,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // ContentObserver should be registered again when the user is changed mSettingsObserver.registerContentObserverLocked(newUserId); - mSettings = InputMethodSettings.createEmptyMap(newUserId); - final String defaultImiId = mSettings.getSelectedInputMethod(); + mCurrentUserId = newUserId; + final String defaultImiId = SecureSettingsWrapper.getString( + Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId); if (DEBUG) { Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId @@ -1536,9 +1542,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER); final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId); - mSettings = newSettings; postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */); - if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) { + if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) { // This is the first time of the user switch and // set the current ime to the proper one. resetDefaultImeLocked(mContext); @@ -1548,12 +1553,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (initialUserSwitch) { InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, newUserId), - mSettings.getEnabledInputMethodList()); + newSettings.getEnabledInputMethodList()); } if (DEBUG) { Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId - + " selectedIme=" + mSettings.getSelectedInputMethod()); + + " selectedIme=" + newSettings.getSelectedInputMethod()); } if (mIsInteractive && clientToBeReset != null) { @@ -1576,7 +1581,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (!mSystemReady) { mSystemReady = true; - final int currentUserId = mSettings.getUserId(); + final int currentUserId = mCurrentUserId; mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class); hideStatusBarIconLocked(); @@ -1597,7 +1602,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // the "mImeDrawsImeNavBarResLazyInitFuture" field. synchronized (ImfLock.class) { mImeDrawsImeNavBarResLazyInitFuture = null; - if (currentUserId != mSettings.getUserId()) { + if (currentUserId != mCurrentUserId) { // This means that the current user is already switched to other user // before the background task is executed. In this scenario the relevant // field should already be initialized. @@ -1616,19 +1621,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub UserHandle.ALL, broadcastFilterForAllUsers, null, null, Context.RECEIVER_EXPORTED); - final String defaultImiId = mSettings.getSelectedInputMethod(); + final String defaultImiId = SecureSettingsWrapper.getString( + Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId); final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, currentUserId, AdditionalSubtypeMapRepository.get(currentUserId), DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(currentUserId, newSettings); - mSettings = newSettings; postInputMethodSettingUpdatedLocked( !imeSelectedOnBoot /* resetDefaultEnabledIme */); updateFromSettingsLocked(true); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), - mSettings.getEnabledInputMethodList()); + newSettings.getEnabledInputMethodList()); } } } @@ -1675,7 +1680,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mSettings.getUserId(), null); + mCurrentUserId, null); if (resolvedUserIds.length != 1) { return Collections.emptyList(); } @@ -1698,7 +1703,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mSettings.getUserId(), null); + mCurrentUserId, null); if (resolvedUserIds.length != 1) { return Collections.emptyList(); } @@ -1726,7 +1731,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // Check if selected IME of current user supports handwriting. - if (userId == mSettings.getUserId()) { + if (userId == mCurrentUserId) { return mBindingController.supportsStylusHandwriting() && (!connectionless || mBindingController.supportsConnectionlessStylusHandwriting()); @@ -1774,15 +1779,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId, int callingUid) { - final ArrayList<InputMethodInfo> methodList; - final InputMethodSettings settings; - if (userId == mSettings.getUserId()) { - methodList = mSettings.getEnabledInputMethodList(); - settings = mSettings; - } else { - settings = InputMethodSettingsRepository.get(userId); - methodList = settings.getEnabledInputMethodList(); - } + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList(); // filter caller's access to input methods methodList.removeIf(imi -> !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)); @@ -2020,7 +2018,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final boolean restarting = !initial; final Binder startInputToken = new Binder(); - final StartInputInfo info = new StartInputInfo(mSettings.getUserId(), + final StartInputInfo info = new StartInputInfo(mCurrentUserId, getCurTokenLocked(), mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.mUid), @@ -2034,9 +2032,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // same-user scenarios. // That said ignoring cross-user scenario will never affect IMEs that do not have // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. - if (mSettings.getUserId() == UserHandle.getUserId( + if (mCurrentUserId == UserHandle.getUserId( mCurClient.mUid)) { - mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(), + mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()), mCurClient.mUid, true /* direct */); } @@ -2059,7 +2057,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } String curId = getCurIdLocked(); - final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId); + final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId) + .getMethodMap().get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = @@ -2239,17 +2238,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return currentMethodId; } + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final int oldDeviceId = mDeviceIdToShowIme; mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme); if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) { if (oldDeviceId == DEVICE_ID_DEFAULT) { return currentMethodId; } - final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod(); + final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod(); if (DEBUG) { Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId); } - mSettings.putSelectedDefaultDeviceInputMethod(null); + settings.putSelectedDefaultDeviceInputMethod(null); return defaultDeviceMethodId; } @@ -2257,7 +2257,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId); if (Objects.equals(deviceMethodId, currentMethodId)) { return currentMethodId; - } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) { + } else if (!settings.getMethodMap().containsKey(deviceMethodId)) { if (DEBUG) { Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme + " because its custom input method is not available: " + deviceMethodId); @@ -2269,7 +2269,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) { Slog.v(TAG, "Storing default device input method " + currentMethodId); } - mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId); + settings.putSelectedDefaultDeviceInputMethod(currentMethodId); } if (DEBUG) { Slog.v(TAG, "Switching current input method from " + currentMethodId @@ -2299,7 +2299,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) { return false; } - final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId); + final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId) + .getMethodMap().get(selectedMethodId); if (imi == null) { return false; } @@ -2643,7 +2644,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } else if (packageName != null) { if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); final PackageManager userAwarePackageManager = - getPackageManagerForUser(mContext, mSettings.getUserId()); + getPackageManagerForUser(mContext, mCurrentUserId); ApplicationInfo applicationInfo = null; try { applicationInfo = userAwarePackageManager.getApplicationInfo(packageName, @@ -2705,7 +2706,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() - && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) { + && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) { return false; } if ((visibility & InputMethodService.IME_ACTIVE) == 0 @@ -2722,7 +2723,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } - List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter( + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter( InputMethodInfo::shouldShowInInputMethodPicker); final int numImes = imes.size(); if (numImes > 2) return true; @@ -2734,7 +2736,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub for (int i = 0; i < numImes; ++i) { final InputMethodInfo imi = imes.get(i); final List<InputMethodSubtype> subtypes = - mSettings.getEnabledInputMethodSubtypeList(imi, true); + settings.getEnabledInputMethodSubtypeList(imi, true); final int subtypeCount = subtypes.size(); if (subtypeCount == 0) { ++nonAuxCount; @@ -2845,12 +2847,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + " inv: " + (vis & InputMethodService.IME_INVISIBLE) + " displayId: " + mCurTokenDisplayId); } + final IBinder focusedWindowToken = mImeBindingState != null + ? mImeBindingState.mFocusedWindow : null; + final Boolean windowPerceptible = focusedWindowToken != null + ? mFocusedWindowPerceptible.get(focusedWindowToken) : null; // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure // all updateSystemUi happens on system privilege. final long ident = Binder.clearCallingIdentity(); try { - if (!mCurPerceptible) { + if (windowPerceptible != null && !windowPerceptible) { if ((vis & InputMethodService.IME_VISIBLE) != 0) { vis &= ~InputMethodService.IME_VISIBLE; vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; @@ -2882,11 +2888,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); if (enabledMayChange) { final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext, - mSettings.getUserId()); + settings.getUserId()); - List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList(); + List<InputMethodInfo> enabled = settings.getEnabledInputMethodList(); for (int i = 0; i < enabled.size(); i++) { // We allow the user to select "disabled until used" apps, so if they // are enabling one of those here we now need to make it enabled. @@ -2913,20 +2920,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) { String ime = SecureSettingsWrapper.getString( - Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId()); + Settings.Secure.DEFAULT_INPUT_METHOD, null, settings.getUserId()); String defaultDeviceIme = SecureSettingsWrapper.getString( - Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId()); + Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId()); if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) { if (DEBUG) { Slog.v(TAG, "Current input method " + ime + " differs from the stored default" - + " device input method for user " + mSettings.getUserId() + + " device input method for user " + settings.getUserId() + " - restoring " + defaultDeviceIme); } SecureSettingsWrapper.putString( Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme, - mSettings.getUserId()); + settings.getUserId()); SecureSettingsWrapper.putString( - Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId()); + Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId()); } } @@ -2934,14 +2941,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // ENABLED_INPUT_METHODS is taking care of keeping them correctly in // sync, so we will never have a DEFAULT_INPUT_METHOD that is not // enabled. - String id = mSettings.getSelectedInputMethod(); + String id = settings.getSelectedInputMethod(); // There is no input method selected, try to choose new applicable input method. if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { - id = mSettings.getSelectedInputMethod(); + id = settings.getSelectedInputMethod(); } if (!TextUtils.isEmpty(id)) { try { - setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id)); + setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id)); } catch (IllegalArgumentException e) { Slog.w(TAG, "Unknown input method from prefs: " + id, e); resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED); @@ -2952,18 +2959,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // TODO: Instantiate mSwitchingController for each user. - if (mSettings.getUserId() == mSwitchingController.getUserId()) { - mSwitchingController.resetCircularListLocked(mSettings.getMethodMap()); + if (settings.getUserId() == mSwitchingController.getUserId()) { + mSwitchingController.resetCircularListLocked(settings.getMethodMap()); } else { mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, mSettings.getMethodMap(), mSettings.getUserId()); + mContext, settings.getMethodMap(), settings.getUserId()); } // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { - mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap()); + if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { + mHardwareKeyboardShortcutController.reset(settings.getMethodMap()); } else { mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - mSettings.getMethodMap(), mSettings.getUserId()); + settings.getMethodMap(), settings.getUserId()); } sendOnNavButtonFlagsChangedLocked(); } @@ -2987,14 +2994,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void setInputMethodLocked(String id, int subtypeId, int deviceId) { - InputMethodInfo info = mSettings.getMethodMap().get(id); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + InputMethodInfo info = settings.getMethodMap().get(id); if (info == null) { throw getExceptionForUnknownImeId(id); } // See if we need to notify a subtype change within the same IME. if (id.equals(getSelectedMethodIdLocked())) { - final int userId = mSettings.getUserId(); + final int userId = settings.getUserId(); final int subtypeCount = info.getSubtypeCount(); if (subtypeCount <= 0) { notifyInputMethodSubtypeChangedLocked(userId, info, null); @@ -3035,7 +3043,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // method is a custom one specific to a virtual device. So only update the settings // entry used to restore the default device input method once we want to show the IME // back on the default device. - mSettings.putSelectedDefaultDeviceInputMethod(id); + settings.putSelectedDefaultDeviceInputMethod(id); return; } IInputMethodInvoker curMethod = getCurMethodLocked(); @@ -3315,11 +3323,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Binder.withCleanCallingIdentity(() -> { Objects.requireNonNull(windowToken, "windowToken must not be null"); synchronized (ImfLock.class) { + Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken); if (mImeBindingState.mFocusedWindow != windowToken - || mCurPerceptible == perceptible) { + || (windowPerceptible != null && windowPerceptible == perceptible)) { return; } - mCurPerceptible = perceptible; + mFocusedWindowPerceptible.put(windowToken, windowPerceptible); updateSystemUiLocked(); } }); @@ -3545,7 +3554,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return InputBindResult.USER_SWITCHING; } final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds( - mSettings.getUserId(), false /* enabledOnly */); + mCurrentUserId, false /* enabledOnly */); for (int profileId : profileIdsWithDisabled) { if (profileId == userId) { scheduleSwitchUserTaskLocked(userId, cs.mClient); @@ -3591,10 +3600,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // Verify if caller is a background user. - final int currentUserId = mSettings.getUserId(); - if (userId != currentUserId) { + if (userId != mCurrentUserId) { if (ArrayUtils.contains( - mUserManagerInternal.getProfileIds(currentUserId, false), userId)) { + mUserManagerInternal.getProfileIds(mCurrentUserId, false), + userId)) { // cross-profile access is always allowed here to allow // profile-switching. scheduleSwitchUserTaskLocked(userId, cs.mClient); @@ -3697,7 +3706,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo); - mCurPerceptible = true; + mFocusedWindowPerceptible.put(windowToken, true); // We want to start input before showing the IME, but after closing // it. We want to do this after closing it to help the IME disappear @@ -3783,7 +3792,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) { return true; } - if (mSettings.getUserId() != UserHandle.getUserId(uid)) { + if (mCurrentUserId != UserHandle.getUserId(uid)) { return false; } if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid( @@ -3851,9 +3860,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!calledWithValidTokenLocked(token)) { return; } - final InputMethodInfo imi = mSettings.getMethodMap().get(id); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final InputMethodInfo imi = settings.getMethodMap().get(id); if (imi == null || !canCallerAccessInputMethod( - imi.getPackageName(), callingUid, userId, mSettings)) { + imi.getPackageName(), callingUid, userId, settings)) { throw getExceptionForUnknownImeId(id); } setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID); @@ -3869,9 +3879,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!calledWithValidTokenLocked(token)) { return; } - final InputMethodInfo imi = mSettings.getMethodMap().get(id); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final InputMethodInfo imi = settings.getMethodMap().get(id); if (imi == null || !canCallerAccessInputMethod( - imi.getPackageName(), callingUid, userId, mSettings)) { + imi.getPackageName(), callingUid, userId, settings)) { throw getExceptionForUnknownImeId(id); } if (subtype != null) { @@ -3889,10 +3900,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!calledWithValidTokenLocked(token)) { return false; } - final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype(); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype(); final InputMethodInfo lastImi; if (lastIme != null) { - lastImi = mSettings.getMethodMap().get(lastIme.first); + lastImi = settings.getMethodMap().get(lastIme.first); } else { lastImi = null; } @@ -3916,7 +3928,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // This is a safety net. If the currentSubtype can't be added to the history // and the framework couldn't find the last ime, we will make the last ime be // the most applicable enabled keyboard subtype of the system imes. - final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList(); + final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList(); if (enabled != null) { final int enabledCount = enabled.size(); final String locale; @@ -3924,7 +3936,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) { locale = mCurrentSubtype.getLocale(); } else { - locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString(); + locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString(); } for (int i = 0; i < enabledCount; ++i) { final InputMethodInfo imi = enabled.get(i); @@ -3971,8 +3983,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()), + onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype); if (nextSubtype == null) { return false; @@ -3988,9 +4001,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!calledWithValidTokenLocked(token)) { return false; } + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( false /* onlyCurrentIme */, - mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype); + settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype); return nextSubtype != null; } } @@ -4002,10 +4016,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { - if (mSettings.getUserId() == userId) { - return mSettings.getLastInputMethodSubtype(); - } - return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype(); } } @@ -4037,7 +4047,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); - final boolean isCurrentUser = (mSettings.getUserId() == userId); + final boolean isCurrentUser = (mCurrentUserId == userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap( imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid); @@ -4051,7 +4061,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isCurrentUser) { final long ident = Binder.clearCallingIdentity(); try { - mSettings = newSettings; postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); } finally { Binder.restoreCallingIdentity(ident); @@ -4081,7 +4090,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final long ident = Binder.clearCallingIdentity(); try { synchronized (ImfLock.class) { - final boolean currentUser = (mSettings.getUserId() == userId); + final boolean currentUser = (mCurrentUserId == userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) { return; @@ -4432,11 +4441,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } return; } - if (mSettings.getUserId() != mSwitchingController.getUserId()) { + if (mCurrentUserId != mSwitchingController.getUserId()) { return; } - final InputMethodInfo imi = - mSettings.getMethodMap().get(getSelectedMethodIdLocked()); + final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId) + .getMethodMap().get(getSelectedMethodIdLocked()); if (imi != null) { mSwitchingController.onUserActionLocked(imi, mCurrentSubtype); } @@ -4496,8 +4505,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return; } else { // Called with current IME's token. - if (mSettings.getMethodMap().get(id) != null - && mSettings.getEnabledInputMethodListWithFilter( + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + if (settings.getMethodMap().get(id) != null + && settings.getEnabledInputMethodListWithFilter( (info) -> info.getId().equals(id)).isEmpty()) { throw new IllegalStateException("Requested IME is not enabled: " + id); } @@ -4676,21 +4686,23 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } synchronized (ImfLock.class) { + final InputMethodSettings settings = + InputMethodSettingsRepository.get(mCurrentUserId); final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked() - && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId()); - final String lastInputMethodId = mSettings.getSelectedInputMethod(); + && mWindowManagerInternal.isKeyguardSecure(settings.getUserId()); + final String lastInputMethodId = settings.getSelectedInputMethod(); int lastInputMethodSubtypeId = - mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId); + settings.getSelectedInputMethodSubtypeId(lastInputMethodId); final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController .getSortedInputMethodAndSubtypeList( showAuxSubtypes, isScreenLocked, true /* forImeMenu */, - mContext, mSettings.getMethodMap(), mSettings.getUserId()); + mContext, settings.getMethodMap(), settings.getUserId()); if (imList.isEmpty()) { Slog.w(TAG, "Show switching menu failed, imList is empty," + " showAuxSubtypes: " + showAuxSubtypes + " isScreenLocked: " + isScreenLocked - + " userId: " + mSettings.getUserId()); + + " userId: " + settings.getUserId()); return false; } @@ -4876,8 +4888,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean chooseNewDefaultIMELocked() { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME( - mSettings.getEnabledInputMethodList()); + settings.getEnabledInputMethodList()); if (imi != null) { if (DEBUG) { Slog.d(TAG, "New default IME was selected: " + imi.getId()); @@ -4991,6 +5004,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mMethodMapUpdateCount++; mMyPackageMonitor.clearKnownImePackageNamesLocked(); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + // Construct the set of possible IME packages for onPackageChanged() to avoid false // negatives when the package state remains to be the same but only the component state is // changed. @@ -5001,7 +5016,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final List<ResolveInfo> allInputMethodServices = mContext.getPackageManager().queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE), - PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId()); + PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId()); final int numImes = allInputMethodServices.size(); for (int i = 0; i < numImes; ++i) { final ServiceInfo si = allInputMethodServices.get(i).serviceInfo; @@ -5016,11 +5031,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!resetDefaultEnabledIme) { boolean enabledImeFound = false; boolean enabledNonAuxImeFound = false; - final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList(); + final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList(); final int numImes = enabledImes.size(); for (int i = 0; i < numImes; ++i) { final InputMethodInfo imi = enabledImes.get(i); - if (mSettings.getMethodMap().containsKey(imi.getId())) { + if (settings.getMethodMap().containsKey(imi.getId())) { enabledImeFound = true; if (!imi.isAuxiliaryIme()) { enabledNonAuxImeFound = true; @@ -5044,7 +5059,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) { final ArrayList<InputMethodInfo> defaultEnabledIme = - InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(), + InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(), reenableMinimumNonAuxSystemImes); final int numImes = defaultEnabledIme.size(); for (int i = 0; i < numImes; ++i) { @@ -5056,9 +5071,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } - final String defaultImiId = mSettings.getSelectedInputMethod(); + final String defaultImiId = settings.getSelectedInputMethod(); if (!TextUtils.isEmpty(defaultImiId)) { - if (!mSettings.getMethodMap().containsKey(defaultImiId)) { + if (!settings.getMethodMap().containsKey(defaultImiId)) { Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); if (chooseNewDefaultIMELocked()) { updateInputMethodsFromSettingsLocked(true); @@ -5072,26 +5087,26 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub updateDefaultVoiceImeIfNeededLocked(); // TODO: Instantiate mSwitchingController for each user. - if (mSettings.getUserId() == mSwitchingController.getUserId()) { - mSwitchingController.resetCircularListLocked(mSettings.getMethodMap()); + if (settings.getUserId() == mSwitchingController.getUserId()) { + mSwitchingController.resetCircularListLocked(settings.getMethodMap()); } else { mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, mSettings.getMethodMap(), mSettings.getUserId()); + mContext, settings.getMethodMap(), mCurrentUserId); } // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { - mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap()); + if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { + mHardwareKeyboardShortcutController.reset(settings.getMethodMap()); } else { mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - mSettings.getMethodMap(), mSettings.getUserId()); + settings.getMethodMap(), settings.getUserId()); } sendOnNavButtonFlagsChangedLocked(); // Notify InputMethodListListeners of the new installed InputMethods. - final List<InputMethodInfo> inputMethodList = mSettings.getMethodList(); + final List<InputMethodInfo> inputMethodList = settings.getMethodList(); mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED, - mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget(); + settings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget(); } @GuardedBy("ImfLock.class") @@ -5106,11 +5121,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private void updateDefaultVoiceImeIfNeededLocked() { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); final String systemSpeechRecognizer = mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer); - final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod(); + final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod(); final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme( - mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId); + settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId); if (newSystemVoiceIme == null) { if (DEBUG) { Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked," @@ -5119,7 +5135,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings // does not update the actual Secure Settings until the user is unlocked. if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) { - mSettings.putDefaultVoiceInputMethod(""); + settings.putDefaultVoiceInputMethod(""); // We don't support disabling the voice ime when a package is removed from the // config. } @@ -5132,7 +5148,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme); } setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true); - mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId()); + settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId()); } // ---------------------------------------------------------------------- @@ -5147,8 +5163,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ @GuardedBy("ImfLock.class") private boolean setInputMethodEnabledLocked(String id, boolean enabled) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); if (enabled) { - final String enabledImeIdsStr = mSettings.getEnabledInputMethodsStr(); + final String enabledImeIdsStr = settings.getEnabledInputMethodsStr(); final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds( enabledImeIdsStr, id); if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) { @@ -5156,29 +5173,29 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Nothing to do. The previous state was enabled. return true; } - mSettings.putEnabledInputMethodsStr(newEnabledImeIdsStr); + settings.putEnabledInputMethodsStr(newEnabledImeIdsStr); // Previous state was disabled. return false; } else { - final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings + final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings .getEnabledInputMethodsAndSubtypeList(); StringBuilder builder = new StringBuilder(); - if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId( + if (settings.buildAndPutEnabledInputMethodsStrRemovingId( builder, enabledInputMethodsList, id)) { if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) { // Disabled input method is currently selected, switch to another one. - final String selId = mSettings.getSelectedInputMethod(); + final String selId = settings.getSelectedInputMethod(); if (id.equals(selId) && !chooseNewDefaultIMELocked()) { Slog.i(TAG, "Can't find new IME, unsetting the current input method."); resetSelectedInputMethodAndSubtypeLocked(""); } - } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) { + } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) { // Disabled default device IME while using a virtual device one, choose a // new default one but only update the settings. InputMethodInfo newDefaultIme = InputMethodInfoUtils.getMostApplicableDefaultIME( - mSettings.getEnabledInputMethodList()); - mSettings.putSelectedDefaultDeviceInputMethod( + settings.getEnabledInputMethodList()); + settings.putSelectedDefaultDeviceInputMethod( newDefaultIme == null ? null : newDefaultIme.getId()); } // Previous state was enabled. @@ -5194,29 +5211,30 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly) { - mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(), + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(), mCurrentSubtype); // Set Subtype here if (imi == null || subtypeId < 0) { - mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); mCurrentSubtype = null; } else { if (subtypeId < imi.getSubtypeCount()) { InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); - mSettings.putSelectedSubtype(subtype.hashCode()); + settings.putSelectedSubtype(subtype.hashCode()); mCurrentSubtype = subtype; } else { - mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); + settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); // If the subtype is not specified, choose the most applicable one mCurrentSubtype = getCurrentInputMethodSubtypeLocked(); } } - notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype); + notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype); if (!setSubtypeOnly) { // Set InputMethod here - mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); + settings.putSelectedInputMethod(imi != null ? imi.getId() : ""); } } @@ -5224,13 +5242,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { mDeviceIdToShowIme = DEVICE_ID_DEFAULT; mDisplayIdToShowIme = INVALID_DISPLAY; - mSettings.putSelectedDefaultDeviceInputMethod(null); - InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + settings.putSelectedDefaultDeviceInputMethod(null); + + InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme); int lastSubtypeId = NOT_A_SUBTYPE_ID; // newDefaultIme is empty when there is no candidate for the selected IME. if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { - String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme); + String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme); if (subtypeHashCode != null) { try { lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi, @@ -5257,7 +5277,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { - if (mSettings.getUserId() == userId) { + if (mCurrentUserId == userId) { return getCurrentInputMethodSubtypeLocked(); } @@ -5282,26 +5302,27 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (selectedMethodId == null) { return null; } - final boolean subtypeIsSelected = mSettings.isSubtypeSelected(); - final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final boolean subtypeIsSelected = settings.isSubtypeSelected(); + final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId); if (imi == null || imi.getSubtypeCount() == 0) { return null; } if (!subtypeIsSelected || mCurrentSubtype == null || !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) { - int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId); + int subtypeId = settings.getSelectedInputMethodSubtypeId(selectedMethodId); if (subtypeId == NOT_A_SUBTYPE_ID) { // If there are no selected subtypes, the framework will try to find // the most applicable subtype from explicitly or implicitly enabled // subtypes. List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = - mSettings.getEnabledInputMethodSubtypeList(imi, true); + settings.getEnabledInputMethodSubtypeList(imi, true); // If there is only one explicitly or implicitly enabled subtype, // just returns it. if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { - final String locale = SystemLocaleWrapper.get(mSettings.getUserId()) + final String locale = SystemLocaleWrapper.get(settings.getUserId()) .get(0).toString(); mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype( explicitlyOrImplicitlyEnabledSubtypes, @@ -5330,16 +5351,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) { - if (userId == mSettings.getUserId()) { - if (!mSettings.getMethodMap().containsKey(imeId) - || !mSettings.getEnabledInputMethodList() - .contains(mSettings.getMethodMap().get(imeId))) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + if (userId == mCurrentUserId) { + if (!settings.getMethodMap().containsKey(imeId) + || !settings.getEnabledInputMethodList() + .contains(settings.getMethodMap().get(imeId))) { return false; // IME is not found or not enabled. } setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); return true; } - final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.getMethodMap().containsKey(imeId) || !settings.getEnabledInputMethodList().contains( settings.getMethodMap().get(imeId))) { @@ -5380,8 +5401,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private void switchKeyboardLayoutLocked(int direction) { - final InputMethodInfo currentImi = mSettings.getMethodMap().get( - getSelectedMethodIdLocked()); + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + + final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked()); if (currentImi == null) { return; } @@ -5393,7 +5415,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (nextSubtypeHandle == null) { return; } - final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId()); + final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId()); if (nextImi == null) { return; } @@ -5472,17 +5494,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) { synchronized (ImfLock.class) { - if (userId == mSettings.getUserId()) { - if (!mSettings.getMethodMap().containsKey(imeId)) { - return false; // IME is not found. - } - setInputMethodEnabledLocked(imeId, enabled); - return true; - } final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.getMethodMap().containsKey(imeId)) { return false; // IME is not found. } + if (userId == mCurrentUserId) { + setInputMethodEnabledLocked(imeId, enabled); + return true; + } if (enabled) { final String enabledImeIdsStr = settings.getEnabledInputMethodsStr(); final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds( @@ -5538,9 +5557,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub public void reportImeControl(@Nullable IBinder windowToken) { synchronized (ImfLock.class) { if (mImeBindingState.mFocusedWindow != windowToken) { - // mCurPerceptible was set by the focused window, but it is no longer in - // control, so we reset mCurPerceptible. - mCurPerceptible = true; + // A perceptible value was set for the focused window, but it is no longer in + // control, so we reset the perceptible for the window passed as argument. + // TODO(b/314149476): Investigate whether this logic is still relevant, if not + // then consider removing using concurrent_input_methods feature flag. + mFocusedWindowPerceptible.put(windowToken, true); } } } @@ -5815,8 +5836,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final Printer p = new PrintWriterPrinter(pw); synchronized (ImfLock.class) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); p.println("Current Input Method Manager state:"); - final List<InputMethodInfo> methodList = mSettings.getMethodList(); + final List<InputMethodInfo> methodList = settings.getMethodList(); int numImes = methodList.size(); p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount); for (int i = 0; i < numImes; i++) { @@ -5840,11 +5862,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub p.println(" curSession=" + c.mCurSession); }; mClientController.forAllClients(clientControllerDump); - + p.println(" mCurrentUserId=" + mCurrentUserId); p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); - p.println(" mCurPerceptible=" + mCurPerceptible); + p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); mImeBindingState.dump(" ", p); p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" @@ -5866,8 +5888,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ? Arrays.toString(mStylusIds.toArray()) : "")); p.println(" mSwitchingController:"); mSwitchingController.dump(p); - p.println(" mSettings:"); - mSettings.dump(p, " "); p.println(" mStartInputHistory:"); mStartInputHistory.dump(pw, " "); @@ -6122,7 +6142,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getUserId(), shellCommand.getErrPrintWriter()); + mCurrentUserId, shellCommand.getErrPrintWriter()); try (PrintWriter pr = shellCommand.getOutPrintWriter()) { for (int userId : userIds) { final List<InputMethodInfo> methods = all @@ -6167,7 +6187,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub PrintWriter error = shellCommand.getErrPrintWriter()) { synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getUserId(), shellCommand.getErrPrintWriter()); + mCurrentUserId, shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6226,14 +6246,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub PrintWriter error) { boolean failedToEnableUnknownIme = false; boolean previouslyEnabled = false; - if (userId == mSettings.getUserId()) { - if (enabled && !mSettings.getMethodMap().containsKey(imeId)) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + if (userId == mCurrentUserId) { + if (enabled && !settings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; } else { previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled); } } else { - final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (enabled) { if (!settings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; @@ -6288,7 +6308,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub PrintWriter error = shellCommand.getErrPrintWriter()) { synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getUserId(), shellCommand.getErrPrintWriter()); + mCurrentUserId, shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6328,7 +6348,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { try (PrintWriter out = shellCommand.getOutPrintWriter()) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getUserId(), shellCommand.getErrPrintWriter()); + mCurrentUserId, shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6340,15 +6360,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } final String nextIme; final List<InputMethodInfo> nextEnabledImes; - if (userId == mSettings.getUserId()) { + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + if (userId == mCurrentUserId) { hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); mBindingController.unbindCurrentMethod(); // Enable default IMEs, disable others - var toDisable = mSettings.getEnabledInputMethodList(); + var toDisable = settings.getEnabledInputMethodList(); var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes( - mContext, mSettings.getMethodList()); + mContext, settings.getMethodList()); toDisable.removeAll(defaultEnabled); for (InputMethodInfo info : toDisable) { setInputMethodEnabledLocked(info.getId(), false); @@ -6362,14 +6383,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( - getPackageManagerForUser(mContext, mSettings.getUserId()), - mSettings.getEnabledInputMethodList()); - nextIme = mSettings.getSelectedInputMethod(); - nextEnabledImes = mSettings.getEnabledInputMethodList(); + getPackageManagerForUser(mContext, settings.getUserId()), + settings.getEnabledInputMethodList()); + nextIme = settings.getSelectedInputMethod(); + nextEnabledImes = settings.getEnabledInputMethodList(); } else { - final InputMethodSettings settings = - InputMethodSettingsRepository.get(userId); - nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList()); nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME( diff --git a/services/core/java/com/android/server/inputmethod/Sequence.java b/services/core/java/com/android/server/inputmethod/Sequence.java new file mode 100644 index 000000000000..05e31ce1c682 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/Sequence.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 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.inputmethod; + +import com.android.internal.annotations.GuardedBy; + +/** + * A sequence number utility class that only generate positive numbers. + */ +final class Sequence { + + private final Object mLock = new Object(); + + private int mSequence; + + int getSequenceNumber() { + synchronized (mLock) { + return mSequence; + } + } + + @GuardedBy("ImfLock.class") + void advanceSequenceNumber() { + synchronized (mLock) { + mSequence++; + if (mSequence <= 0) { + mSequence = 1; + } + } + } +} diff --git a/services/core/java/com/android/server/inputmethod/UserData.java b/services/core/java/com/android/server/inputmethod/UserData.java new file mode 100644 index 000000000000..fc2a422e136f --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/UserData.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 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.inputmethod; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.pm.UserInfo; +import android.os.Handler; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +final class UserData { + + @NonNull + private static final SparseArray<UserData> sPerUserMonitor = new SparseArray<>(); + + @UserIdInt + final int mUserId; + + @GuardedBy("ImfLock.class") + final Sequence mSequence = new Sequence(); + + /** + * Not intended to be instantiated. + */ + private UserData(int userId) { + mUserId = userId; + } + + @GuardedBy("ImfLock.class") + static UserData getOrCreate(@UserIdInt int userId) { + UserData monitor = sPerUserMonitor.get(userId); + if (monitor == null) { + monitor = new UserData(userId); + sPerUserMonitor.put(userId, monitor); + } + return monitor; + } + + static void initialize(Handler handler) { + final UserManagerInternal userManagerInternal = + LocalServices.getService(UserManagerInternal.class); + userManagerInternal.addUserLifecycleListener( + new UserManagerInternal.UserLifecycleListener() { + @Override + public void onUserRemoved(UserInfo user) { + final int userId = user.id; + handler.post(() -> { + synchronized (ImfLock.class) { + sPerUserMonitor.remove(userId); + } + }); + } + + @Override + public void onUserCreated(UserInfo user, Object unusedToken) { + final int userId = user.id; + handler.post(() -> { + synchronized (ImfLock.class) { + getOrCreate(userId); + } + }); + } + }); + synchronized (ImfLock.class) { + for (int userId : userManagerInternal.getUserIds()) { + getOrCreate(userId); + } + } + } +} diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 97ce77c71138..18b495bfce5d 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -340,8 +340,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { static final String TAG = NetworkPolicyLogger.TAG; private static final boolean LOGD = NetworkPolicyLogger.LOGD; private static final boolean LOGV = NetworkPolicyLogger.LOGV; - // TODO: b/304347838 - Remove once the feature is in staging. - private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false; /** * No opportunistic quota could be calculated from user data plan or data settings. @@ -1070,8 +1068,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } // The flag is boot-stable. - mBackgroundNetworkRestricted = ALWAYS_RESTRICT_BACKGROUND_NETWORK - && Flags.networkBlockedForTopSleepingAndAbove(); + mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove(); if (mBackgroundNetworkRestricted) { // Firewall rules and UidBlockedState will get updated in // updateRulesForGlobalChangeAL below. diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 4bfd077760e4..4bec61acc38f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2892,17 +2892,20 @@ final class InstallPackageHelper { mPm.notifyPackageChanged(packageName, request.getAppId()); } - // Apply restricted settings on potentially dangerous packages. Needs to happen - // after appOpsManager is notified of the new package - if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE - || request.getPackageSource() - == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { - final int appId = request.getAppId(); - mPm.mHandler.post(() -> { - for (int userId : firstUserIds) { - enableRestrictedSettings(packageName, appId, userId); - } - }); + if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + || !android.security.Flags.extendEcmToAllSettings()) { + // Apply restricted settings on potentially dangerous packages. Needs to happen + // after appOpsManager is notified of the new package + if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE + || request.getPackageSource() + == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { + final int appId = request.getAppId(); + mPm.mHandler.post(() -> { + for (int userId : firstUserIds) { + enableRestrictedSettings(packageName, appId, userId); + } + }); + } } // Log current value of "unknown sources" setting diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 9e4ea6a74da4..3eeeae7dc260 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -148,6 +148,7 @@ import android.os.incremental.V4Signature; import android.os.storage.StorageManager; import android.provider.DeviceConfig; import android.provider.Settings.Global; +import android.service.persistentdata.PersistentDataBlockManager; import android.stats.devicepolicy.DevicePolicyEnums; import android.system.ErrnoException; import android.system.Int64Ref; @@ -2364,8 +2365,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertPreparedAndNotDestroyedLocked("commit of session " + sessionId); assertNoWriteFileTransfersOpenLocked(); - final boolean isSecureFrpEnabled = - Global.getInt(mContext.getContentResolver(), Global.SECURE_FRP_MODE, 0) == 1; + boolean isSecureFrpEnabled; + if (android.security.Flags.frpEnforcement()) { + PersistentDataBlockManager pdbManager = + mContext.getSystemService(PersistentDataBlockManager.class); + if (pdbManager == null) { + // Some devices may not support FRP. In that case, we can't block the install + // accordingly. + isSecureFrpEnabled = false; + } else { + isSecureFrpEnabled = pdbManager.isFactoryResetProtectionActive(); + } + } else { + isSecureFrpEnabled = Global.getInt(mContext.getContentResolver(), + Global.SECURE_FRP_MODE, 0) == 1; + } if (isSecureFrpEnabled && !isSecureFrpInstallAllowed(mContext, Binder.getCallingUid())) { diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index c9fd2610bfb7..19191374f12a 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -217,8 +217,7 @@ public class UserRestrictionsUtils { private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet( UserManager.DISALLOW_RECORD_AUDIO, UserManager.DISALLOW_WALLPAPER, - UserManager.DISALLOW_OEM_UNLOCK, - UserManager.DISALLOW_ADD_PRIVATE_PROFILE + UserManager.DISALLOW_OEM_UNLOCK ); /** @@ -293,7 +292,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_USB_FILE_TRANSFER, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, UserManager.DISALLOW_UNMUTE_MICROPHONE, - UserManager.DISALLOW_CONFIG_DEFAULT_APPS + UserManager.DISALLOW_CONFIG_DEFAULT_APPS, + UserManager.DISALLOW_ADD_PRIVATE_PROFILE ); /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 266418fd5b4a..9e31748385c5 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1506,9 +1506,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void stemPrimaryPress(int count) { - if (DEBUG_INPUT) { - Slog.d(TAG, "stemPrimaryPress: " + count); - } + Slog.d(TAG, "stemPrimaryPress: " + count); if (count == 3) { stemPrimaryTriplePressAction(mTriplePressOnStemPrimaryBehavior); } else if (count == 2) { @@ -1519,22 +1517,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void stemPrimarySinglePressAction(int behavior) { - if (DEBUG_INPUT) { - Slog.d(TAG, "stemPrimarySinglePressAction: behavior=" + behavior); - } + Slog.d(TAG, "stemPrimarySinglePressAction: behavior=" + behavior); if (behavior == SHORT_PRESS_PRIMARY_NOTHING) return; final boolean keyguardActive = mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); if (keyguardActive) { // If keyguarded then notify the keyguard. mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY); + Slog.d(TAG, "stemPrimarySinglePressAction: skip due to keyguard"); return; } switch (behavior) { case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS: - if (DEBUG_INPUT) { - Slog.d(TAG, "Executing stem primary short press action behavior."); - } Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS); allAppsIntent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK @@ -1542,12 +1536,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { startActivityAsUser(allAppsIntent, UserHandle.CURRENT_OR_SELF); break; case SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY: - if (DEBUG_INPUT) { - Slog.d( - TAG, - "Executing stem primary short press action behavior for launching " - + "target activity."); - } if (mPrimaryShortPressTargetActivity != null) { Intent targetActivityIntent = new Intent(); targetActivityIntent.setComponent(mPrimaryShortPressTargetActivity); @@ -1578,13 +1566,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void stemPrimaryDoublePressAction(int behavior) { + Slog.d(TAG, "stemPrimaryDoublePressAction: " + behavior); switch (behavior) { case DOUBLE_PRESS_PRIMARY_NOTHING: break; case DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP: - if (DEBUG_INPUT) { - Slog.d(TAG, "Executing stem primary double press action behavior."); - } final boolean keyguardActive = mKeyguardDelegate == null ? false : mKeyguardDelegate.isShowing(); @@ -1596,13 +1582,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void stemPrimaryTriplePressAction(int behavior) { + Slog.d(TAG, "stemPrimaryTriplePressAction: " + behavior); switch (behavior) { case TRIPLE_PRESS_PRIMARY_NOTHING: break; case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY: - if (DEBUG_INPUT) { - Slog.d(TAG, "Executing stem primary triple press action behavior."); - } mTalkbackShortcutController.toggleTalkback(mCurrentUserId); if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) { performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */ @@ -1614,9 +1598,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void stemPrimaryLongPress(long eventTime) { - if (DEBUG_INPUT) { - Slog.d(TAG, "Executing stem primary long press action behavior."); - } + Slog.d(TAG, "stemPrimaryLongPress: " + mLongPressOnStemPrimaryBehavior); switch (mLongPressOnStemPrimaryBehavior) { case LONG_PRESS_PRIMARY_NOTHING: @@ -1696,10 +1678,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { return mLongPressOnStemPrimaryBehavior != LONG_PRESS_PRIMARY_NOTHING; } + /** Determine whether the device has any stem primary behaviors. */ private boolean hasStemPrimaryBehavior() { + // Read the default stem behaviors from the XML config to determine whether stem primary + // behaviors are supported in this build. If they are supported, then the behaviors may be + // overridden at runtime through their respective Settings overrides. If they are not + // supported, the Settings overrides will not apply. + final int defaultShortPressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior); + final int defaultLongPressOnStemPrimaryBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior); return getMaxMultiPressStemPrimaryCount() > 1 - || hasLongPressOnStemPrimaryBehavior() - || mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING; + || defaultLongPressOnStemPrimaryBehavior != LONG_PRESS_PRIMARY_NOTHING + || defaultShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING; } private void interceptScreenshotChord(int source, long pressDelay) { @@ -2787,6 +2778,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> { + Slog.d(TAG, "StemPrimaryKeyRule: executing deferred onKeyUp"); // Save the info of the focused task on screen. This may be used // later to bring the current focused task back to top. For // example, stem primary triple press enables the A11y interface @@ -3526,6 +3518,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), true /* leftOrTop */); logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); + } else if (event.isAltPressed()) { + setSplitscreenFocus(true /* leftOrTop */); + logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS); } else { logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK); injectBackGesture(event.getDownTime()); @@ -3534,11 +3529,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } break; case KeyEvent.KEYCODE_DPAD_RIGHT: - if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { - moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), - false /* leftOrTop */); - logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); - return true; + if (firstDown && event.isMetaPressed()) { + if (event.isCtrlPressed()) { + moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), + false /* leftOrTop */); + logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); + return true; + } else if (event.isAltPressed()) { + setSplitscreenFocus(false /* leftOrTop */); + logKeyboardSystemsEvent(event, KeyboardLogEvent.CHANGE_SPLITSCREEN_FOCUS); + return true; + } } break; case KeyEvent.KEYCODE_SLASH: @@ -4398,6 +4399,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private void setSplitscreenFocus(boolean leftOrTop) { + StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); + if (statusbar != null) { + statusbar.setSplitscreenFocus(leftOrTop); + } + } + void launchHomeFromHotKey(int displayId) { launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index b50e2bf317b5..6ff8cf3cc2c8 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -56,6 +56,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.SensorManager; import android.hardware.SystemSensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.DisplayManagerInternal; @@ -7152,9 +7153,10 @@ public final class PowerManagerService extends SystemService private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; @Override - public void onStateChanged(int deviceState) { - if (mDeviceState != deviceState) { - mDeviceState = deviceState; + public void onDeviceStateChanged(@NonNull DeviceState deviceState) { + int stateIdentifier = deviceState.getIdentifier(); + if (mDeviceState != stateIdentifier) { + mDeviceState = stateIdentifier; // Device-state interactions are applied to the default display so that they // are reflected only with the default power group. userActivityInternal(Display.DEFAULT_DISPLAY, mClock.uptimeMillis(), diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index c73f89c4731e..f7c236afda20 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -238,6 +238,14 @@ public interface StatusBarManagerInternal { void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop); /** + * Change the split screen focus to the left / top app or the right / bottom app based on + * {@param leftOrTop}. + * + * @see com.android.internal.statusbar.IStatusBar#setSplitscreenFocus + */ + void setSplitscreenFocus(boolean leftOrTop); + + /** * Shows the media output switcher dialog. * * @param packageName of the session for which the output switcher is shown. diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 214dbe01aee5..7b3e23776a55 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -830,6 +830,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override + public void setSplitscreenFocus(boolean leftOrTop) { + IStatusBar bar = mBar; + if (bar != null) { + try { + bar.setSplitscreenFocus(leftOrTop); + } catch (RemoteException ex) { } + } + } + @Override public void enterDesktop(int displayId) { IStatusBar bar = mBar; if (bar != null) { diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index 7206c0377bf7..ecd140e23ab6 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -89,7 +89,7 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { @NonNull private final TelephonyManager mTelephonyManager; @NonNull private final SubscriptionManager mSubscriptionManager; - @NonNull private final CarrierConfigManager mCarrierConfigManager; + @Nullable private final CarrierConfigManager mCarrierConfigManager; @NonNull private final ActiveDataSubscriptionIdListener mActiveDataSubIdListener; @@ -158,8 +158,10 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { mSubscriptionManager.addOnSubscriptionsChangedListener( executor, mSubscriptionChangedListener); mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener); - mCarrierConfigManager.registerCarrierConfigChangeListener(executor, - mCarrierConfigChangeListener); + if (mCarrierConfigManager != null) { + mCarrierConfigManager.registerCarrierConfigChangeListener(executor, + mCarrierConfigChangeListener); + } registerCarrierPrivilegesCallbacks(); } @@ -200,7 +202,10 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { mContext.unregisterReceiver(this); mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener); mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener); - mCarrierConfigManager.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener); + if (mCarrierConfigManager != null) { + mCarrierConfigManager.unregisterCarrierConfigChangeListener( + mCarrierConfigChangeListener); + } unregisterCarrierPrivilegesCallbacks(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 963b4cba5069..4ec2f576efa9 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -75,7 +75,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.ANIMATE; import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_TASK_MSG; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; -import static com.android.server.wm.ClientLifecycleManager.shouldDispatchCompatClientTransactionIndependently; +import static com.android.server.wm.ClientLifecycleManager.shouldDispatchLaunchActivityItemIndependently; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; @@ -953,7 +953,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } // Schedule transaction. - if (shouldDispatchCompatClientTransactionIndependently(r.mTargetSdk)) { + if (shouldDispatchLaunchActivityItemIndependently(r.info.packageName, r.getUid())) { // LaunchActivityItem has @UnsupportedAppUsage usages. // Guard the bundleClientTransactionFlag feature with targetSDK on Android 15+. // To not bundle the transaction, dispatch the pending before schedule new diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 1a63f14e1b8c..5184e49385b2 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -228,11 +228,8 @@ public class AppTransition implements Dump { private int mAppTransitionState = APP_STATE_IDLE; private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>(); - private KeyguardExitAnimationStartListener mKeyguardExitAnimationStartListener; private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor(); - private final boolean mGridLayoutRecentsEnabled; - private final int mDefaultWindowAnimationStyleResId; private boolean mOverrideTaskTransition; @@ -249,8 +246,6 @@ public class AppTransition implements Dump { mTransitionAnimation = new TransitionAnimation( context, ProtoLog.isEnabled(WM_DEBUG_ANIM), TAG); - mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false); - final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes( com.android.internal.R.styleable.Window); mDefaultWindowAnimationStyleResId = windowStyle.getResourceId( @@ -493,11 +488,6 @@ public class AppTransition implements Dump { mListeners.remove(listener); } - void registerKeygaurdExitAnimationStartListener( - KeyguardExitAnimationStartListener listener) { - mKeyguardExitAnimationStartListener = listener; - } - public void notifyAppTransitionFinishedLocked(IBinder token) { for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onAppTransitionFinishedLocked(token); @@ -1595,14 +1585,6 @@ public class AppTransition implements Dump { return mNextAppTransitionRequests.contains(transit); } - /** - * @return whether the transition should show the thumbnail being scaled down. - */ - private boolean shouldScaleDownThumbnailTransition(int uiMode, int orientation) { - return mGridLayoutRecentsEnabled - || orientation == Configuration.ORIENTATION_PORTRAIT; - } - private void handleAppTransitionTimeout() { synchronized (mService.mGlobalLock) { final DisplayContent dc = mDisplayContent; diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 6ed896751bb3..e7fb26550886 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -642,7 +642,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume // by drawing the rotated content before applying projection transaction of display. // And it will fade in after the display transition is finished. if (mTransitionOp == OP_APP_SWITCH && !mIsStartTransactionCommitted - && canBeAsync(w.mToken)) { + && canBeAsync(w.mToken) && !mDisplayContent.hasFixedRotationTransientLaunch()) { hideImmediately(w.mToken, Operation.ACTION_FADE); if (DEBUG) Slog.d(TAG, "Hide on finishDrawing " + w.mToken.getTopChild()); } diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java index 816fe1dbae94..e396f2b0e18f 100644 --- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java +++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java @@ -18,14 +18,19 @@ package com.android.server.wm; import android.annotation.NonNull; import android.app.IApplicationThread; +import android.app.compat.CompatChanges; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.LaunchActivityItem; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.Slog; @@ -42,6 +47,15 @@ class ClientLifecycleManager { private static final String TAG = "ClientLifecycleManager"; + /** + * To prevent any existing apps from having app compat issue with the non-sdk usages of + * {@link ClientTransaction#getActivityToken()}, only allow bundling {@link LaunchActivityItem} + * for apps with targetSDK of V and above. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + private static final long ENABLE_BUNDLE_LAUNCH_ACTIVITY_ITEM = 324203798L; + /** Mapping from client process binder to its pending transaction. */ @VisibleForTesting final ArrayMap<IBinder, ClientTransaction> mPendingTransactions = new ArrayMap<>(); @@ -251,16 +265,11 @@ class ClientLifecycleManager { && !mWms.mWindowPlacerLocked.isInLayout(); } - /** - * Guards the bundleClientTransactionFlag feature with targetSDK on Android 15+. - * - * Suppressing because it can't guard with @EnabledSince on VANILLA_ICE_CREAM yet since the - * version is not published. - * - * TODO(b/324203798): update in V - */ - @SuppressWarnings("AndroidFrameworkCompatChange") - static boolean shouldDispatchCompatClientTransactionIndependently(int appTargetSdk) { - return appTargetSdk <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + /** Guards bundling {@link LaunchActivityItem} with targetSDK. */ + static boolean shouldDispatchLaunchActivityItemIndependently( + @NonNull String appPackageName, int appUid) { + return !CompatChanges.isChangeEnabled(ENABLE_BUNDLE_LAUNCH_ACTIVITY_ITEM, + appPackageName, + UserHandle.getUserHandleForUid(appUid)); } } diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index b616d24cfebb..802051660c0e 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -157,6 +157,10 @@ final class ContentRecorder implements WindowContainerListener { } } + void onMirrorOutputSurfaceOrientationChanged() { + onConfigurationChanged(mLastOrientation, mLastWindowingMode); + } + /** * Handle a configuration change on the display content, and resize recording if needed. * @param lastOrientation the prior orientation of the configuration diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 837d08b33756..1dcfde464a97 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1950,6 +1950,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp && mFixedRotationLaunchingApp != mFixedRotationTransitionListener.mAnimatingRecents; } + /** It usually means whether the recents activity is launching with a different rotation. */ + boolean hasFixedRotationTransientLaunch() { + return mFixedRotationLaunchingApp != null + && mTransitionController.isTransientLaunch(mFixedRotationLaunchingApp); + } + boolean isFixedRotationLaunchingApp(ActivityRecord r) { return mFixedRotationLaunchingApp == r; } @@ -3515,6 +3521,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void enableHighPerfTransition(boolean enable) { + if (!mWmService.mSupportsHighPerfTransitions) { + return; + } if (!explicitRefreshRateHints()) { if (enable) { getPendingTransaction().setEarlyWakeupStart(); @@ -6835,6 +6844,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mContentRecorder; } + void onMirrorOutputSurfaceOrientationChanged() { + if (mContentRecorder != null) { + mContentRecorder.onMirrorOutputSurfaceOrientationChanged(); + } + } + /** * Pause the recording session. */ @@ -7013,9 +7028,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean shouldDeferRotation() { ActivityRecord source = null; if (mTransitionController.isShellTransitionsEnabled()) { - final ActivityRecord r = mFixedRotationLaunchingApp; - if (r != null && mTransitionController.isTransientLaunch(r)) { - source = r; + if (hasFixedRotationTransientLaunch()) { + source = mFixedRotationLaunchingApp; } } else if (mAnimatingRecents != null && !hasTopFixedRotationLaunchingApp()) { source = mAnimatingRecents; diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 384b91a07d4e..4a97128c27b6 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.Display.TYPE_EXTERNAL; +import static android.view.Display.TYPE_OVERLAY; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; @@ -915,6 +917,11 @@ public class DisplayRotation { + " for " + mDisplayContent); userRotation = Surface.ROTATION_0; } + final int userRotationOverride = getUserRotationOverride(); + if (userRotationOverride != 0) { + userRotationMode = WindowManagerPolicy.USER_ROTATION_LOCKED; + userRotation = userRotationOverride; + } mUserRotationMode = userRotationMode; mUserRotation = userRotation; } @@ -965,6 +972,13 @@ public class DisplayRotation { if (changed) { mService.updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); + // ContentRecorder.onConfigurationChanged and Device.setProjectionLocked are called + // during updateRotation above. But onConfigurationChanged is called before + // Device.setProjectionLocked, which means that the onConfigurationChanged will + // not have the new rotation when it calls getDisplaySurfaceDefaultSize. + // To make sure that mirroring takes the new rotation of the output surface + // into account we need to call onConfigurationChanged again. + mDisplayContent.onMirrorOutputSurfaceOrientationChanged(); } } @@ -1780,6 +1794,23 @@ public class DisplayRotation { } } + @Surface.Rotation + private int getUserRotationOverride() { + final int userRotationOverride = SystemProperties.getInt("persist.demo.userrotation", + Surface.ROTATION_0); + if (userRotationOverride == Surface.ROTATION_0) { + return userRotationOverride; + } + + final var display = mDisplayContent.mDisplay; + if (display.getType() == TYPE_EXTERNAL || display.getType() == TYPE_OVERLAY) { + // TODO b/329442350 add chromecast virtual displays here + return userRotationOverride; + } + + return Surface.ROTATION_0; + } + @VisibleForTesting long uptimeMillis() { return SystemClock.uptimeMillis(); diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index dd146420c748..33588a03b8e1 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -364,11 +364,6 @@ class RecentTasks { com.android.internal.R.integer.config_minNumVisibleRecentTasks_lowRam); mMaxNumVisibleTasks = res.getInteger( com.android.internal.R.integer.config_maxNumVisibleRecentTasks_lowRam); - } else if (SystemProperties.getBoolean("ro.recents.grid", false)) { - mMinNumVisibleTasks = res.getInteger( - com.android.internal.R.integer.config_minNumVisibleRecentTasks_grid); - mMaxNumVisibleTasks = res.getInteger( - com.android.internal.R.integer.config_maxNumVisibleRecentTasks_grid); } else { mMinNumVisibleTasks = res.getInteger( com.android.internal.R.integer.config_minNumVisibleRecentTasks); diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3fb5998d3d93..a2f6fb4c08ad 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -336,7 +336,7 @@ class WallpaperController { for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); token.setVisibility(false); - if (ProtoLog.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) { + if (token.isVisible()) { ProtoLog.d(WM_DEBUG_WALLPAPER, "Hiding wallpaper %s from %s target=%s prev=%s callers=%s", token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget, @@ -527,15 +527,15 @@ class WallpaperController { if ((mLastWallpaperTimeoutTime + WALLPAPER_TIMEOUT_RECOVERY) < start) { try { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Waiting for offset complete..."); + ProtoLog.v(WM_DEBUG_WALLPAPER, "Waiting for offset complete..."); mService.mGlobalLock.wait(WALLPAPER_TIMEOUT); } catch (InterruptedException e) { } - if (DEBUG_WALLPAPER) Slog.v(TAG, "Offset complete!"); + ProtoLog.v(WM_DEBUG_WALLPAPER, "Offset complete!"); if ((start + WALLPAPER_TIMEOUT) < SystemClock.uptimeMillis()) { - Slog.i(TAG, "Timeout waiting for wallpaper to offset: " - + wallpaperWin); + ProtoLog.v(WM_DEBUG_WALLPAPER, + "Timeout waiting for wallpaper to offset: %s", + wallpaperWin); mLastWallpaperTimeoutTime = start; } } @@ -891,10 +891,6 @@ class WallpaperController { // The window is visible to the compositor...but is it visible to the user? // That is what the wallpaper cares about. final boolean visible = token != null; - if (DEBUG_WALLPAPER) { - Slog.v(TAG, "Wallpaper visibility: " + visible + " at display " - + mDisplayContent.getDisplayId()); - } if (visible) { if (mWallpaperTarget.mWallpaperX >= 0) { @@ -915,10 +911,9 @@ class WallpaperController { updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked()); - if (DEBUG_WALLPAPER) { - Slog.v(TAG, "adjustWallpaperWindows: wallpaper visibility " + visible - + ", lock visibility " + mDisplayContent.isKeyguardLocked()); - } + ProtoLog.v(WM_DEBUG_WALLPAPER, + "Wallpaper at display %d - visibility: %b, keyguardLocked: %b", + mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked()); if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) { mLastFrozen = mFindResults.isWallpaperTargetForLetterbox; @@ -927,7 +922,7 @@ class WallpaperController { /* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false); } - ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s", + ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper target=%s prev=%s", mWallpaperTarget, mPrevWallpaperTarget); } @@ -973,11 +968,9 @@ class WallpaperController { WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION); } - if (DEBUG_WALLPAPER) { - Slog.v(TAG, - "Wallpaper should be visible but has not been drawn yet. " - + "mWallpaperDrawState=" + mWallpaperDrawState); - } + ProtoLog.v(WM_DEBUG_WALLPAPER, + "Wallpaper should be visible but has not been drawn yet. " + + "mWallpaperDrawState=%d", mWallpaperDrawState); break; } } @@ -1210,15 +1203,17 @@ class WallpaperController { boolean isWallpaperTargetForLetterbox = false; void setTopHideWhenLockedWallpaper(WindowState win) { - if (DEBUG_WALLPAPER) { - Slog.v(TAG, "setTopHideWhenLockedWallpaper " + win); + if (mTopWallpaper.mTopHideWhenLockedWallpaper != win) { + ProtoLog.d(WM_DEBUG_WALLPAPER, "New home screen wallpaper: %s, prev: %s", + win, mTopWallpaper.mTopHideWhenLockedWallpaper); } mTopWallpaper.mTopHideWhenLockedWallpaper = win; } void setTopShowWhenLockedWallpaper(WindowState win) { - if (DEBUG_WALLPAPER) { - Slog.v(TAG, "setTopShowWhenLockedWallpaper " + win); + if (mTopWallpaper.mTopShowWhenLockedWallpaper != win) { + ProtoLog.d(WM_DEBUG_WALLPAPER, "New lock/shared screen wallpaper: %s, prev: %s", + win, mTopWallpaper.mTopShowWhenLockedWallpaper); } mTopWallpaper.mTopShowWhenLockedWallpaper = win; } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 77319cc0ba8a..acc63305055b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -620,14 +620,6 @@ public abstract class WindowManagerInternal { public abstract void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener); /** - * Registers a listener to be notified to start the keyguard exit animation. - * - * @param listener The listener to register. - */ - public abstract void registerKeyguardExitAnimationStartListener( - KeyguardExitAnimationStartListener listener); - - /** * Reports that the password for the given user has changed. */ public abstract void reportPasswordChanged(int userId); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 40b1b20909af..207b1bbcea16 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -538,7 +538,7 @@ public class WindowManagerService extends IWindowManager.Stub final boolean mHasPermanentDpad; final long mDrawLockTimeoutMillis; final boolean mAllowAnimationsInLowPowerMode; - + final boolean mSupportsHighPerfTransitions; final boolean mAllowBootMessages; // Indicates whether the Assistant should show on top of the Dream (respectively, above @@ -1181,6 +1181,8 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_allowAnimationsInLowPowerMode); mMaxUiWidth = context.getResources().getInteger( com.android.internal.R.integer.config_maxUiWidth); + mSupportsHighPerfTransitions = context.getResources().getBoolean( + com.android.internal.R.bool.config_deviceSupportsHighPerfTransitions); mDisableTransitionAnimation = context.getResources().getBoolean( com.android.internal.R.bool.config_disableTransitionAnimation); mPerDisplayFocusEnabled = context.getResources().getBoolean( @@ -1192,6 +1194,7 @@ public class WindowManagerService extends IWindowManager.Stub final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources() .getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize) && mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout; + if (mFlags.mInsetsDecoupledConfiguration) { mDecorTypes = 0; mConfigTypes = 0; @@ -8094,15 +8097,6 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void registerKeyguardExitAnimationStartListener( - KeyguardExitAnimationStartListener listener) { - synchronized (mGlobalLock) { - getDefaultDisplayContentLocked().mAppTransition - .registerKeygaurdExitAnimationStartListener(listener); - } - } - - @Override public void reportPasswordChanged(int userId) { mKeyguardDisableHandler.updateKeyguardEnabled(userId); } diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index a6db310f4e63..731184fbc39c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -519,6 +519,9 @@ public class WindowManagerShellCommand extends ShellCommand { case "default": fixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT; break; + case "enabled_if_no_auto_rotation": + fixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION; + break; default: getErrPrintWriter().println("Error: expecting enabled, disabled or default, but we " + "get " + arg); @@ -538,6 +541,9 @@ public class WindowManagerShellCommand extends ShellCommand { case IWindowManager.FIXED_TO_USER_ROTATION_DISABLED: pw.println("disabled"); return 0; + case IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION: + pw.println("enabled_if_no_auto_rotation"); + return 0; case IWindowManager.FIXED_TO_USER_ROTATION_ENABLED: pw.println("enabled"); return 0; @@ -1494,7 +1500,8 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" Print or set user rotation mode and user rotation."); pw.println(" dump-visible-window-views"); pw.println(" Dumps the encoded view hierarchies of visible windows"); - pw.println(" fixed-to-user-rotation [-d DISPLAY_ID] [enabled|disabled|default]"); + pw.println(" fixed-to-user-rotation [-d DISPLAY_ID] [enabled|disabled|default"); + pw.println(" |enabled_if_no_auto_rotation]"); pw.println(" Print or set rotating display for app requested orientation."); pw.println(" set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]"); pw.println(" get-ignore-orientation-request [-d DISPLAY_ID] "); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 7e6f5ac7497e..d967cde84cbf 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1770,6 +1770,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub throw new RuntimeException("Reparenting leaf Tasks is not supported now. " + task); } } else { + if (hop.getToTop() && task.isRootTask()) { + final ActivityRecord pipCandidate = task.findEnterPipOnTaskSwitchCandidate( + task.getDisplayArea().getTopRootTask()); + task.enableEnterPipOnTaskSwitch(pipCandidate, task, null /* toFrontActivity */, + null /* options */); + } + task.getParent().positionChildAt( hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, task, false /* includingParents */); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 46bac161f0a6..2b337aed5b87 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5700,7 +5700,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // window becomes visible while the sync group is still active. return true; } - if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn()) { + if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn() + && mPrepareSyncSeqId <= 0) { // Complete the sync state immediately for a drawn window that doesn't need to redraw. onSyncFinishedDrawing(); } diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp index 0a9ce2fed7fc..9dc70afad9d9 100644 --- a/services/core/jni/com_android_server_UsbDeviceManager.cpp +++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp @@ -108,18 +108,6 @@ static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv* /* env return (result == 1); } -static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv* /* env */, jobject /* thiz */) -{ - int fd = open(DRIVER_NAME, O_RDWR); - if (fd < 0) { - ALOGE("could not open %s", DRIVER_NAME); - return false; - } - int result = ioctl(fd, ACCESSORY_GET_AUDIO_MODE); - close(fd); - return result; -} - static jobject android_server_UsbDeviceManager_openControl(JNIEnv *env, jobject /* thiz */, jstring jFunction) { ScopedUtfChars function(env, jFunction); bool ptp = false; @@ -148,16 +136,13 @@ static jobject android_server_UsbDeviceManager_openControl(JNIEnv *env, jobject } static const JNINativeMethod method_table[] = { - { "nativeGetAccessoryStrings", "()[Ljava/lang/String;", - (void*)android_server_UsbDeviceManager_getAccessoryStrings }, - { "nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;", - (void*)android_server_UsbDeviceManager_openAccessory }, - { "nativeIsStartRequested", "()Z", - (void*)android_server_UsbDeviceManager_isStartRequested }, - { "nativeGetAudioMode", "()I", - (void*)android_server_UsbDeviceManager_getAudioMode }, - { "nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;", - (void*)android_server_UsbDeviceManager_openControl }, + {"nativeGetAccessoryStrings", "()[Ljava/lang/String;", + (void *)android_server_UsbDeviceManager_getAccessoryStrings}, + {"nativeOpenAccessory", "()Landroid/os/ParcelFileDescriptor;", + (void *)android_server_UsbDeviceManager_openAccessory}, + {"nativeIsStartRequested", "()Z", (void *)android_server_UsbDeviceManager_isStartRequested}, + {"nativeOpenControl", "(Ljava/lang/String;)Ljava/io/FileDescriptor;", + (void *)android_server_UsbDeviceManager_openControl}, }; int register_android_server_UsbDeviceManager(JNIEnv *env) diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index b38a2f9558e9..d0df2b20721b 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -168,6 +168,10 @@ <xs:element type="nonNegativeDecimal" name="screenBrightnessCapForWearBedtimeMode"> <xs:annotation name="final"/> </xs:element> + <!-- Timeout after which we reduce the refresh rate if the screen has been idle, in order to save power. --> + <xs:element type="idleScreenRefreshRateTimeout" name="idleScreenRefreshRateTimeout" minOccurs="0"> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> </xs:element> @@ -772,6 +776,30 @@ </xs:sequence> </xs:complexType> + <xs:complexType name="idleScreenRefreshRateTimeout"> + <xs:element name="luxThresholds" type="idleScreenRefreshRateTimeoutLuxThresholds" minOccurs="0"> + <xs:annotation name="final"/> + </xs:element> + </xs:complexType> + + <!-- Lux based timeout after which we reduce the refresh rate if the screen has been idle, in order to save power. --> + <xs:complexType name="idleScreenRefreshRateTimeoutLuxThresholds"> + <xs:sequence> + <xs:element name="point" type="idleScreenRefreshRateTimeoutLuxThresholdPoint" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + + <!-- Represents a tuple of lux and timeout(in ms), which represents the timeout value for the lux in + the [luxValue, nextLuxValue (INF if missing)) --> + <xs:complexType name="idleScreenRefreshRateTimeoutLuxThresholdPoint"> + <xs:element name="lux" type="xs:nonNegativeInteger"> + <xs:annotation name="final"/> + </xs:element> + <xs:element name="timeout" type="xs:nonNegativeInteger"> + <xs:annotation name="final"/> + </xs:element> + </xs:complexType> + <!-- Predefined type names as defined by AutomaticBrightnessController.AutomaticBrightnessMode --> <xs:simpleType name="AutoBrightnessModeName"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index b329db4a2076..00dc90828d90 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -111,6 +111,7 @@ package com.android.server.display.config { method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle(); method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); + method public final com.android.server.display.config.IdleScreenRefreshRateTimeout getIdleScreenRefreshRateTimeout(); method public final com.android.server.display.config.SensorDetails getLightSensor(); method public com.android.server.display.config.LuxThrottling getLuxThrottling(); method @Nullable public final String getName(); @@ -146,6 +147,7 @@ package com.android.server.display.config { method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds); method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); + method public final void setIdleScreenRefreshRateTimeout(com.android.server.display.config.IdleScreenRefreshRateTimeout); method public final void setLightSensor(com.android.server.display.config.SensorDetails); method public void setLuxThrottling(com.android.server.display.config.LuxThrottling); method public final void setName(@Nullable String); @@ -222,6 +224,25 @@ package com.android.server.display.config { method public final void setTransitionPoint_all(@NonNull java.math.BigDecimal); } + public class IdleScreenRefreshRateTimeout { + ctor public IdleScreenRefreshRateTimeout(); + method public final com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds getLuxThresholds(); + method public final void setLuxThresholds(com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds); + } + + public class IdleScreenRefreshRateTimeoutLuxThresholdPoint { + ctor public IdleScreenRefreshRateTimeoutLuxThresholdPoint(); + method public final java.math.BigInteger getLux(); + method public final java.math.BigInteger getTimeout(); + method public final void setLux(java.math.BigInteger); + method public final void setTimeout(java.math.BigInteger); + } + + public class IdleScreenRefreshRateTimeoutLuxThresholds { + ctor public IdleScreenRefreshRateTimeoutLuxThresholds(); + method public java.util.List<com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint> getPoint(); + } + public class IntegerArray { ctor public IntegerArray(); method public java.util.List<java.math.BigInteger> getItem(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 38ab765154d6..cb87f7e4bf31 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -79,7 +79,6 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STATUS_BAR; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES; -import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING; @@ -93,6 +92,7 @@ import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.Manifest.permission.MASTER_CLEAR; import static android.Manifest.permission.NOTIFY_PENDING_SYSTEM_UPDATE; import static android.Manifest.permission.QUERY_ADMIN_POLICY; +import static android.Manifest.permission.QUERY_DEVICE_STOLEN_STATE; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; @@ -18460,7 +18460,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } new CalculateHasIncompatibleAccountsTask().executeOnExecutor( - calculateHasIncompatibleAccountsExecutor, null); + calculateHasIncompatibleAccountsExecutor); } @Nullable @@ -22128,12 +22128,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean isTheftDetectionTriggered(String callerPackageName) { + public boolean isDevicePotentiallyStolen(String callerPackageName) { final CallerIdentity caller = getCallerIdentity(callerPackageName); if (!android.app.admin.flags.Flags.deviceTheftImplEnabled()) { return false; } - enforcePermission(MANAGE_DEVICE_POLICY_THEFT_DETECTION, caller.getPackageName(), + enforcePermission(QUERY_DEVICE_STOLEN_STATE, caller.getPackageName(), caller.getUserId()); return mInjector.binderWithCleanCallingIdentity(() -> diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c6189ed405a1..3b2a3dd9763a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -408,6 +408,8 @@ public final class SystemServer implements Dumpable { "com.android.server.searchui.SearchUiManagerService"; private static final String SMARTSPACE_MANAGER_SERVICE_CLASS = "com.android.server.smartspace.SmartspaceManagerService"; + private static final String CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS = + "com.android.server.contextualsearch.ContextualSearchManagerService"; private static final String DEVICE_IDLE_CONTROLLER_CLASS = "com.android.server.DeviceIdleController"; private static final String BLOB_STORE_MANAGER_SERVICE_CLASS = @@ -2016,6 +2018,16 @@ public final class SystemServer implements Dumpable { Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag"); } + // Contextual search manager service + if (deviceHasConfigString(context, + R.string.config_defaultContextualSearchPackageName)) { + t.traceBegin("StartContextualSearchService"); + mSystemServiceManager.startService(CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS); + t.traceEnd(); + } else { + Slog.d(TAG, "ContextualSearchManagerService not defined or disabled by flag"); + } + t.traceBegin("InitConnectivityModuleConnector"); try { ConnectivityModuleConnector.getInstance().init(context); diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index af8ce31205bf..761874042be8 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -268,14 +268,33 @@ class AppIdPermissionPolicy : SchemePolicy() { } else { newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED } - newFlags = - if ( - permission.isSoftRestricted && !isExempt && - !anyPackageInAppId(appId) { - permissionName in it.androidPackage!!.requestedPermissions && - isSoftRestrictedPermissionExemptForPackage(it, permissionName) + val isSoftRestricted = + if (permission.isSoftRestricted && !isExempt) { + val targetSdkVersion = + reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT) { + targetSdkVersion, + packageState -> + if (permissionName in packageState.androidPackage!!.requestedPermissions) { + targetSdkVersion.coerceAtMost( + packageState.androidPackage!!.targetSdkVersion + ) + } else { + targetSdkVersion + } } - ) { + !anyPackageInAppId(appId) { + permissionName in it.androidPackage!!.requestedPermissions && + isSoftRestrictedPermissionExemptForPackage( + it, + targetSdkVersion, + permissionName + ) + } + } else { + false + } + newFlags = + if (isSoftRestricted) { newFlags or PermissionFlags.SOFT_RESTRICTED } else { newFlags andInv PermissionFlags.SOFT_RESTRICTED @@ -1159,9 +1178,14 @@ class AppIdPermissionPolicy : SchemePolicy() { } newFlags = if ( - permission.isSoftRestricted && !isExempt && + permission.isSoftRestricted && + !isExempt && !requestingPackageStates.anyIndexed { _, it -> - isSoftRestrictedPermissionExemptForPackage(it, permissionName) + isSoftRestrictedPermissionExemptForPackage( + it, + targetSdkVersion, + permissionName + ) } ) { newFlags or PermissionFlags.SOFT_RESTRICTED @@ -1444,13 +1468,20 @@ class AppIdPermissionPolicy : SchemePolicy() { } // See also SoftRestrictedPermissionPolicy.mayGrantPermission() + // Note: we need the appIdTargetSdkVersion parameter here because we are OR-ing the exempt + // status for all packages in a shared UID, but the storage soft restriction logic needs to NOT + // exempt when the target SDK version is low, which is the opposite of what most of our code do, + // and thus can't check the individual package's target SDK version and rely on the OR among + // them. private fun isSoftRestrictedPermissionExemptForPackage( packageState: PackageState, + appIdTargetSdkVersion: Int, permissionName: String ): Boolean = when (permissionName) { - Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE -> - packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE -> + appIdTargetSdkVersion >= Build.VERSION_CODES.Q else -> false } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 2867041511b5..35b69f812ff0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -54,6 +54,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.server.display.config.HdrBrightnessData; +import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.ThermalStatus; import com.android.server.display.feature.DisplayManagerFlags; @@ -108,6 +109,7 @@ public final class DisplayDeviceConfigTest { when(mContext.getResources()).thenReturn(mResources); when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(true); when(mFlags.isSensorBasedBrightnessThrottlingEnabled()).thenReturn(true); + when(mFlags.isIdleScreenRefreshRateTimeoutEnabled()).thenReturn(true); mockDeviceConfigs(); } @@ -146,6 +148,8 @@ public final class DisplayDeviceConfigTest { assertNull(mDisplayDeviceConfig.getProximitySensor().type); assertNull(mDisplayDeviceConfig.getProximitySensor().name); assertEquals(TEMPERATURE_TYPE_SKIN, mDisplayDeviceConfig.getTempSensor().type); + assertEquals(List.of(), mDisplayDeviceConfig + .getIdleScreenRefreshRateTimeoutLuxThresholdPoint()); assertNull(mDisplayDeviceConfig.getTempSensor().name); assertTrue(mDisplayDeviceConfig.isAutoBrightnessAvailable()); } @@ -226,6 +230,19 @@ public final class DisplayDeviceConfigTest { assertNotNull(mDisplayDeviceConfig.getHostUsiVersion()); assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2); assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0); + + List<IdleScreenRefreshRateTimeoutLuxThresholdPoint> + idleScreenRefreshRateTimeoutLuxThresholdPoints = + mDisplayDeviceConfig.getIdleScreenRefreshRateTimeoutLuxThresholdPoint(); + assertEquals(2, idleScreenRefreshRateTimeoutLuxThresholdPoints.size()); + assertEquals(6, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(0).getLux() + .intValue()); + assertEquals(1000, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(0) + .getTimeout().intValue()); + assertEquals(10, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1) + .getLux().intValue()); + assertEquals(800, idleScreenRefreshRateTimeoutLuxThresholdPoints.get(1) + .getTimeout().intValue()); } @Test @@ -734,6 +751,8 @@ public final class DisplayDeviceConfigTest { assertEquals(brightnessIntToFloat(35), mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA); + assertEquals(List.of(), mDisplayDeviceConfig + .getIdleScreenRefreshRateTimeoutLuxThresholdPoint()); } @Test @@ -1587,6 +1606,18 @@ public final class DisplayDeviceConfigTest { + "<screenBrightnessCapForWearBedtimeMode>" + "0.1" + "</screenBrightnessCapForWearBedtimeMode>" + + "<idleScreenRefreshRateTimeout>" + + "<luxThresholds>" + + "<point>" + + "<lux>6</lux>" + + "<timeout>1000</timeout>" + + "</point>" + + "<point>" + + "<lux>10</lux>" + + "<timeout>800</timeout>" + + "</point>" + + "</luxThresholds>" + + "</idleScreenRefreshRateTimeout>" + "</displayConfiguration>\n"; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 48fc4078999d..869cec8d733d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -78,6 +78,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.hardware.Sensor; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; @@ -721,7 +722,9 @@ public class DisplayManagerServiceTest { IDisplayManagerCallback displayChangesCallback = registerDisplayChangeCallback( displayManager); - listener.onStateChanged(123); + listener.onDeviceStateChanged(new DeviceState( + new DeviceState.Configuration.Builder(123 /* identifier */, + "TEST" /* name */).build())); waitForIdleHandler(handler); InOrder inOrder = inOrder(mMockWindowManagerInternal, displayChangesCallback); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 2939192d3d2e..d0c7077f29c0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -650,7 +650,6 @@ public class LogicalDisplayMapperTest { public void testDeviceShouldBePutToSleep() { assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, DEVICE_STATE_OPEN, - /* isOverrideActive= */false, /* isInteractive= */true, /* isBootCompleted= */true)); } @@ -661,7 +660,6 @@ public class LogicalDisplayMapperTest { assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, DEVICE_STATE_OPEN, - /* isOverrideActive= */false, /* isInteractive= */true, /* isBootCompleted= */true)); } @@ -670,21 +668,10 @@ public class LogicalDisplayMapperTest { public void testDeviceShouldNotBePutToSleep() { assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN, DEVICE_STATE_CLOSED, - /* isOverrideActive= */false, /* isInteractive= */true, /* isBootCompleted= */true)); assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, INVALID_DEVICE_STATE_IDENTIFIER, - /* isOverrideActive= */false, - /* isInteractive= */true, - /* isBootCompleted= */true)); - } - - @Test - public void testDeviceShouldNotBePutToSleepDifferentBaseState() { - assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED, - DEVICE_STATE_OPEN, - /* isOverrideActive= */true, /* isInteractive= */true, /* isBootCompleted= */true)); } @@ -750,7 +737,7 @@ public class LogicalDisplayMapperTest { // We can only have one default display assertEquals(DEFAULT_DISPLAY, id(display1)); - mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mLogicalDisplayMapper.setDeviceStateLocked(0); advanceTime(1000); // The new state is not applied until the boot is completed assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); @@ -771,7 +758,7 @@ public class LogicalDisplayMapperTest { assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2) .getDisplayInfoLocked().thermalBrightnessThrottlingDataId); - mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mLogicalDisplayMapper.setDeviceStateLocked(1); advanceTime(1000); assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); @@ -784,7 +771,7 @@ public class LogicalDisplayMapperTest { mLogicalDisplayMapper.getDisplayLocked(device2) .getDisplayInfoLocked().thermalBrightnessThrottlingDataId); - mLogicalDisplayMapper.setDeviceStateLocked(2, false); + mLogicalDisplayMapper.setDeviceStateLocked(2); advanceTime(1000); assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked()); assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked()); @@ -861,7 +848,7 @@ public class LogicalDisplayMapperTest { // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state // 4) Dispatch handler events. mLogicalDisplayMapper.onBootCompleted(); - mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mLogicalDisplayMapper.setDeviceStateLocked(0); mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); advanceTime(1000); final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked( @@ -891,7 +878,7 @@ public class LogicalDisplayMapperTest { /* includeDisabled= */ false)); // Now do it again to go back to state 1 - mLogicalDisplayMapper.setDeviceStateLocked(1, false); + mLogicalDisplayMapper.setDeviceStateLocked(1); mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); advanceTime(1000); final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked( @@ -945,7 +932,7 @@ public class LogicalDisplayMapperTest { // We can only have one default display assertEquals(DEFAULT_DISPLAY, id(display1)); - mLogicalDisplayMapper.setDeviceStateLocked(0, false); + mLogicalDisplayMapper.setDeviceStateLocked(0); advanceTime(1000); mLogicalDisplayMapper.onBootCompleted(); advanceTime(1000); @@ -964,11 +951,11 @@ public class LogicalDisplayMapperTest { ///////////////// private void finishBootAndFoldDevice() { - mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN, false); + mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN); advanceTime(1000); mLogicalDisplayMapper.onBootCompleted(); advanceTime(1000); - mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED, false); + mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED); advanceTime(1000); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java index 549f0d74b67b..e798aa20f4bf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java @@ -107,8 +107,7 @@ public class LogicalDisplayTest { @Test public void testLetterbox() { - mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ false); + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); mDisplayDeviceInfo.xDpi = 0.5f; mDisplayDeviceInfo.yDpi = 1.0f; @@ -146,7 +145,8 @@ public class LogicalDisplayTest { @Test public void testNoLetterbox_anisotropyCorrection() { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ true); + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust // to using the whole screen. This is because display will rescale it back to fill the @@ -173,7 +173,8 @@ public class LogicalDisplayTest { @Test public void testLetterbox_anisotropyCorrectionYDpi() { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ true); + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = DISPLAY_WIDTH; @@ -191,8 +192,7 @@ public class LogicalDisplayTest { @Test public void testPillarbox() { - mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ false); + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice); mDisplayDeviceInfo.xDpi = 0.5f; mDisplayDeviceInfo.yDpi = 1.0f; @@ -230,7 +230,8 @@ public class LogicalDisplayTest { @Test public void testPillarbox_anisotropyCorrection() { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ true); + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = DISPLAY_WIDTH; @@ -257,7 +258,8 @@ public class LogicalDisplayTest { @Test public void testNoPillarbox_anisotropyCorrectionYDpi() { mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, - /*isAnisotropyCorrectionEnabled=*/ true); + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust // to using the whole screen. This is because display will rescale it back to fill the @@ -315,6 +317,47 @@ public class LogicalDisplayTest { } @Test + public void testGetDisplayPositionAlwaysRotateDisplayEnabled() { + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, + /*isAnisotropyCorrectionEnabled=*/ true, + /*isAlwaysRotateDisplayDeviceEnabled=*/ true); + mLogicalDisplay.updateLocked(mDeviceRepo); + Point expectedPosition = new Point(); + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + expectedPosition.set(20, 40); + mLogicalDisplay.setDisplayOffsetsLocked(20, 40); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = DISPLAY_WIDTH; + displayInfo.logicalHeight = DISPLAY_HEIGHT; + // Rotation sent from WindowManager is always taken into account by LogicalDisplay + // not matter whether FLAG_ROTATES_WITH_CONTENT is set or not. + // This is because WindowManager takes care of rotation and expects that LogicalDisplay + // will follow the rotation supplied by WindowManager + expectedPosition.set(115, -20); + displayInfo.rotation = Surface.ROTATION_90; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + + expectedPosition.set(40, -20); + mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; + mLogicalDisplay.updateLocked(mDeviceRepo); + displayInfo.logicalWidth = DISPLAY_HEIGHT; + displayInfo.logicalHeight = DISPLAY_WIDTH; + displayInfo.rotation = Surface.ROTATION_90; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); + } + + @Test public void testDisplayInputFlags() { DisplayDevice displayDevice = new DisplayDevice(mDisplayAdapter, mDisplayToken, "unique_display_id", mContext) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt index 638924eeb2a3..b182ccef091e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt @@ -95,9 +95,9 @@ class BrightnessObserverTest { ) { ALL_ENABLED(true, true, CombinedVote( listOf(DisableRefreshRateSwitchingVote(true), - SupportedModesVote( - listOf(SupportedModesVote.SupportedMode(60f, 60f), - SupportedModesVote.SupportedMode(120f, 120f)))))), + SupportedRefreshRatesVote( + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f), + SupportedRefreshRatesVote.RefreshRates(120f, 120f)))))), VRR_NOT_SUPPORTED(false, true, DisableRefreshRateSwitchingVote(true)), VSYNC_VOTE_DISABLED(true, false, DisableRefreshRateSwitchingVote(true)) } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java new file mode 100644 index 000000000000..e93e5bc63dd8 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/ProximitySensorObserverTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2024 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.mode; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.hardware.display.DisplayManagerInternal; +import android.util.SparseArray; +import android.view.Display; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.server.sensors.SensorManagerInternal; + +import junitparams.JUnitParamsRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(JUnitParamsRunner.class) +public class ProximitySensorObserverTest { + + private static final float FLOAT_TOLERANCE = 0.01f; + private static final int DISPLAY_ID = 1; + private static final SurfaceControl.RefreshRateRange REFRESH_RATE_RANGE = + new SurfaceControl.RefreshRateRange(60, 90); + + private final VotesStorage mStorage = new VotesStorage(() -> { }, null); + private final FakesInjector mInjector = new FakesInjector(); + private ProximitySensorObserver mSensorObserver; + + @Mock + DisplayManagerInternal mMockDisplayManagerInternal; + @Mock + SensorManagerInternal mMockSensorManagerInternal; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockDisplayManagerInternal.getRefreshRateForDisplayAndSensor(eq(DISPLAY_ID), + any(), any())).thenReturn(REFRESH_RATE_RANGE); + mSensorObserver = new ProximitySensorObserver(mStorage, mInjector); + mSensorObserver.observe(); + } + + @Test + public void testAddsProximityVoteIfSensorManagerProximityActive() { + mSensorObserver.onProximityActive(true); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(1); + Vote vote = displayVotes.get(Vote.PRIORITY_PROXIMITY); + assertThat(vote).isNotNull(); + assertThat(vote).isInstanceOf(CombinedVote.class); + CombinedVote combinedVote = (CombinedVote) vote; + RefreshRateVote.PhysicalVote physicalVote = + (RefreshRateVote.PhysicalVote) combinedVote.mVotes.get(0); + assertThat(physicalVote.mMinRefreshRate).isWithin(FLOAT_TOLERANCE).of(60); + assertThat(physicalVote.mMaxRefreshRate).isWithin(FLOAT_TOLERANCE).of(90); + } + + @Test + public void testDoesNotAddProximityVoteIfSensorManagerProximityNotActive() { + mSensorObserver.onProximityActive(false); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(0); + } + + @Test + public void testDoesNotAddProximityVoteIfDoze() { + mInjector.mDozeState = true; + mSensorObserver.onDisplayChanged(DISPLAY_ID); + mSensorObserver.onProximityActive(true); + + SparseArray<Vote> displayVotes = mStorage.getVotes(DISPLAY_ID); + assertThat(displayVotes.size()).isEqualTo(0); + } + + private class FakesInjector extends DisplayModeDirectorTest.FakesInjector { + + private boolean mDozeState = false; + + @Override + public Display[] getDisplays() { + return new Display[] { createDisplay(DISPLAY_ID) }; + } + + @Override + public DisplayManagerInternal getDisplayManagerInternal() { + return mMockDisplayManagerInternal; + } + + @Override + public SensorManagerInternal getSensorManagerInternal() { + return mMockSensorManagerInternal; + } + + @Override + public boolean isDozeState(Display d) { + return mDozeState; + } + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt index ebb4f1889cd6..230317ba738b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt @@ -85,9 +85,9 @@ class SettingsObserverTest { internal val expectedVote: Vote? ) { ALL_ENABLED(true, true, true, - SupportedModesVote(listOf( - SupportedModesVote.SupportedMode(60f, 240f), - SupportedModesVote.SupportedMode(60f, 60f) + SupportedRefreshRatesVote(listOf( + SupportedRefreshRatesVote.RefreshRates(60f, 240f), + SupportedRefreshRatesVote.RefreshRates(60f, 60f) ))), LOW_POWER_OFF(true, true, false, null), DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true, diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt index 04e626536eba..6ce49b8cb31e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 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. @@ -27,12 +27,9 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SupportedModesVoteTest { - private val supportedModes = listOf( - SupportedModesVote.SupportedMode(60f, 90f ), - SupportedModesVote.SupportedMode(120f, 240f ) - ) + private val supportedModes = listOf(1, 2, 4) - private val otherMode = SupportedModesVote.SupportedMode(120f, 120f ) + private val otherMode = 5 private lateinit var supportedModesVote: SupportedModesVote @@ -42,31 +39,31 @@ class SupportedModesVoteTest { } @Test - fun `adds supported modes if supportedModes in summary is null`() { + fun `adds supported mode ids if supportedModeIds in summary is null`() { val summary = createVotesSummary() supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).containsExactlyElementsIn(supportedModes) + assertThat(summary.supportedModeIds).containsExactlyElementsIn(supportedModes) } @Test - fun `does not add supported modes if summary has empty list of modes`() { + fun `does not add supported mode ids if summary has empty list of modeIds`() { val summary = createVotesSummary() - summary.supportedModes = ArrayList() + summary.supportedModeIds = ArrayList() supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).isEmpty() + assertThat(summary.supportedModeIds).isEmpty() } @Test fun `filters out modes that does not match vote`() { val summary = createVotesSummary() - summary.supportedModes = ArrayList(listOf(otherMode, supportedModes[0])) + summary.supportedModeIds = ArrayList(listOf(otherMode, supportedModes[0])) supportedModesVote.updateSummary(summary) - assertThat(summary.supportedModes).containsExactly(supportedModes[0]) + assertThat(summary.supportedModeIds).containsExactly(supportedModes[0]) } }
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt new file mode 100644 index 000000000000..d0c112be24a2 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SupportedRefreshRatesVoteTest { + private val refreshRates = listOf( + SupportedRefreshRatesVote.RefreshRates(60f, 90f), + SupportedRefreshRatesVote.RefreshRates(120f, 240f) + ) + + private val otherMode = SupportedRefreshRatesVote.RefreshRates(120f, 120f) + + private lateinit var supportedRefreshRatesVote: SupportedRefreshRatesVote + + @Before + fun setUp() { + supportedRefreshRatesVote = SupportedRefreshRatesVote(refreshRates) + } + + @Test + fun `adds supported refresh rates if supportedModes in summary is null`() { + val summary = createVotesSummary() + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).containsExactlyElementsIn(refreshRates) + } + + @Test + fun `does not add supported refresh rates if summary has empty list of refresh rates`() { + val summary = createVotesSummary() + summary.supportedRefreshRates = ArrayList() + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).isEmpty() + } + + @Test + fun `filters out supported refresh rates that does not match vote`() { + val summary = createVotesSummary() + summary.supportedRefreshRates = ArrayList(listOf(otherMode, refreshRates[0])) + + supportedRefreshRatesVote.updateSummary(summary) + + assertThat(summary.supportedRefreshRates).containsExactly(refreshRates[0]) + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt new file mode 100644 index 000000000000..c49205bcfe3d --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 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.mode + +import android.os.IBinder +import android.os.RemoteException +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +private const val DISPLAY_ID = 1 +private const val DISPLAY_ID_OTHER = 2 + +@SmallTest +@RunWith(TestParameterInjector::class) +class SystemRequestObserverTest { + + + @get:Rule + val mockitoRule = MockitoJUnit.rule() + + private val mockToken = mock<IBinder>() + private val mockOtherToken = mock<IBinder>() + + private val storage = VotesStorage({}, null) + + @Test + fun `requestDisplayModes adds vote to storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size) + for (mode in requestedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `requestDisplayModes overrides votes in storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, intArrayOf(1, 2, 3)) + + val overrideModes = intArrayOf(10, 20, 30) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, overrideModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(overrideModes.size) + for (mode in overrideModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `requestDisplayModes removes vote to storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `requestDisplayModes calls linkToDeath to token`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + verify(mockToken).linkToDeath(any(), eq(0)) + } + + @Test + fun `does not add votes to storage if binder died when requestDisplayModes called`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + doThrow(RemoteException()).whenever(mockOtherToken).linkToDeath(any(), eq(0)) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedModes) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `removes all votes from storage when binder dies`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>() + verify(mockToken).linkToDeath(deathRecipientCaptor.capture(), eq(0)) + + deathRecipientCaptor.lastValue.binderDied(mockToken) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(0) + } + + @Test + fun `calls unlinkToDeath on token when no votes remaining`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + verify(mockToken).unlinkToDeath(any(), eq(0)) + } + + @Test + fun `does not call unlinkToDeath on token when votes for other display in storage`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID_OTHER, requestedModes) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, null) + + verify(mockToken, never()).unlinkToDeath(any(), eq(0)) + } + + @Test + fun `requestDisplayModes subset modes from different tokens`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val requestedOtherModes = intArrayOf(2, 3, 4) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes) + + verify(mockToken).linkToDeath(any(), eq(0)) + verify(mockOtherToken).linkToDeath(any(), eq(0)) + verify(mockToken, never()).unlinkToDeath(any(), eq(0)) + verify(mockOtherToken, never()).unlinkToDeath(any(), eq(0)) + + val expectedModes = intArrayOf(2, 3) + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(expectedModes.size) + for (mode in expectedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } + + @Test + fun `recalculates vote if one binder dies`() { + val systemRequestObserver = SystemRequestObserver(storage) + val requestedModes = intArrayOf(1, 2, 3) + systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes) + + val requestedOtherModes = intArrayOf(2, 3, 4) + systemRequestObserver.requestDisplayModes(mockOtherToken, DISPLAY_ID, requestedOtherModes) + + val deathRecipientCaptor = argumentCaptor<IBinder.DeathRecipient>() + verify(mockOtherToken).linkToDeath(deathRecipientCaptor.capture(), eq(0)) + deathRecipientCaptor.lastValue.binderDied(mockOtherToken) + + val votes = storage.getVotes(DISPLAY_ID) + assertThat(votes.size()).isEqualTo(1) + val vote = votes.get(Vote.PRIORITY_SYSTEM_REQUESTED_MODES) + assertThat(vote).isInstanceOf(SupportedModesVote::class.java) + val supportedModesVote = vote as SupportedModesVote + assertThat(supportedModesVote.mModeIds.size).isEqualTo(requestedModes.size) + for (mode in requestedModes) { + assertThat(supportedModesVote.mModeIds).contains(mode) + } + } +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt index 910e03c5db85..6b90bde188c5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt @@ -18,10 +18,10 @@ package com.android.server.display.mode internal fun createVotesSummary( isDisplayResolutionRangeVotingEnabled: Boolean = true, - vsyncProximityVoteEnabled: Boolean = true, + supportedModesVoteEnabled: Boolean = true, loggingEnabled: Boolean = true, supportsFrameRateOverride: Boolean = true ): VoteSummary { - return VoteSummary(isDisplayResolutionRangeVotingEnabled, vsyncProximityVoteEnabled, + return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled, loggingEnabled, supportsFrameRateOverride) }
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt index d6c84690e65f..04b35f10545f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt @@ -28,29 +28,29 @@ import org.junit.runner.RunWith @RunWith(TestParameterInjector::class) class VoteSummaryTest { - enum class SupportedModesVoteTestCase( - val vsyncProximityVoteEnabled: Boolean, - internal val summarySupportedModes: List<SupportedModesVote.SupportedMode>?, + enum class SupportedRefreshRatesTestCase( + val supportedModesVoteEnabled: Boolean, + internal val summaryRefreshRates: List<SupportedRefreshRatesVote.RefreshRates>?, val modesToFilter: Array<Display.Mode>, val expectedModeIds: List<Int> ) { HAS_NO_MATCHING_VOTE(true, - listOf(SupportedModesVote.SupportedMode(60f, 60f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 60f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), listOf() ), HAS_SINGLE_MATCHING_VOTE(true, - listOf(SupportedModesVote.SupportedMode(60f, 90f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), listOf(3) ), HAS_MULTIPLE_MATCHING_VOTES(true, - listOf(SupportedModesVote.SupportedMode(60f, 90f), - SupportedModesVote.SupportedMode(90f, 90f)), + listOf(SupportedRefreshRatesVote.RefreshRates(60f, 90f), + SupportedRefreshRatesVote.RefreshRates(90f, 90f)), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), createMode(3, 60f, 90f)), @@ -70,7 +70,69 @@ class VoteSummaryTest { createMode(3, 60f, 90f)), listOf(1, 2, 3) ), - HAS_VSYNC_PROXIMITY_DISABLED(false, + HAS_SUPPORTED_MODES_VOTE_DISABLED(false, + listOf(), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 2, 3) + ), + } + + @Test + fun `filters modes for summary supportedRefreshRates`( + @TestParameter testCase: SupportedRefreshRatesTestCase + ) { + val summary = createSummary(testCase.supportedModesVoteEnabled) + summary.supportedRefreshRates = testCase.summaryRefreshRates + + val result = summary.filterModes(testCase.modesToFilter) + + assertThat(result.map { it.modeId }).containsExactlyElementsIn(testCase.expectedModeIds) + } + + enum class SupportedModesTestCase( + val supportedModesVoteEnabled: Boolean, + internal val summarySupportedModes: List<Int>?, + val modesToFilter: Array<Display.Mode>, + val expectedModeIds: List<Int> + ) { + HAS_NO_MATCHING_VOTE(true, + listOf(4, 5), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf() + ), + HAS_SINGLE_MATCHING_VOTE(true, + listOf(3), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(3) + ), + HAS_MULTIPLE_MATCHING_VOTES(true, + listOf(1, 3), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 3) + ), + HAS_NO_SUPPORTED_MODES(true, + listOf(), + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf() + ), + HAS_NULL_SUPPORTED_MODES(true, + null, + arrayOf(createMode(1, 90f, 90f), + createMode(2, 90f, 60f), + createMode(3, 60f, 90f)), + listOf(1, 2, 3) + ), + HAS_SUPPORTED_MODES_VOTE_DISABLED(false, listOf(), arrayOf(createMode(1, 90f, 90f), createMode(2, 90f, 60f), @@ -81,10 +143,10 @@ class VoteSummaryTest { @Test fun `filters modes for summary supportedModes`( - @TestParameter testCase: SupportedModesVoteTestCase + @TestParameter testCase: SupportedModesTestCase ) { - val summary = createSummary(testCase.vsyncProximityVoteEnabled) - summary.supportedModes = testCase.summarySupportedModes + val summary = createSummary(testCase.supportedModesVoteEnabled) + summary.supportedModeIds = testCase.summarySupportedModes val result = summary.filterModes(testCase.modesToFilter) @@ -96,8 +158,8 @@ private fun createMode(modeId: Int, refreshRate: Float, vsyncRate: Float): Displ FloatArray(0), IntArray(0)) } -private fun createSummary(vsyncVoteEnabled: Boolean): VoteSummary { - val summary = createVotesSummary(vsyncProximityVoteEnabled = vsyncVoteEnabled) +private fun createSummary(supportedModesVoteEnabled: Boolean): VoteSummary { + val summary = createVotesSummary(supportedModesVoteEnabled = supportedModesVoteEnabled) summary.width = 600 summary.height = 800 summary.maxPhysicalRefreshRate = Float.POSITIVE_INFINITY diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java index 1f6f1a41bea7..a248d6de118f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java @@ -18,6 +18,7 @@ package com.android.server.display.mode; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -153,4 +154,51 @@ public class VotesStorageTest { assertThat(mVotesStorage.getVotes(DISPLAY_ID).size()).isEqualTo(0); verify(mVotesListener, never()).onChanged(); } + + + @Test + public void removesAllVotesForPriority() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER); + // WHEN removeAllVotesForPriority is called + mVotesStorage.removeAllVotesForPriority(PRIORITY); + // THEN votes with priority are removed from the storage + SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isNull(); + votes = mVotesStorage.getVotes(DISPLAY_ID_OTHER); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isNull(); + } + + @Test + public void removesAllVotesForPriority_notifiesListenerOnce() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID_OTHER, PRIORITY_OTHER, VOTE_OTHER); + clearInvocations(mVotesListener); + // WHEN removeAllVotesForPriority is called + mVotesStorage.removeAllVotesForPriority(PRIORITY); + // THEN listener notified once + verify(mVotesListener).onChanged(); + } + + @Test + public void removesAllVotesForPriority_noChangesIfNothingRemoved() { + // GIVEN vote storage with votes + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + clearInvocations(mVotesListener); + // WHEN removeAllVotesForPriority is called for missing priority + mVotesStorage.removeAllVotesForPriority(PRIORITY_OTHER); + // THEN no changes to votes storage + SparseArray<Vote> votes = mVotesStorage.getVotes(DISPLAY_ID); + assertThat(votes.size()).isEqualTo(1); + assertThat(votes.get(PRIORITY)).isEqualTo(VOTE); + verify(mVotesListener, never()).onChanged(); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index fcb3caa19b85..dc5b00a18d7b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -28,10 +28,15 @@ import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; +import static android.os.PowerExemptionManager.REASON_DENIED; import static android.util.DebugUtils.valueToString; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.am.ActivityManagerInternalTest.CustomThread; import static com.android.server.am.ActivityManagerService.Injector; import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK; @@ -52,28 +57,40 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; import android.Manifest; import android.app.ActivityManager; +import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.BackgroundStartPrivileges; import android.app.BroadcastOptions; +import android.app.ForegroundServiceDelegationOptions; import android.app.IApplicationThread; import android.app.IUidObserver; +import android.app.Notification; +import android.app.NotificationChannel; import android.app.SyncNotedAppOp; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.ServiceInfo; +import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -84,6 +101,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.permission.IPermissionManager; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -105,18 +123,20 @@ import com.android.server.am.ProcessList.IsolatedUidRange; import com.android.server.am.ProcessList.IsolatedUidRangeAllocator; import com.android.server.am.UidObserverController.ChangeRecord; import com.android.server.appop.AppOpsService; +import com.android.server.notification.NotificationManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerService; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.verification.VerificationMode; import java.io.File; import java.util.ArrayList; @@ -127,13 +147,15 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.function.Function; /** * Test class for {@link ActivityManagerService}. * * Build/Install/Run: - * atest FrameworksServicesTests:ActivityManagerServiceTest + * atest FrameworksMockingServicesTests:ActivityManagerServiceTest */ @Presubmit @SmallTest @@ -148,6 +170,9 @@ public class ActivityManagerServiceTest { private static final String TEST_EXTRA_KEY1 = "com.android.server.am.TEST_EXTRA_KEY1"; private static final String TEST_EXTRA_VALUE1 = "com.android.server.am.TEST_EXTRA_VALUE1"; + + private static final String TEST_PACKAGE_NAME = "com.android.server.am.testpackage"; + private static final String PROPERTY_APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS = "apply_sdk_sandbox_audit_restrictions"; private static final String PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = @@ -155,6 +180,7 @@ public class ActivityManagerServiceTest { private static final String APPLY_SDK_SANDBOX_AUDIT_RESTRICTIONS = ":isSdkSandboxAudit"; private static final String APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = ":isSdkSandboxNext"; private static final int TEST_UID = 11111; + private static final int TEST_PID = 22222; private static final int USER_ID = 666; private static final long TEST_PROC_STATE_SEQ1 = 555; @@ -169,22 +195,8 @@ public class ActivityManagerServiceTest { UidRecord.CHANGE_CAPABILITY, }; - private static PackageManagerInternal sPackageManagerInternal; private static ProcessList.ProcessListSettingsListener sProcessListSettingsListener; - @BeforeClass - public static void setUpOnce() { - sPackageManagerInternal = mock(PackageManagerInternal.class); - doReturn(new ComponentName("", "")).when(sPackageManagerInternal) - .getSystemUiServiceComponent(); - LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal); - } - - @AfterClass - public static void tearDownOnce() { - LocalServices.removeServiceForTest(PackageManagerInternal.class); - } - @Rule public final ApplicationExitInfoTest.ServiceThreadRule mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); @@ -196,15 +208,41 @@ public class ActivityManagerServiceTest { @Mock private AppOpsService mAppOpsService; @Mock private UserController mUserController; + @Mock private IPackageManager mPackageManager; + @Mock private IPermissionManager mPermissionManager; + @Mock private BatteryStatsService mBatteryStatsService; + @Mock private PackageManagerInternal mPackageManagerInternal; + @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal; + @Mock private NotificationManagerInternal mNotificationManagerInternal; + private TestInjector mInjector; private ActivityManagerService mAms; + private ActiveServices mActiveServices; private HandlerThread mHandlerThread; private TestHandler mHandler; + private MockitoSession mMockingSession; + @Before - public void setUp() { + public void setUp() throws Exception { + mMockingSession = mockitoSession() + .initMocks(this) + .mockStatic(AppGlobals.class) + .strictness(Strictness.LENIENT) + .startMocking(); MockitoAnnotations.initMocks(this); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); + LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal); + LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal); + + doReturn(new ComponentName("", "")).when(mPackageManagerInternal) + .getSystemUiServiceComponent(); + + doReturn(mPackageManager).when(AppGlobals::getPackageManager); + doReturn(mPermissionManager).when(AppGlobals::getPermissionManager); + doReturn(new String[]{""}).when(mPackageManager).getPackagesForUid(eq(Process.myUid())); + mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mHandler = new TestHandler(mHandlerThread.getLooper()); @@ -220,6 +258,7 @@ public class ActivityManagerServiceTest { mAms.mConstants.mNetworkAccessTimeoutMs = 2000; mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); mAms.mActivityTaskManager.initialize(null, null, mHandler.getLooper()); + mAms.mAtmInternal = mActivityTaskManagerInternal; mHandler.setRunnablesToIgnore( List.of(mAms.mUidObserverController.getDispatchRunnableForTest())); @@ -250,6 +289,15 @@ public class ActivityManagerServiceTest { if (sProcessListSettingsListener != null) { sProcessListSettingsListener.unregisterObserver(); } + clearInvocations(mNotificationManagerInternal); + + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); + LocalServices.removeServiceForTest(NotificationManagerInternal.class); + + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } } @SuppressWarnings("GuardedBy") @@ -445,6 +493,7 @@ public class ActivityManagerServiceTest { } } + @SuppressWarnings("GuardedBy") private UidRecord addUidRecord(int uid) { final UidRecord uidRec = new UidRecord(uid, mAms); uidRec.procStateSeqWaitingForNetwork = 1; @@ -453,10 +502,13 @@ public class ActivityManagerServiceTest { ApplicationInfo info = new ApplicationInfo(); info.packageName = ""; + info.uid = uid; final ProcessRecord appRec = new ProcessRecord(mAms, info, TAG, uid); - final ProcessStatsService tracker = new ProcessStatsService(mAms, mContext.getCacheDir()); - appRec.makeActive(mock(IApplicationThread.class), tracker); + final ProcessStatsService tracker = mAms.mProcessStats; + final IApplicationThread appThread = mock(IApplicationThread.class); + doReturn(mock(IBinder.class)).when(appThread).asBinder(); + appRec.makeActive(appThread, tracker); mAms.mProcessList.getLruProcessesLSP().add(appRec); return uidRec; @@ -1209,6 +1261,108 @@ public class ActivityManagerServiceTest { mAms.mUidObserverController.getPendingUidChangesForTest().clear(); } + @Test + public void testStartForegroundServiceDelegateWithNotification() throws Exception { + testStartForegroundServiceDelegate(true); + } + + @Test + public void testStartForegroundServiceDelegateWithoutNotification() throws Exception { + testStartForegroundServiceDelegate(false); + } + + @SuppressWarnings("GuardedBy") + private void testStartForegroundServiceDelegate(boolean withNotification) throws Exception { + mockNoteOperation(); + + final int notificationId = 42; + final Notification notification = mock(Notification.class); + + addUidRecord(TEST_UID); + final ProcessRecord app = mAms.mProcessList.getLruProcessesLSP().get(0); + app.mPid = TEST_PID; + app.info.packageName = TEST_PACKAGE_NAME; + app.info.processName = TEST_PACKAGE_NAME; + + doReturn(app.info).when(mPackageManager).getApplicationInfo( + eq(app.info.packageName), anyLong(), anyInt()); + + doReturn(true).when(mActiveServices) + .canStartForegroundServiceLocked(anyInt(), anyInt(), anyString()); + doReturn(REASON_DENIED).when(mActiveServices) + .shouldAllowFgsWhileInUsePermissionLocked(anyString(), anyInt(), anyInt(), + any(ProcessRecord.class), any(BackgroundStartPrivileges.class)); + + doReturn(true).when(mNotificationManagerInternal).areNotificationsEnabledForPackage( + anyString(), anyInt()); + doReturn(mock(Icon.class)).when(notification).getSmallIcon(); + doReturn("").when(notification).getChannelId(); + doReturn(mock(NotificationChannel.class)).when(mNotificationManagerInternal) + .getNotificationChannel(anyString(), anyInt(), anyString()); + + mAms.mAppProfiler.mCachedAppsWatermarkData.mCachedAppHighWatermark = Integer.MAX_VALUE; + + final ForegroundServiceDelegationOptions.Builder optionsBuilder = + new ForegroundServiceDelegationOptions.Builder() + .setClientPid(app.mPid) + .setClientUid(app.uid) + .setClientPackageName(app.info.packageName) + .setClientAppThread(app.getThread()) + .setSticky(false) + .setClientInstanceName( + "SystemExemptedFgsDelegate_" + + Process.myUid() + + "_" + + app.uid + + "_" + + app.info.packageName) + .setForegroundServiceTypes(ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED) + .setDelegationService( + ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SYSTEM_EXEMPTED); + if (withNotification) { + optionsBuilder.setClientNotification(notificationId, notification); + } + final ForegroundServiceDelegationOptions options = optionsBuilder.build(); + + final CountDownLatch[] latchHolder = new CountDownLatch[1]; + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + latchHolder[0].countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + latchHolder[0].countDown(); + } + }; + + latchHolder[0] = new CountDownLatch(1); + mAms.mInternal.startForegroundServiceDelegate(options, conn); + + assertThat(latchHolder[0].await(5, TimeUnit.SECONDS)).isTrue(); + assertEquals(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, + app.mState.getCurProcState()); + final long timeoutMs = 5000L; + final VerificationMode mode = withNotification + ? timeout(timeoutMs) : after(timeoutMs).atMost(0); + verify(mNotificationManagerInternal, mode) + .enqueueNotification(eq(app.info.packageName), eq(app.info.packageName), + eq(app.info.uid), eq(app.mPid), eq(null), + eq(notificationId), eq(notification), anyInt(), eq(true)); + + latchHolder[0] = new CountDownLatch(1); + mAms.mInternal.stopForegroundServiceDelegate(options); + + assertThat(latchHolder[0].await(5, TimeUnit.SECONDS)).isTrue(); + assertEquals(ActivityManager.PROCESS_STATE_CACHED_EMPTY, + app.mState.getCurProcState()); + verify(mNotificationManagerInternal, mode) + .cancelNotification(eq(app.info.packageName), eq(app.info.packageName), + eq(app.info.uid), eq(app.mPid), eq(null), + eq(notificationId), anyInt()); + } + private static class TestHandler extends Handler { private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec @@ -1291,6 +1445,19 @@ public class ActivityManagerServiceTest { usersStartedOnSecondaryDisplays.add(new Pair<>(userId, displayId)); return returnValueForstartUserOnSecondaryDisplay; } + + @Override + public ActiveServices getActiveServices(ActivityManagerService service) { + if (mActiveServices == null) { + mActiveServices = spy(new ActiveServices(service)); + } + return mActiveServices; + } + + @Override + public BatteryStatsService getBatteryStatsService() { + return mBatteryStatsService; + } } // TODO: [b/302724778] Remove manual JNI load diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java index ce281daf41c4..bd20ae26821e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java @@ -56,7 +56,6 @@ import com.android.server.wm.ActivityTaskManagerService; import org.junit.Rule; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.File; @@ -161,7 +160,6 @@ public abstract class BaseBroadcastQueueTest { realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal()); - realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class); realAms.mOomAdjuster = spy(realAms.mOomAdjuster); ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt())); realAms.mPackageManagerInt = mPackageManagerInt; 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 1172685c4466..66ab8076a217 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -86,6 +86,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; @@ -2334,8 +2335,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue)); } + @Ignore @Test - @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BCASTS) public void testDeferOutgoingBroadcasts() throws Exception { final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */); @@ -2349,8 +2350,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { makeRegisteredReceiver(receiverGreenApp), makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)))); - // Verify that we invoke the call to freeze the caller app. - verify(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncImmediateLSP(callerApp); waitForIdle(); verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index bc7c9a59ff29..6aa1825ba6b7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -582,7 +582,8 @@ public final class UserManagerServiceTest { @Test public void testAutoLockPrivateProfile() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, @@ -600,7 +601,8 @@ public final class UserManagerServiceTest { @Test public void testAutoLockOnDeviceLockForPrivateProfile() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = @@ -620,7 +622,8 @@ public final class UserManagerServiceTest { @Test public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = @@ -638,7 +641,8 @@ public final class UserManagerServiceTest { @Test public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = @@ -657,7 +661,8 @@ public final class UserManagerServiceTest { @Test public void testAutoLockAfterInactityForPrivateProfile() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY); @@ -679,7 +684,8 @@ public final class UserManagerServiceTest { @Test public void testSetOrUpdateAutoLockPreference_noPrivateProfile() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); mUms.setOrUpdateAutoLockPreferenceForPrivateProfile( @@ -693,7 +699,8 @@ public final class UserManagerServiceTest { @Test public void testSetOrUpdateAutoLockPreference() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, 0, null); @@ -743,6 +750,8 @@ public final class UserManagerServiceTest { @Test public void testGetProfileIdsExcludingHidden() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES); UserInfo privateProfileUser = mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", @@ -754,7 +763,8 @@ public final class UserManagerServiceTest { @Test public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); UserManagerService mSpiedUms = spy(mUms); int mainUser = mSpiedUms.getMainUserId(); @@ -766,7 +776,8 @@ public final class UserManagerServiceTest { @Test public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0); assertThat(mUms.canAddPrivateProfile(user.id)).isFalse(); @@ -777,7 +788,8 @@ public final class UserManagerServiceTest { @Test public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt()); int mainUser = mUms.getMainUserId(); @@ -789,7 +801,8 @@ public final class UserManagerServiceTest { @Test public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt()); int mainUser = mUms.getMainUserId(); @@ -801,7 +814,8 @@ public final class UserManagerServiceTest { @Test public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt()); int mainUser = mUms.getMainUserId(); @@ -813,7 +827,8 @@ public final class UserManagerServiceTest { @Test public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt()); int mainUser = mUms.getMainUserId(); diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index aec896f383c4..f86ff14218ba 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -64,6 +64,7 @@ import android.content.IntentFilter; import android.content.PermissionChecker; import android.content.res.Resources; import android.hardware.SensorManager; +import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.hardware.display.AmbientDisplayConfiguration; @@ -154,6 +155,8 @@ public class PowerManagerServiceTest { private static final float BRIGHTNESS_FACTOR = 0.7f; private static final boolean BATTERY_SAVER_ENABLED = true; + private static final DeviceState DEVICE_STATE_1 = new DeviceState( + new DeviceState.Configuration.Builder(1 /* identifier */, "" /* name */).build()); @Mock private BatterySaverController mBatterySaverControllerMock; @Mock private BatterySaverPolicy mBatterySaverPolicyMock; @@ -2839,7 +2842,7 @@ public class PowerManagerServiceTest { // Send a display state change event and advance the clock 10. final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue(); - deviceStateCallback.onStateChanged(1); + deviceStateCallback.onDeviceStateChanged(DEVICE_STATE_1); final long timeToAdvance = 10; advanceTime(timeToAdvance); @@ -2849,7 +2852,7 @@ public class PowerManagerServiceTest { assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse(); // Send the same state and ensure that does not trigger an update. - deviceStateCallback.onStateChanged(1); + deviceStateCallback.onDeviceStateChanged(DEVICE_STATE_1); advanceTime(timeToAdvance); final long newTime = timeToAdvance * 2; diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 0089d4cafaad..2724149d859f 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -111,6 +111,8 @@ <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" /> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.CAMERA" /> <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index eb8950384f10..b2ecea1b0302 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -813,7 +813,6 @@ public class AccessibilityManagerServiceTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_DISABLE_CONTINUOUS_SHORTCUT_ON_FORCE_STOP) public void testPackagesForceStopped_fromContinuousService_removesButtonTarget() { final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo(); info_a.setComponentName(COMPONENT_NAME); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index f86cb7bae8dc..cda8b01afceb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -208,7 +208,6 @@ public class AccessibilityServiceConnectionTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK) public void onServiceConnected_addsWindowTokens() { setServiceBinding(COMPONENT_NAME); mConnection.bindLocked(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index b1964e23ff53..b269beb90c75 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -51,7 +51,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.graphics.Color; -import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; @@ -220,18 +219,6 @@ public class AccessibilityUserStateTest { } @Test - // addServiceLocked only calls addWindowTokensForAllDisplays when - // FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK is off, so skip the test if it is on. - @RequiresFlagsDisabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK) - public void addService_flagDisabled_addsWindowTokens() { - when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME); - - mUserState.addServiceLocked(mMockConnection); - - verify(mMockConnection).addWindowTokensForAllDisplays(); - } - - @Test public void reconcileSoftKeyboardMode_whenStateNotMatchSettings_setBothDefault() { // When soft kb show mode is hidden in settings and is auto in state. putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index 3ee5f61bb8cd..95a1f5a8a52f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java @@ -39,7 +39,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.hardware.display.DisplayManager; import android.os.IBinder; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.view.WindowManager; @@ -209,7 +208,6 @@ public class UiAutomationManagerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK) public void registerUiAutomationService_callsAddWindowTokenUsingHandler() { register(0); // registerUiTestAutomationServiceLocked() should not directly call addWindowToken. diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java index 2890078f7638..8a6ba4d484f7 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java @@ -67,7 +67,6 @@ public class AppOpsStartedWatcherTest { // Start some ops appOpsManager.startOp(AppOpsManager.OP_FINE_LOCATION); appOpsManager.startOp(AppOpsManager.OP_CAMERA); - appOpsManager.startOp(AppOpsManager.OP_RECORD_AUDIO); // Verify that we got called for the ops being started final InOrder inOrder = inOrder(listener); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 67b131fcbb75..124970758fa5 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -206,7 +206,6 @@ import libcore.io.Streams; import org.junit.After; import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.MethodRule; @@ -2151,14 +2150,12 @@ public class NetworkPolicyManagerServiceTest { assertFalse(mService.isUidNetworkingBlocked(UID_E, false)); } - @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainEnabled() throws Exception { verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true); } - @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainOnProcStateChange() throws Exception { @@ -2188,7 +2185,6 @@ public class NetworkPolicyManagerServiceTest { assertTrue(mService.isUidNetworkingBlocked(UID_A, false)); } - @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainOnAllowlistChange() throws Exception { @@ -2227,7 +2223,6 @@ public class NetworkPolicyManagerServiceTest { assertFalse(mService.isUidNetworkingBlocked(UID_B, false)); } - @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainOnTempAllowlistChange() throws Exception { @@ -2266,7 +2261,6 @@ public class NetworkPolicyManagerServiceTest { && uidState.procState == procState && uidState.capability == capability; } - @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testUidObserverFiltersProcStateChanges() throws Exception { @@ -2329,7 +2323,6 @@ public class NetworkPolicyManagerServiceTest { waitForUidEventHandlerIdle(); } - @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testUidObserverFiltersStaleChanges() throws Exception { @@ -2350,7 +2343,6 @@ public class NetworkPolicyManagerServiceTest { waitForUidEventHandlerIdle(); } - @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testUidObserverFiltersCapabilityChanges() throws Exception { @@ -2430,7 +2422,6 @@ public class NetworkPolicyManagerServiceTest { assertFalse(mService.isUidNetworkingBlocked(UID_A, false)); } - @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testObsoleteHandleUidChanged() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java index e249cd7311cf..55c48e07162b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java @@ -90,8 +90,6 @@ public class UserRestrictionsUtilsTest { public void testCanDeviceOwnerChange() { assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO)); assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER)); - assertFalse(UserRestrictionsUtils.canDeviceOwnerChange( - UserManager.DISALLOW_ADD_PRIVATE_PROFILE)); assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER)); assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_USER_SWITCH)); } @@ -110,10 +108,6 @@ public class UserRestrictionsUtilsTest { UserManager.DISALLOW_USER_SWITCH, true, false)); - assertFalse(UserRestrictionsUtils.canProfileOwnerChange( - UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - true, - false)); assertTrue(UserRestrictionsUtils.canProfileOwnerChange( UserManager.DISALLOW_ADD_USER, true, diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java index fee6582c1ba4..dff4984adb80 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -43,9 +43,12 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER; +import static org.mockito.ArgumentMatchers.eq; + import static java.util.Collections.unmodifiableMap; import android.content.Context; +import android.content.res.Resources; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import android.view.InputDevice; @@ -57,6 +60,7 @@ import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.rules.RuleChain; @@ -70,9 +74,10 @@ class ShortcutKeyTestBase { @Rule public RuleChain rules = RuleChain.outerRule(mSettingsProviderRule).around(mSetFlagsRule); + private Resources mResources; TestPhoneWindowManager mPhoneWindowManager; DispatchedKeyHandler mDispatchedKeyHandler = event -> false; - final Context mContext = spy(getInstrumentation().getTargetContext()); + Context mContext; /** Modifier key to meta state */ protected static final Map<Integer, Integer> MODIFIER; @@ -90,6 +95,16 @@ class ShortcutKeyTestBase { MODIFIER = unmodifiableMap(map); } + @Before + public void setup() { + mContext = spy(getInstrumentation().getTargetContext()); + mResources = spy(mContext.getResources()); + doReturn(mResources).when(mContext).getResources(); + doReturn(mSettingsProviderRule.mockContentResolver(mContext)) + .when(mContext).getContentResolver(); + } + + /** Same as {@link setUpPhoneWindowManager(boolean)}, without supporting settings update. */ protected final void setUpPhoneWindowManager() { setUpPhoneWindowManager(/* supportSettingsUpdate= */ false); @@ -101,12 +116,14 @@ class ShortcutKeyTestBase { * <p>Subclasses must call this at the start of the test if they intend to interact with phone * window manager. * - * @param supportSettingsUpdate {@code true} if this test should read and listen to provider - * settings values. + * @param supportSettingsUpdate {@code true} to have PWM respond to any Settings changes upon + * instantiation. Although this is supposed to also allow a test to listen to any Settings + * changes after instantiation, MockContentResolver in this class's setup stubs out + * notifyChange(), which prevents SettingsObserver from getting notified of events. So + * we're effectively always instantiating TestPhoneWindowManager with + * supportSettingsUpdate=false. */ protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) { - doReturn(mSettingsProviderRule.mockContentResolver(mContext)) - .when(mContext).getContentResolver(); mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate); } @@ -187,6 +204,23 @@ class ShortcutKeyTestBase { sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY); } + /** + * Since we use SettingsProviderRule to mock the ContentResolver in these + * tests, the settings observer registered by PhoneWindowManager will not + * be triggered automatically by the mock. Use this method to force the + * settings observer change after modifying any settings. + */ + void triggerSettingsObserverChange() { + mPhoneWindowManager.getSettingsObserver().onChange( + // This boolean doesn't matter. This observer does the same thing regardless. + /*selfChange=*/true); + } + + /** Override a resource's return value. */ + void overrideResource(int resId, int expectedBehavior) { + doReturn(expectedBehavior).when(mResources).getInteger(eq(resId)); + } + private void interceptKey(KeyEvent keyEvent) { int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent); if ((actions & ACTION_PASS_TO_USER) != 0) { diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java index 77e7a0a65c82..2e850252765b 100644 --- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java @@ -291,6 +291,101 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { mPhoneWindowManager.assertSwitchToTask(referenceId); } + /** + * Ensure the stem rule is added even when button behaviors are set to nothing. + * + * This makes sure that if stem key behaviors are overridden to NOTHING, then we check the + * XML config as the source of truth upon reboot to see whether a device should have a stem + * key rule. This test walks us through a scenario where a device powers off during Wear's + * Touch Lock mode. + */ + @Test + public void stemKeyRuleIsAddedEvenWhenBehaviorsRemoved() { + // deactivate stem button presses + overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, + PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING); + overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, + PhoneWindowManager.DOUBLE_PRESS_PRIMARY_NOTHING); + overrideBehavior(STEM_PRIMARY_BUTTON_TRIPLE_PRESS, + PhoneWindowManager.TRIPLE_PRESS_PRIMARY_NOTHING); + overrideBehavior(STEM_PRIMARY_BUTTON_LONG_PRESS, + PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING); + + // pretend like we have stem keys enabled in the xmls + overrideResource( + com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior, + SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + + // start the PhoneWindowManager, just like would happen with a reboot + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + // Set the stem behavior back to something normal after boot + overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, + SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + // manually trigger the SettingsObserver's onChange() method because subclasses of + // ShortcutKeyTestBase cannot automatically pick up Settings changes. + triggerSettingsObserverChange(); + + // These calls are required to make the All Apps view show up + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); + mPhoneWindowManager.overrideStartActivity(); + mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + + sendKey(KEYCODE_STEM_PRIMARY); + + // Because the rule was loaded and we changed the behavior back to non-zero, PWM should + // actually perform this action. It would not perform the action if the rule was missing. + mPhoneWindowManager.assertOpenAllAppView(); + } + + /** + * Ensure the stem rule is not added when stem behavior is not defined in the xml. + * + * This is the opposite of the test above. + */ + @Test + public void stemKeyRuleIsNotAddedWhenXmlDoesntDefineIt() { + // deactivate stem button presses + overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, + PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING); + overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, + PhoneWindowManager.DOUBLE_PRESS_PRIMARY_NOTHING); + overrideBehavior(STEM_PRIMARY_BUTTON_TRIPLE_PRESS, + PhoneWindowManager.TRIPLE_PRESS_PRIMARY_NOTHING); + overrideBehavior(STEM_PRIMARY_BUTTON_LONG_PRESS, + PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING); + + // pretend like we do not have stem keys enabled in the xmls + overrideResource( + com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior, + PhoneWindowManager.SHORT_PRESS_PRIMARY_NOTHING); + overrideResource( + com.android.internal.R.integer.config_longPressOnStemPrimaryBehavior, + PhoneWindowManager.LONG_PRESS_PRIMARY_NOTHING); + + // start the PhoneWindowManager, just like would happen with a reboot + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + // Set the stem behavior back to something normal after boot + // (Despite this fact, a stem press shouldn't have any behavior because there's no rule.) + overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, + SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + // manually trigger the SettingsObserver's onChange() method because subclasses of + // ShortcutKeyTestBase cannot automatically pick up Settings changes. + triggerSettingsObserverChange(); + + // These calls are required to make the All Apps view show up + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); + mPhoneWindowManager.overrideStartActivity(); + mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + + sendKey(KEYCODE_STEM_PRIMARY); + + // Because the rule was not loaded, PWM should not actually perform this action, even + // though the Settings override is set to non-null. + mPhoneWindowManager.assertNotOpenAllAppView(); + } + private void overrideBehavior(String key, int expectedBehavior) { Settings.Global.putLong(mContext.getContentResolver(), key, expectedBehavior); } diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 0776c512b61d..52df010bd588 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -66,6 +66,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; @@ -246,6 +247,13 @@ class TestPhoneWindowManager { } } + /** + * {@link TestPhoneWindowManager}'s constructor. + * + * @param context The {@Context} to be used in any Context-related actions. + * @param supportSettingsUpdate {@code true} if this object should read and listen to provider + * settings values. + */ TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) { MockitoAnnotations.initMocks(this); mHandler = new Handler(mTestLooper.getLooper()); @@ -416,6 +424,13 @@ class TestPhoneWindowManager { mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE); } + /** + * Provide access to the SettingsObserver so that tests can manually trigger Settings changes. + */ + ContentObserver getSettingsObserver() { + return mPhoneWindowManager.mSettingsObserver; + } + long getCurrentTime() { return mClock.now(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 10cfb5fdd922..3bd6496a01dd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -623,6 +623,23 @@ public class TaskTests extends WindowTestsBase { doReturn(true).when(root).fillsParent(); } + @Test + public void testIsTopActivityTranslucent() { + DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); + final Task task = rootTask.getBottomMostTask(); + final ActivityRecord root = task.getTopNonFinishingActivity(); + spyOn(mWm.mLetterboxConfiguration); + spyOn(root); + + doReturn(false).when(root).fillsParent(); + assertTrue(task.getTaskInfo().isTopActivityTransparent); + + doReturn(true).when(root).fillsParent(); + assertFalse(task.getTaskInfo().isTopActivityTransparent); + } + /** * Tests that a task with forced orientation has orientation-consistent bounds within the * parent. diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 03b695d170ad..43b424fab907 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -1376,8 +1376,9 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(w1.syncNextBuffer()); assertTrue(w2.syncNextBuffer()); - // A drawn window can complete the sync state automatically. + // A drawn window in non-explicit sync can complete the sync state automatically. w1.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; + w1.mPrepareSyncSeqId = 0; makeLastConfigReportedToClient(w1, true /* visible */); mWm.mSyncEngine.onSurfacePlacement(); verify(mockCallback).onTransactionReady(anyInt(), any()); diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 77b263824b78..9acda5f249d2 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -142,8 +142,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser "/sys/class/android_usb/android0/state"; private static final String RNDIS_ETH_ADDR_PATH = "/sys/class/android_usb/android0/f_rndis/ethaddr"; - private static final String AUDIO_SOURCE_PCM_PATH = - "/sys/class/android_usb/android0/f_audio_source/pcm"; private static final String MIDI_ALSA_PATH = "/sys/class/android_usb/android0/f_midi/alsa"; @@ -172,8 +170,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private static final int MSG_UPDATE_USB_SPEED = 22; private static final int MSG_UPDATE_HAL_VERSION = 23; - private static final int AUDIO_MODE_SOURCE = 1; - // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, // which need debouncing. @@ -464,7 +460,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser int operationId = sUsbOperationCount.incrementAndGet(); mAccessoryStrings = nativeGetAccessoryStrings(); - boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE); // don't start accessory mode if our mandatory strings have not been set boolean enableAccessory = (mAccessoryStrings != null && mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null && @@ -474,9 +469,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser if (enableAccessory) { functions |= UsbManager.FUNCTION_ACCESSORY; } - if (enableAudio) { - functions |= UsbManager.FUNCTION_AUDIO_SOURCE; - } if (functions != UsbManager.FUNCTION_NONE) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_MODE_ENTER_TIMEOUT), @@ -2490,6 +2482,4 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private native FileDescriptor nativeOpenControl(String usbFunction); private native boolean nativeIsStartRequested(); - - private native int nativeGetAudioMode(); } diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index 5ba5ee8836e1..8b9a93bd92a4 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -16,6 +16,8 @@ package android.telecom; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,8 +30,11 @@ import android.telephony.PreciseDisconnectCause; import android.telephony.ims.ImsReasonInfo; import android.text.TextUtils; +import androidx.annotation.IntDef; + import com.android.server.telecom.flags.Flags; +import java.lang.annotation.Retention; import java.util.Objects; /** @@ -42,48 +47,69 @@ import java.util.Objects; public final class DisconnectCause implements Parcelable { /** Disconnected because of an unknown or unspecified reason. */ - public static final int UNKNOWN = TelecomProtoEnums.UNKNOWN; // = 0 + public static final int UNKNOWN = 0; /** Disconnected because there was an error, such as a problem with the network. */ - public static final int ERROR = TelecomProtoEnums.ERROR; // = 1 + public static final int ERROR = 1; /** Disconnected because of a local user-initiated action, such as hanging up. */ - public static final int LOCAL = TelecomProtoEnums.LOCAL; // = 2 + public static final int LOCAL = 2; /** * Disconnected because the remote party hung up an ongoing call, or because an outgoing call * was not answered by the remote party. */ - public static final int REMOTE = TelecomProtoEnums.REMOTE; // = 3 + public static final int REMOTE = 3; /** Disconnected because it has been canceled. */ - public static final int CANCELED = TelecomProtoEnums.CANCELED; // = 4 + public static final int CANCELED = 4; /** Disconnected because there was no response to an incoming call. */ - public static final int MISSED = TelecomProtoEnums.MISSED; // = 5 + public static final int MISSED = 5; /** Disconnected because the user rejected an incoming call. */ - public static final int REJECTED = TelecomProtoEnums.REJECTED; // = 6 + public static final int REJECTED = 6; /** Disconnected because the other party was busy. */ - public static final int BUSY = TelecomProtoEnums.BUSY; // = 7 + public static final int BUSY = 7; /** * Disconnected because of a restriction on placing the call, such as dialing in airplane * mode. */ - public static final int RESTRICTED = TelecomProtoEnums.RESTRICTED; // = 8 + public static final int RESTRICTED = 8; /** Disconnected for reason not described by other disconnect codes. */ - public static final int OTHER = TelecomProtoEnums.OTHER; // = 9 + public static final int OTHER = 9; /** * Disconnected because the connection manager did not support the call. The call will be tried * again without a connection manager. See {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. */ - public static final int CONNECTION_MANAGER_NOT_SUPPORTED = - TelecomProtoEnums.CONNECTION_MANAGER_NOT_SUPPORTED; // = 10 + public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; /** * Disconnected because the user did not locally answer the incoming call, but it was answered * on another device where the call was ringing. */ - public static final int ANSWERED_ELSEWHERE = TelecomProtoEnums.ANSWERED_ELSEWHERE; // = 11 + public static final int ANSWERED_ELSEWHERE = 11; /** * Disconnected because the call was pulled from the current device to another device. */ - public static final int CALL_PULLED = TelecomProtoEnums.CALL_PULLED; // = 12 + public static final int CALL_PULLED = 12; + + /** + * @hide + */ + @Retention(SOURCE) + @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) + @IntDef({ + UNKNOWN, + ERROR, + LOCAL, + REMOTE, + CANCELED, + MISSED, + REJECTED, + BUSY, + RESTRICTED, + OTHER, + CONNECTION_MANAGER_NOT_SUPPORTED, + ANSWERED_ELSEWHERE, + CALL_PULLED + }) + public @interface DisconnectCauseCode {} /** * Reason code (returned via {@link #getReason()}) which indicates that a call could not be @@ -116,7 +142,7 @@ public final class DisconnectCause implements Parcelable { */ public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED"; - private int mDisconnectCode; + private @DisconnectCauseCode int mDisconnectCode; private CharSequence mDisconnectLabel; private CharSequence mDisconnectDescription; private String mDisconnectReason; @@ -130,7 +156,7 @@ public final class DisconnectCause implements Parcelable { * * @param code The code for the disconnect cause. */ - public DisconnectCause(int code) { + public DisconnectCause(@DisconnectCauseCode int code) { this(code, null, null, null, ToneGenerator.TONE_UNKNOWN); } @@ -140,7 +166,7 @@ public final class DisconnectCause implements Parcelable { * @param code The code for the disconnect cause. * @param reason The reason for the disconnect. */ - public DisconnectCause(int code, String reason) { + public DisconnectCause(@DisconnectCauseCode int code, String reason) { this(code, null, null, reason, ToneGenerator.TONE_UNKNOWN); } @@ -152,7 +178,8 @@ public final class DisconnectCause implements Parcelable { * @param description The localized description to show to the user to explain the disconnect. * @param reason The reason for the disconnect. */ - public DisconnectCause(int code, CharSequence label, CharSequence description, String reason) { + public DisconnectCause(@DisconnectCauseCode int code, CharSequence label, + CharSequence description, String reason) { this(code, label, description, reason, ToneGenerator.TONE_UNKNOWN); } @@ -165,8 +192,8 @@ public final class DisconnectCause implements Parcelable { * @param reason The reason for the disconnect. * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}. */ - public DisconnectCause(int code, CharSequence label, CharSequence description, String reason, - int toneToPlay) { + public DisconnectCause(@DisconnectCauseCode int code, CharSequence label, + CharSequence description, String reason, int toneToPlay) { this(code, label, description, reason, toneToPlay, android.telephony.DisconnectCause.ERROR_UNSPECIFIED, PreciseDisconnectCause.ERROR_UNSPECIFIED, null /* imsReasonInfo */); @@ -186,8 +213,8 @@ public final class DisconnectCause implements Parcelable { * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available. * @hide */ - public DisconnectCause(int code, @NonNull CharSequence label, - @NonNull CharSequence description, @NonNull String reason, + public DisconnectCause(@DisconnectCauseCode int code, @Nullable CharSequence label, + @Nullable CharSequence description, @Nullable String reason, int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause, @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause, @Nullable ImsReasonInfo imsReasonInfo) { @@ -206,7 +233,7 @@ public final class DisconnectCause implements Parcelable { * * @return The disconnect code. */ - public int getCode() { + public @DisconnectCauseCode int getCode() { return mDisconnectCode; } @@ -301,28 +328,24 @@ public final class DisconnectCause implements Parcelable { @SystemApi @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) public static final class Builder { - private int mDisconnectCode; + private @DisconnectCauseCode int mDisconnectCode; private CharSequence mDisconnectLabel; private CharSequence mDisconnectDescription; private String mDisconnectReason; - private int mToneToPlay; + private int mToneToPlay = ToneGenerator.TONE_UNKNOWN; private int mTelephonyDisconnectCause; private int mTelephonyPreciseDisconnectCause; private ImsReasonInfo mImsReasonInfo; - /** - * Sets the code for the reason for this disconnect. - * @param code The code denoting the type of disconnect. - */ - public @NonNull DisconnectCause.Builder setCode(int code) { + public Builder(@DisconnectCauseCode int code) { mDisconnectCode = code; - return this; } /** * Sets a label which explains the reason for the disconnect cause, used for display in the * user interface. * @param label The label to associate with the disconnect cause. + * @return The {@link DisconnectCause} builder instance. */ public @NonNull DisconnectCause.Builder setLabel(@Nullable CharSequence label) { mDisconnectLabel = label; @@ -333,6 +356,7 @@ public final class DisconnectCause implements Parcelable { * Sets a description which provides the reason for the disconnect cause, used for display * in the user interface. * @param description The description to associate with the disconnect cause. + * @return The {@link DisconnectCause} builder instance. */ public @NonNull DisconnectCause.Builder setDescription( @Nullable CharSequence description) { @@ -344,6 +368,7 @@ public final class DisconnectCause implements Parcelable { * Sets a reason providing explanation for the disconnect (intended for logging and not for * displaying in the user interface). * @param reason The reason for the disconnect. + * @return The {@link DisconnectCause} builder instance. */ public @NonNull DisconnectCause.Builder setReason(@NonNull String reason) { mDisconnectReason = reason; @@ -353,6 +378,7 @@ public final class DisconnectCause implements Parcelable { /** * Sets the tone to play when disconnected. * @param toneToPlay The tone as defined in {@link ToneGenerator} to play when disconnected. + * @return The {@link DisconnectCause} builder instance. */ public @NonNull DisconnectCause.Builder setTone(int toneToPlay) { mToneToPlay = toneToPlay; @@ -363,6 +389,7 @@ public final class DisconnectCause implements Parcelable { * Sets the telephony {@link android.telephony.DisconnectCause} for the call (used * internally by Telecom for providing extra debug information from Telephony). * @param telephonyDisconnectCause The disconnect cause as provided by Telephony. + * @return The {@link DisconnectCause} builder instance. */ public @NonNull DisconnectCause.Builder setTelephonyDisconnectCause( @Annotation.DisconnectCauses int telephonyDisconnectCause) { @@ -375,6 +402,7 @@ public final class DisconnectCause implements Parcelable { * internally by Telecom for providing extra debug information from Telephony). * @param telephonyPreciseDisconnectCause The precise disconnect cause as provided by * Telephony. + * @return The {@link DisconnectCause} builder instance. */ public @NonNull DisconnectCause.Builder setTelephonyPreciseDisconnectCause( @@ -384,10 +412,11 @@ public final class DisconnectCause implements Parcelable { } /** - * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection. This + * Sets the telephony {@link ImsReasonInfo} associated with the call disconnection. This * is only used internally by Telecom for providing extra debug information from Telephony. * * @param imsReasonInfo The {@link ImsReasonInfo} or {@code null} if not known. + * @return The {@link DisconnectCause} builder instance. */ public @NonNull DisconnectCause.Builder setImsReasonInfo( @Nullable ImsReasonInfo imsReasonInfo) { diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index e3ce766b40bc..ebabbf911399 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -512,6 +512,7 @@ public class EuiccManager { EUICC_ACTIVATION_TYPE_BACKUP, EUICC_ACTIVATION_TYPE_TRANSFER, EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED, + EUICC_ACTIVATION_TYPE_TRANSFER_FINAL_HOLD, }) public @interface EuiccActivationType{} @@ -555,6 +556,15 @@ public class EuiccManager { public static final int EUICC_ACTIVATION_TYPE_ACCOUNT_REQUIRED = 4; /** + * The activation flow of eSIM transfer to block the transfer process before B&R flow. + * This is needed to avoid connection overlapping between eSIM connection B&R connection. + * + * @hide + */ + // TODO(b/329212614): add system api annotation during the allowed api timeline. + public static final int EUICC_ACTIVATION_TYPE_TRANSFER_FINAL_HOLD = 5; + + /** * Euicc OTA update status which can be got by {@link #getOtaStatus} * @removed mistakenly exposed as system-api previously */ diff --git a/tools/aapt2/optimize/VersionCollapser.cpp b/tools/aapt2/optimize/VersionCollapser.cpp index cd791bda250b..27fff9a05334 100644 --- a/tools/aapt2/optimize/VersionCollapser.cpp +++ b/tools/aapt2/optimize/VersionCollapser.cpp @@ -70,7 +70,7 @@ FilterIterator<Iterator, Pred> make_filter_iterator(Iterator begin, * exception is when there is no exact matching resource for the minSdk. The next smallest one will * be kept. */ -static void CollapseVersions(int min_sdk, ResourceEntry* entry) { +static void CollapseVersions(IAaptContext* context, int min_sdk, ResourceEntry* entry) { // First look for all sdks less than minSdk. for (auto iter = entry->values.rbegin(); iter != entry->values.rend(); ++iter) { @@ -102,7 +102,14 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { auto filter_iter = make_filter_iterator(iter + 1, entry->values.rend(), pred); while (filter_iter.HasNext()) { - filter_iter.Next() = {}; + auto& next = filter_iter.Next(); + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(android::DiagMessage() + << "removing configuration " << next->config.to_string() + << " for entry: " << entry->name + << ", because its SDK version is smaller than minSdk"); + } + next = {}; } } } @@ -126,6 +133,12 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { util::make_unique<ResourceConfigValue>( config_value->config.CopyWithoutSdkVersion(), config_value->product); + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(android::DiagMessage() + << "overriding resource: " << entry->name + << ", removing SDK version from configuration " + << config_value->config.to_string()); + } new_value->value = std::move(config_value->value); config_value = std::move(new_value); @@ -147,10 +160,14 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { bool VersionCollapser::Consume(IAaptContext* context, ResourceTable* table) { TRACE_NAME("VersionCollapser::Consume"); const int min_sdk = context->GetMinSdkVersion(); + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(android::DiagMessage() + << "Running VersionCollapser with minSdk = " << min_sdk); + } for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { - CollapseVersions(min_sdk, entry.get()); + CollapseVersions(context, min_sdk, entry.get()); } } } |