diff options
403 files changed, 11393 insertions, 2841 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 7e6c30f0719a..28462217e5de 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -75,6 +75,7 @@ aconfig_srcjars = [ ":com.android.internal.pm.pkg.component.flags-aconfig-java{.generated_srcjars}", ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", ":com.android.media.flags.editing-aconfig-java{.generated_srcjars}", + ":com.android.media.flags.projection-aconfig-java{.generated_srcjars}", ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}", ":com.android.server.flags.services-aconfig-java{.generated_srcjars}", ":com.android.text.flags-aconfig-java{.generated_srcjars}", @@ -566,6 +567,21 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// MediaProjection +aconfig_declarations { + name: "com.android.media.flags.projection-aconfig", + package: "com.android.media.projection.flags", + srcs: [ + "media/java/android/media/flags/projection.aconfig", + ], +} + +java_aconfig_library { + name: "com.android.media.flags.projection-aconfig-java", + aconfig_declarations: "com.android.media.flags.projection-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media TV aconfig_declarations { name: "android.media.tv.flags-aconfig", diff --git a/Android.bp b/Android.bp index e12f74fcd7ca..870df5a5723e 100644 --- a/Android.bp +++ b/Android.bp @@ -508,6 +508,8 @@ java_library { lint: { baseline_filename: "lint-baseline.xml", }, + // For jarjar repackaging + jarjar_prefix: "com.android.internal.hidden_from_bootclasspath", } java_library { diff --git a/Ravenwood.bp b/Ravenwood.bp index 2babf6a56ab4..633702233cf4 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -93,16 +93,33 @@ java_library { ], } +// Jars in "ravenwood-runtime" are set to the classpath, sorted alphabetically. +// Rename some of the dependencies to make sure they're included in the intended order. +java_genrule { + name: "100-framework-minus-apex.ravenwood", + cmd: "cp $(in) $(out)", + srcs: [":framework-minus-apex.ravenwood"], + out: ["100-framework-minus-apex.ravenwood.jar"], + visibility: ["//visibility:private"], +} + +java_genrule { + // Use 200 to make sure it comes before the mainline stub ("all-updatable..."). + name: "200-kxml2-android", + cmd: "cp $(in) $(out)", + srcs: [":kxml2-android"], + out: ["200-kxml2-android.jar"], + visibility: ["//visibility:private"], +} + android_ravenwood_libgroup { name: "ravenwood-runtime", libs: [ - // Prefixed with "200" to ensure it's sorted early in Tradefed classpath - // so that we provide a concrete implementation before Mainline stubs + "100-framework-minus-apex.ravenwood", "200-kxml2-android", "all-updatable-modules-system-stubs", "android.test.mock.ravenwood", - "framework-minus-apex.ravenwood", - "hoststubgen-helper-framework-runtime.ravenwood", + "ravenwood-helper-runtime", "hoststubgen-helper-runtime.ravenwood", // Provide runtime versions of utils linked in below diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index f819f15b430f..bd00c03741f3 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -3852,7 +3852,7 @@ public class JobSchedulerService extends com.android.server.SystemService // the other jobs that will use this network. if (DEBUG) { Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking " - + batchedJobs.size() + " jobs on " + network + + (batchedJobs.size() - unbatchedJobCount) + " jobs on " + network + " because of unbatched job"); } jobsToRun.addAll(batchedJobs); @@ -3892,8 +3892,12 @@ public class JobSchedulerService extends com.android.server.SystemService // Some job is going to use the CPU anyway. Might as well run all the other // CPU-only jobs. if (DEBUG) { + final Integer unbatchedJobCountObj = mUnbatchedJobCount.get(null); + final int unbatchedJobCount = + unbatchedJobCountObj == null ? 0 : unbatchedJobCountObj; Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking " - + batchedNonNetworkedJobs.size() + " non-network jobs"); + + (batchedNonNetworkedJobs.size() - unbatchedJobCount) + + " non-network jobs"); } jobsToRun.addAll(batchedNonNetworkedJobs); } else if (batchedNonNetworkedJobs.size() >= minReadyCount) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 6ed42ec990ea..3219f7e5ce20 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -21,6 +21,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_SATELLITE; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; @@ -139,8 +140,9 @@ public final class ConnectivityController extends RestrictingController implemen static final SparseIntArray sNetworkTransportAffinities = new SparseIntArray(); static { sNetworkTransportAffinities.put(TRANSPORT_CELLULAR, TRANSPORT_AFFINITY_AVOID); - sNetworkTransportAffinities.put(TRANSPORT_WIFI, TRANSPORT_AFFINITY_PREFER); sNetworkTransportAffinities.put(TRANSPORT_ETHERNET, TRANSPORT_AFFINITY_PREFER); + sNetworkTransportAffinities.put(TRANSPORT_SATELLITE, TRANSPORT_AFFINITY_AVOID); + sNetworkTransportAffinities.put(TRANSPORT_WIFI, TRANSPORT_AFFINITY_PREFER); } private final CcConfig mCcConfig; diff --git a/boot/hiddenapi/hiddenapi-unsupported.txt b/boot/hiddenapi/hiddenapi-unsupported.txt index 26dc7003a828..adcc3df2d7fe 100644 --- a/boot/hiddenapi/hiddenapi-unsupported.txt +++ b/boot/hiddenapi/hiddenapi-unsupported.txt @@ -133,8 +133,6 @@ Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)La Landroid/hardware/location/IContextHubService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IContextHubService; Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/location/ICountryListener$Stub;-><init>()V -Landroid/location/IGeocodeProvider$Stub;-><init>()V -Landroid/location/IGeocodeProvider$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/IGeocodeProvider; Landroid/location/ILocationListener$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/location/ILocationListener$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/location/ILocationListener$Stub;-><init>()V diff --git a/core/TEST_MAPPING b/core/TEST_MAPPING index f1e4d0ee4906..fd571c95f568 100644 --- a/core/TEST_MAPPING +++ b/core/TEST_MAPPING @@ -20,15 +20,5 @@ "core/tests/coretests/src/com/android/internal/inputmethod/.*" ] } - ], - "postsubmit": [ - { - "name": "CtsContactKeysManagerTestCases", - "options": [ - { - "include-filter": "android.provider.cts.contactkeys." - } - ] - } - ] + ] } diff --git a/core/api/current.txt b/core/api/current.txt index 2d03be7bfa31..0c118114d159 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -144,10 +144,12 @@ package android { field public static final String MANAGE_DEVICE_POLICY_AUDIO_OUTPUT = "android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT"; field public static final String MANAGE_DEVICE_POLICY_AUTOFILL = "android.permission.MANAGE_DEVICE_POLICY_AUTOFILL"; field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE"; + field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = "android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL"; field public static final String MANAGE_DEVICE_POLICY_BLUETOOTH = "android.permission.MANAGE_DEVICE_POLICY_BLUETOOTH"; field public static final String MANAGE_DEVICE_POLICY_BUGREPORT = "android.permission.MANAGE_DEVICE_POLICY_BUGREPORT"; field public static final String MANAGE_DEVICE_POLICY_CALLS = "android.permission.MANAGE_DEVICE_POLICY_CALLS"; field public static final String MANAGE_DEVICE_POLICY_CAMERA = "android.permission.MANAGE_DEVICE_POLICY_CAMERA"; + field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE"; field public static final String MANAGE_DEVICE_POLICY_CERTIFICATES = "android.permission.MANAGE_DEVICE_POLICY_CERTIFICATES"; field public static final String MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE = "android.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE"; field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String MANAGE_DEVICE_POLICY_CONTENT_PROTECTION = "android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION"; @@ -169,6 +171,7 @@ package android { field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"; field public static final String MANAGE_DEVICE_POLICY_METERED_DATA = "android.permission.MANAGE_DEVICE_POLICY_METERED_DATA"; field public static final String MANAGE_DEVICE_POLICY_MICROPHONE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE"; + field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE"; field public static final String MANAGE_DEVICE_POLICY_MOBILE_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK"; field public static final String MANAGE_DEVICE_POLICY_MODIFY_USERS = "android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS"; field public static final String MANAGE_DEVICE_POLICY_MTE = "android.permission.MANAGE_DEVICE_POLICY_MTE"; @@ -454,6 +457,7 @@ package android { field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 field public static final int allowClickWhenDisabled = 16844312; // 0x1010618 + field @FlaggedApi("android.security.asm_restrictions_enabled") public static final int allowCrossUidActivitySwitchFromBelow; field public static final int allowEmbedded = 16843765; // 0x10103f5 field public static final int allowGameAngleDriver = 16844376; // 0x1010658 field public static final int allowGameDownscaling = 16844377; // 0x1010659 @@ -687,6 +691,7 @@ package android { field public static final int defaultHeight = 16844021; // 0x10104f5 field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale; field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504 + field @FlaggedApi("android.nfc.Flags.FLAG_OBSERVE_MODE") public static final int defaultToObserveMode; field public static final int defaultValue = 16843245; // 0x10101ed field public static final int defaultWidth = 16844020; // 0x10104f4 field public static final int delay = 16843212; // 0x10101cc @@ -1491,6 +1496,7 @@ package android { field @Deprecated public static final int sharedUserLabel = 16843361; // 0x1010261 field public static final int sharedUserMaxSdkVersion = 16844365; // 0x101064d field public static final int shell = 16844180; // 0x1010594 + field @FlaggedApi("com.android.text.flags.use_bounds_for_width") public static final int shiftDrawingOffsetForStartOverhang; field public static final int shortcutDisabledMessage = 16844075; // 0x101052b field public static final int shortcutId = 16844072; // 0x1010528 field public static final int shortcutLongLabel = 16844074; // 0x101052a @@ -4423,12 +4429,14 @@ package android.app { method @Deprecated public void finishFromChild(android.app.Activity); method @Nullable public android.app.ActionBar getActionBar(); method public final android.app.Application getApplication(); + method @FlaggedApi("android.security.content_uri_permission_apis") @Nullable public android.app.ComponentCaller getCaller(); method @Nullable public android.content.ComponentName getCallingActivity(); method @Nullable public String getCallingPackage(); method public int getChangingConfigurations(); method public android.content.ComponentName getComponentName(); method public android.transition.Scene getContentScene(); method public android.transition.TransitionManager getContentTransitionManager(); + method @FlaggedApi("android.security.content_uri_permission_apis") @NonNull public android.app.ComponentCaller getCurrentCaller(); method @Nullable public android.view.View getCurrentFocus(); method @Deprecated public android.app.FragmentManager getFragmentManager(); method @FlaggedApi("android.security.content_uri_permission_apis") @NonNull public android.app.ComponentCaller getInitialCaller(); @@ -4521,6 +4529,7 @@ package android.app { method public boolean onNavigateUp(); method @Deprecated public boolean onNavigateUpFromChild(android.app.Activity); method protected void onNewIntent(android.content.Intent); + method @FlaggedApi("android.security.content_uri_permission_apis") public void onNewIntent(@NonNull android.content.Intent, @NonNull android.app.ComponentCaller); method public boolean onOptionsItemSelected(@NonNull android.view.MenuItem); method public void onOptionsMenuClosed(android.view.Menu); method public void onPanelClosed(int, @NonNull android.view.Menu); @@ -4608,6 +4617,7 @@ package android.app { method public void setImmersive(boolean); method public void setInheritShowWhenLocked(boolean); method public void setIntent(android.content.Intent); + method @FlaggedApi("android.security.content_uri_permission_apis") public void setIntent(@Nullable android.content.Intent, @Nullable android.app.ComponentCaller); method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle); method public final void setMediaController(android.media.session.MediaController); method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams); @@ -5329,6 +5339,7 @@ package android.app { method public int getStartType(); method public int getStartupState(); method @NonNull public java.util.Map<java.lang.Integer,java.lang.Long> getStartupTimestamps(); + method @FlaggedApi("android.content.pm.stay_stopped") public boolean wasForceStopped(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationStartInfo> CREATOR; field public static final int LAUNCH_MODE_SINGLE_INSTANCE = 2; // 0x2 @@ -5458,11 +5469,15 @@ package android.app { method public int getDeferralPolicy(); method @Nullable public String getDeliveryGroupMatchingKey(); method public int getDeliveryGroupPolicy(); + method @FlaggedApi("android.app.bcast_event_timestamps") public long getEventTriggerTimestampMillis(); + method @FlaggedApi("android.app.bcast_event_timestamps") public long getRemoteEventTriggerTimestampMillis(); method public boolean isShareIdentityEnabled(); method @NonNull public static android.app.BroadcastOptions makeBasic(); method @NonNull public android.app.BroadcastOptions setDeferralPolicy(int); method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String); method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int); + method @FlaggedApi("android.app.bcast_event_timestamps") public void setEventTriggerTimestampMillis(long); + method @FlaggedApi("android.app.bcast_event_timestamps") public void setRemoteEventTriggerTimestampMillis(long); method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean); method @NonNull public android.os.Bundle toBundle(); field public static final int DEFERRAL_POLICY_DEFAULT = 0; // 0x0 @@ -6090,6 +6105,7 @@ package android.app { method public void callActivityOnCreate(android.app.Activity, android.os.Bundle, android.os.PersistableBundle); method public void callActivityOnDestroy(android.app.Activity); method public void callActivityOnNewIntent(android.app.Activity, android.content.Intent); + method @FlaggedApi("android.security.content_uri_permission_apis") public void callActivityOnNewIntent(@NonNull android.app.Activity, @NonNull android.content.Intent, @NonNull android.app.ComponentCaller); method public void callActivityOnPause(android.app.Activity); method public void callActivityOnPictureInPictureRequested(@NonNull android.app.Activity); method public void callActivityOnPostCreate(@NonNull android.app.Activity, @Nullable android.os.Bundle); @@ -8016,7 +8032,7 @@ package android.app.admin { method public CharSequence getDeviceOwnerLockScreenInfo(); method @Nullable public String getDevicePolicyManagementRoleHolderPackage(); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); - method @NonNull public String getEnrollmentSpecificId(); + method @FlaggedApi("android.app.admin.flags.permission_migration_for_zero_trust_api_enabled") @NonNull @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES, conditional=true) public String getEnrollmentSpecificId(); method @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET, conditional=true) public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName); method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName); @@ -8055,7 +8071,7 @@ package android.app.admin { method @Deprecated public int getPasswordMinimumSymbols(@Nullable android.content.ComponentName); method @Deprecated public int getPasswordMinimumUpperCase(@Nullable android.content.ComponentName); method @Deprecated public int getPasswordQuality(@Nullable android.content.ComponentName); - method @Nullable public android.app.admin.SystemUpdateInfo getPendingSystemUpdate(@NonNull android.content.ComponentName); + method @FlaggedApi("android.app.admin.flags.permission_migration_for_zero_trust_api_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional=true) public android.app.admin.SystemUpdateInfo getPendingSystemUpdate(@Nullable android.content.ComponentName); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, conditional=true) public int getPermissionGrantState(@Nullable android.content.ComponentName, @NonNull String, @NonNull String); method public int getPermissionPolicy(android.content.ComponentName); method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName); @@ -8112,6 +8128,7 @@ package android.app.admin { method public boolean isLogoutEnabled(); method public boolean isManagedProfile(@NonNull android.content.ComponentName); method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName); + method @FlaggedApi("android.app.admin.flags.is_mte_policy_enforced") public static boolean isMtePolicyEnforced(); method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName); method public boolean isOrganizationOwnedDeviceWithManagedProfile(); method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName); @@ -12384,6 +12401,8 @@ package android.content.pm { method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle); method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle); method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles(); + method @FlaggedApi("android.app.admin.flags.allow_querying_profile_type") public boolean isManagedProfile(@NonNull android.os.UserHandle); + method @FlaggedApi("android.app.admin.flags.allow_querying_profile_type") public boolean isProfile(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity); method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity, @Nullable android.os.Bundle); method public void startMainActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); @@ -12474,8 +12493,11 @@ 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 @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 @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 public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int); method @Nullable public android.content.IntentSender getShortcutConfigActivityIntent(@NonNull android.content.pm.LauncherActivityInfo); @@ -12557,6 +12579,14 @@ package android.content.pm { field public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1024; // 0x400 } + @FlaggedApi("android.os.allow_private_profile") public final class LauncherUserInfo implements android.os.Parcelable { + method @FlaggedApi("android.os.allow_private_profile") public int describeContents(); + method @FlaggedApi("android.os.allow_private_profile") public int getUserSerialNumber(); + method @FlaggedApi("android.os.allow_private_profile") @NonNull public String getUserType(); + method @FlaggedApi("android.os.allow_private_profile") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("android.os.allow_private_profile") @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherUserInfo> CREATOR; + } + public final class ModuleInfo implements android.os.Parcelable { method public int describeContents(); method @Nullable public CharSequence getName(); @@ -15670,6 +15700,7 @@ package android.graphics { method public boolean clipOutRect(@NonNull android.graphics.Rect); method public boolean clipOutRect(float, float, float, float); method public boolean clipOutRect(int, int, int, int); + method @FlaggedApi("com.android.graphics.hwui.flags.clip_shader") public void clipOutShader(@NonNull android.graphics.Shader); method @Deprecated public boolean clipPath(@NonNull android.graphics.Path, @NonNull android.graphics.Region.Op); method public boolean clipPath(@NonNull android.graphics.Path); method @Deprecated public boolean clipRect(@NonNull android.graphics.RectF, @NonNull android.graphics.Region.Op); @@ -15679,6 +15710,7 @@ package android.graphics { method @Deprecated public boolean clipRect(float, float, float, float, @NonNull android.graphics.Region.Op); method public boolean clipRect(float, float, float, float); method public boolean clipRect(int, int, int, int); + method @FlaggedApi("com.android.graphics.hwui.flags.clip_shader") public void clipShader(@NonNull android.graphics.Shader); method public void concat(@Nullable android.graphics.Matrix); method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44); method public void disableZ(); @@ -19275,6 +19307,7 @@ package android.hardware.camera2 { method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions(); method public boolean isCaptureProcessProgressAvailable(int); method public boolean isPostviewAvailable(int); + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE; field public static final int EXTENSION_AUTOMATIC = 0; // 0x0 field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1 field public static final int EXTENSION_BOKEH = 2; // 0x2 @@ -19876,6 +19909,32 @@ package android.hardware.camera2 { field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100 } + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureRequest { + ctor public ExtensionCaptureRequest(); + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT; + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureResult { + ctor public ExtensionCaptureResult(); + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT; + } + public class MultiResolutionImageReader implements java.lang.AutoCloseable { ctor public MultiResolutionImageReader(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int); method public void close(); @@ -19961,11 +20020,14 @@ package android.hardware.camera2.params { public final class ExtensionSessionConfiguration { ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void clearColorSpace(); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") @Nullable public android.graphics.ColorSpace getColorSpace(); method @NonNull public java.util.concurrent.Executor getExecutor(); method public int getExtension(); method @NonNull public java.util.List<android.hardware.camera2.params.OutputConfiguration> getOutputConfigurations(); method @Nullable public android.hardware.camera2.params.OutputConfiguration getPostviewOutputConfiguration(); method @NonNull public android.hardware.camera2.CameraExtensionSession.StateCallback getStateCallback(); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(@NonNull android.graphics.ColorSpace.Named); method public void setPostviewOutputConfiguration(@Nullable android.hardware.camera2.params.OutputConfiguration); } @@ -22690,7 +22752,6 @@ package android.media { public final class MediaCodec.QueueRequest { method public void queue(); - method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setBufferInfos(@NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>); method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer); method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, @NonNull android.media.MediaCodec.CryptoInfo); method @NonNull public android.media.MediaCodec.QueueRequest setFlags(int); @@ -22700,6 +22761,7 @@ package android.media { method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int); method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long); method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setMultiFrameEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>, @NonNull java.util.ArrayDeque<android.media.MediaCodec.CryptoInfo>); + method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setMultiFrameLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>); method @NonNull public android.media.MediaCodec.QueueRequest setPresentationTimeUs(long); method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String); } @@ -22708,12 +22770,16 @@ package android.media { method @NonNull public String getCanonicalName(); method public android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(String); method @NonNull public String getName(); + method @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public int getSecurityModel(); method public String[] getSupportedTypes(); method public boolean isAlias(); method public boolean isEncoder(); method public boolean isHardwareAccelerated(); method public boolean isSoftwareOnly(); method public boolean isVendor(); + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_MEMORY_SAFE = 1; // 0x1 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_SANDBOXED = 0; // 0x0 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 2; // 0x2 } public static final class MediaCodecInfo.AudioCapabilities { @@ -23542,6 +23608,9 @@ package android.media { field public static final int COLOR_TRANSFER_LINEAR = 1; // 0x1 field public static final int COLOR_TRANSFER_SDR_VIDEO = 3; // 0x3 field public static final int COLOR_TRANSFER_ST2084 = 6; // 0x6 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_MEMORY_SAFE = 2; // 0x2 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_SANDBOXED = 1; // 0x1 + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 4; // 0x4 field public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode"; field public static final String KEY_AAC_DRC_ATTENUATION_FACTOR = "aac-drc-cut-level"; field public static final String KEY_AAC_DRC_BOOST_FACTOR = "aac-drc-boost-level"; @@ -23623,6 +23692,7 @@ package android.media { field public static final String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after"; field public static final String KEY_ROTATION = "rotation-degrees"; field public static final String KEY_SAMPLE_RATE = "sample-rate"; + field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final String KEY_SECURITY_MODEL = "security-model"; field public static final String KEY_SLICE_HEIGHT = "slice-height"; field public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers"; field public static final String KEY_STRIDE = "stride"; @@ -34035,6 +34105,9 @@ package android.os { field public static final int USER_OPERATION_ERROR_MAX_USERS = 6; // 0x6 field public static final int USER_OPERATION_ERROR_UNKNOWN = 1; // 0x1 field public static final int USER_OPERATION_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; + field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; + field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE"; } public static class UserManager.UserOperationException extends java.lang.RuntimeException { @@ -42346,6 +42419,7 @@ package android.telecom { field public static final int SUPPORTS_SET_INACTIVE = 2; // 0x2 field public static final int SUPPORTS_STREAM = 4; // 0x4 field public static final int SUPPORTS_TRANSFER = 8; // 0x8 + field @FlaggedApi("com.android.server.telecom.flags.transactional_video_state") public static final int SUPPORTS_VIDEO_CALLING = 16; // 0x10 field public static final int VIDEO_CALL = 2; // 0x2 } @@ -42381,6 +42455,7 @@ package android.telecom { method @NonNull public android.os.ParcelUuid getCallId(); method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void requestMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); + method @FlaggedApi("com.android.server.telecom.flags.transactional_video_state") public void requestVideoState(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method public void sendEvent(@NonNull String, @NonNull android.os.Bundle); method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>); @@ -42429,6 +42504,7 @@ package android.telecom { method public void onCallStreamingFailed(int); method public void onEvent(@NonNull String, @NonNull android.os.Bundle); method public void onMuteStateChanged(boolean); + method @FlaggedApi("com.android.server.telecom.flags.transactional_video_state") public default void onVideoStateChanged(int); } public final class CallException extends java.lang.RuntimeException implements android.os.Parcelable { @@ -45732,6 +45808,7 @@ package android.telephony { method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getSubscriptionsInGroup(@NonNull android.os.ParcelUuid); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isActiveSubscriptionId(int); method public boolean isNetworkRoaming(int); + method @FlaggedApi("com.android.internal.telephony.flags.subscription_user_association_query") @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isSubscriptionAssociatedWithUser(int); method public static boolean isUsableSubscriptionId(int); method public static boolean isValidSubscriptionId(int); method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); @@ -47481,6 +47558,7 @@ package android.text { method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float); method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.DynamicLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics); + method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setShiftDrawingOffsetForStartOverhang(boolean); method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic); method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean); method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean); @@ -47684,6 +47762,7 @@ package android.text { method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @Nullable public final int[] getRightIndents(); method public float getSecondaryHorizontal(int); method public void getSelectionPath(int, int, android.graphics.Path); + method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public boolean getShiftDrawingOffsetForStartOverhang(); method public final float getSpacingAdd(); method public final float getSpacingMultiplier(); method @NonNull public final CharSequence getText(); @@ -47740,6 +47819,7 @@ package android.text { method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int); method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.Layout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics); method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]); + method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.Layout.Builder setShiftDrawingOffsetForStartOverhang(boolean); method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic); method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean); } @@ -48011,6 +48091,7 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float); method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int); method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.StaticLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics); + method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setShiftDrawingOffsetForStartOverhang(boolean); method public android.text.StaticLayout.Builder setText(CharSequence); method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic); method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean); @@ -50520,7 +50601,6 @@ package android.view { method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener); method public default void setChildBoundingInsets(@NonNull android.graphics.Rect); method public default void setTouchableRegion(@Nullable android.graphics.Region); - method @FlaggedApi("com.android.window.flags.transfer_gesture_to_embedded") public default boolean transferHostTouchGestureToEmbedded(@NonNull android.view.SurfaceControlViewHost.SurfacePackage); } @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener { @@ -52238,6 +52318,7 @@ package android.view { public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable { ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage); method public int describeContents(); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @Nullable public android.window.InputTransferToken getInputTransferToken(); method @NonNull public android.view.SurfaceControl getSurfaceControl(); method public void notifyConfigurationChanged(@NonNull android.content.res.Configuration); method public void notifyDetachedFromWindow(); @@ -53198,7 +53279,7 @@ package android.view { field protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET; field protected static final int[] PRESSED_STATE_SET; field protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET; - field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0.0f; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = (0.0f/0.0f); field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4.0f; field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2.0f; field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3.0f; @@ -54373,15 +54454,17 @@ package android.view { method @Deprecated public android.view.Display getDefaultDisplay(); method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics(); method public default boolean isCrossWindowBlurEnabled(); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerBatchedSurfaceControlInputReceiver(int, @NonNull android.window.InputTransferToken, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.window.InputTransferToken registerBatchedSurfaceControlInputReceiver(int, @NonNull android.window.InputTransferToken, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.window.InputTransferToken, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.window.InputTransferToken registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.window.InputTransferToken, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver); method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer); method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>); method public void removeViewImmediate(android.view.View); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default boolean transferTouchGesture(@NonNull android.window.InputTransferToken, @NonNull android.window.InputTransferToken); method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); + field @FlaggedApi("com.android.window.flags.cover_display_opt_in") public static final int COMPAT_SMALL_COVER_SCREEN_OPT_IN = 1; // 0x1 field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"; field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"; field @FlaggedApi("com.android.window.flags.untrusted_embedding_state_sharing") public static final String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING = "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING"; @@ -54394,6 +54477,7 @@ package android.view { field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"; field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES"; field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"; + field @FlaggedApi("com.android.window.flags.cover_display_opt_in") public static final String PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN = "android.window.PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN"; field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"; field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE"; field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS"; @@ -55206,6 +55290,7 @@ package android.view.accessibility { field public static final int TYPE_MAGNIFICATION_OVERLAY = 6; // 0x6 field public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; // 0x5 field public static final int TYPE_SYSTEM = 3; // 0x3 + field @FlaggedApi("android.view.accessibility.add_type_window_control") public static final int TYPE_WINDOW_CONTROL = 7; // 0x7 } public class CaptioningManager { @@ -60826,6 +60911,7 @@ package android.widget { method public float getShadowDx(); method public float getShadowDy(); method public float getShadowRadius(); + method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public boolean getShiftDrawingOffsetForStartOverhang(); method public final boolean getShowSoftInputOnFocus(); method public CharSequence getText(); method @NonNull public android.view.textclassifier.TextClassifier getTextClassifier(); @@ -60962,6 +61048,7 @@ package android.widget { method public void setSearchResultHighlights(@Nullable int...); method public void setSelectAllOnFocus(boolean); method public void setShadowLayer(float, float, float, int); + method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public void setShiftDrawingOffsetForStartOverhang(boolean); method public final void setShowSoftInputOnFocus(boolean); method public void setSingleLine(); method public void setSingleLine(boolean); diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt index b36b963f99c9..1b0da055038d 100644 --- a/core/api/lint-baseline.txt +++ b/core/api/lint-baseline.txt @@ -245,6 +245,14 @@ BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SE Field 'ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE' is missing @BroadcastBehavior +CompileTimeConstant: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_GIMBAL: + All constants must be defined at compile time: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_GIMBAL +CompileTimeConstant: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED: + All constants must be defined at compile time: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED +CompileTimeConstant: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_OFF: + All constants must be defined at compile time: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_OFF + + DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle): Method android.accounts.AccountManager.newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match DeprecationMismatch: android.app.Activity#enterPictureInPictureMode(): @@ -1087,6 +1095,14 @@ RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean): Method 'setGeolocationEnabled' documentation mentions permissions without declaring @RequiresPermission +StaticUtils: ExtensionCaptureRequest: + Fully-static utility classes must not have constructor +StaticUtils: android.hardware.camera2.ExtensionCaptureRequest: + Fully-static utility classes must not have constructor +StaticUtils: android.hardware.camera2.ExtensionCaptureResult: + Fully-static utility classes must not have constructor + + Todo: android.hardware.camera2.params.StreamConfigurationMap: Documentation mentions 'TODO' Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor): @@ -1445,6 +1461,15 @@ UnflaggedApi: android.graphics.text.PositionedGlyphs#getItalicOverride(int): New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getItalicOverride(int) UnflaggedApi: android.graphics.text.PositionedGlyphs#getWeightOverride(int): New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getWeightOverride(int) + +UnflaggedApi: android.hardware.camera2.ExtensionCaptureRequest: + New API must be flagged with @FlaggedApi: class android.hardware.camera2.ExtensionCaptureRequest +UnflaggedApi: android.hardware.camera2.ExtensionCaptureRequest#ExtensionCaptureRequest(): + New API must be flagged with @FlaggedApi: constructor android.hardware.camera2.ExtensionCaptureRequest() +UnflaggedApi: android.hardware.camera2.ExtensionCaptureResult: + New API must be flagged with @FlaggedApi: class android.hardware.camera2.ExtensionCaptureResult +UnflaggedApi: android.hardware.camera2.ExtensionCaptureResult#ExtensionCaptureResult(): + New API must be flagged with @FlaggedApi: constructor android.hardware.camera2.ExtensionCaptureResult() UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_CAR: New API must be flagged with @FlaggedApi: field android.media.MediaRoute2Info.TYPE_REMOTE_CAR UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_COMPUTER: diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 159410dcde09..15de42afbc66 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -100,6 +100,7 @@ package android { field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED"; field @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission") public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER"; field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER"; + field @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static final String CAMERA_PRIVACY_ALLOWLIST = "android.permission.CAMERA_PRIVACY_ALLOWLIST"; field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD"; field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD"; field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT"; @@ -194,6 +195,8 @@ package android { field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS"; 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"; @@ -592,6 +595,7 @@ package android.app { field public static final int FOREGROUND_SERVICE_API_TYPE_MICROPHONE = 6; // 0x6 field public static final int FOREGROUND_SERVICE_API_TYPE_PHONE_CALL = 7; // 0x7 field public static final int FOREGROUND_SERVICE_API_TYPE_USB = 8; // 0x8 + field @FlaggedApi("android.media.audio.foreground_audio_control") public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 64; // 0x40 field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4 @@ -622,6 +626,8 @@ package android.app { method public static String[] getOpStrs(); method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...); method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[]); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[], @NonNull String); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermissionGroupUsage> getPermissionGroupUsageForPrivacyIndicator(boolean); method public static int opToDefaultMode(@NonNull String); method @Nullable public static String opToPermission(@NonNull String); method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int); @@ -1286,6 +1292,10 @@ package android.app.admin { field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyDrawableResource> CREATOR; } + public final class DevicePolicyIdentifiers { + field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String AUDIT_LOGGING_POLICY = "auditLogging"; + } + public class DevicePolicyKeyguardService extends android.app.Service { ctor public DevicePolicyKeyguardService(); method @Nullable public void dismiss(); @@ -1314,12 +1324,14 @@ package android.app.admin { method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; 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 @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); @@ -1328,6 +1340,8 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean); + method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); @@ -3681,6 +3695,7 @@ package android.content { field public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS"; field @Deprecated public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"; field public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA"; + field @FlaggedApi("android.security.frp_enforcement") public static final String ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED = "android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED"; field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_MANAGE_APP_PERMISSION = "android.intent.action.MANAGE_APP_PERMISSION"; field @Deprecated public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS"; field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP"; @@ -4674,11 +4689,15 @@ package android.hardware { method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist(); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int, int); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int); } public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { @@ -4688,6 +4707,7 @@ package android.hardware { public static class SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams { method public int getSensor(); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public int getState(); method public int getToggleType(); method public boolean isEnabled(); } @@ -4748,9 +4768,13 @@ package android.hardware.camera2.extension { @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class CameraOutputSurface { ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public int getColorSpace(); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public long getDynamicRangeProfile(); method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat(); method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize(); method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface(); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long); } @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class CharacteristicsMap { @@ -10231,6 +10255,7 @@ package android.nfc.cardemulation { ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilterToAutoTransact(@NonNull String); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean defaultToObserveMode(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream); @@ -10260,6 +10285,7 @@ package android.nfc.cardemulation { method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setCategoryOtherServiceEnabled(boolean); + method @FlaggedApi("android.nfc.nfc_observe_mode") public void setDefaultToObserveMode(boolean); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int); @@ -11088,9 +11114,6 @@ package android.os { field public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST"; field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; - field public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; - field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; - field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE"; field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS"; } @@ -11328,6 +11351,7 @@ package android.permission { method public long getLastAccessTimeMillis(); method @NonNull public String getPackageName(); method @NonNull public String getPermissionGroupName(); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull public String getPersistentDeviceId(); method @Nullable public CharSequence getProxyLabel(); method public int getUid(); method public boolean isActive(); @@ -14346,9 +14370,9 @@ package android.telephony { @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public abstract class DomainSelectionService extends android.app.Service { ctor public DomainSelectionService(); + method @NonNull public java.util.concurrent.Executor getCreateExecutor(); method public void onBarringInfoUpdated(int, int, @NonNull android.telephony.BarringInfo); method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @NonNull public java.util.concurrent.Executor onCreateExecutor(); method public abstract void onDomainSelection(@NonNull android.telephony.DomainSelectionService.SelectionAttributes, @NonNull android.telephony.TransportSelectorCallback); method public void onServiceStateUpdated(int, int, @NonNull android.telephony.ServiceState); field public static final int SCAN_TYPE_FULL_SERVICE = 2; // 0x2 @@ -15128,6 +15152,7 @@ package android.telephony { method @Deprecated public boolean getDataEnabled(int); method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getDefaultRespondViaMessageApplication(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion(int); + method @FlaggedApi("android.permission.flags.get_emergency_role_holder_api_enabled") @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getEmergencyAssistancePackage(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain(); diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index a6505c8b6093..ca9fab815167 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -535,6 +535,10 @@ ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmis Listeners should always be at end of argument list (method `stopSatelliteTransmissionUpdates`) +MethodNameUnits: android.hardware.camera2.extension.CameraOutputSurface#getColorSpace(): + Expected method name units to be `Bytes`, was `Space` in `getColorSpace` + + MissingGetterMatchingBuilder: android.service.voice.HotwordTrainingData.Builder#addTrainingAudio(android.service.voice.HotwordTrainingAudio): android.service.voice.HotwordTrainingData does not declare a `getTrainingAudios()` method matching method android.service.voice.HotwordTrainingData.Builder.addTrainingAudio(android.service.voice.HotwordTrainingAudio) MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean): diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3838b941527b..f6366a2afbf0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1519,6 +1519,7 @@ package android.hardware { public final class SensorPrivacyManager { method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int); } public static class SensorPrivacyManager.Sources { diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig index 39e46bbdfa6a..de4e607b50f1 100644 --- a/core/java/android/adaptiveauth/flags.aconfig +++ b/core/java/android/adaptiveauth/flags.aconfig @@ -1,6 +1,13 @@ package: "android.adaptiveauth" flag { + name: "enable_adaptive_auth" + namespace: "biometrics" + description: "Feature flag for enabling the new adaptive auth service" + bug: "285053096" +} + +flag { name: "report_biometric_auth_attempts" namespace: "biometrics" description: "Control the usage of the biometric auth signal in adaptive auth" diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 23fe731701b6..fae9f839317b 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -844,7 +844,28 @@ public class Activity extends ContextThemeWrapper private IBinder mToken; private IBinder mAssistToken; private IBinder mShareableActivityToken; + + /** Initial caller of the activity. Can be retrieved from {@link #getInitialCaller} */ private ComponentCaller mInitialCaller; + /** + * Caller associated with the Intent from {@link #getIntent}. Can be retrieved from + * {@link #getCaller}. + * + * <p>The value of this field depends on how the activity set its intent: + * - If via {@link #setIntent(Intent)}, the caller will be {@code null}. + * - If via {@link #setIntent(Intent, ComponentCaller)}, the caller will be set to the passed + * caller. + */ + private ComponentCaller mCaller; + /** + * Caller associated with an Intent within {@link #onNewIntent}. Can be retrieved from + * {@link #getCurrentCaller}, or by overriding {@link #onNewIntent(Intent, ComponentCaller)} and + * getting the second argument. + * + * <p>The value of this field will be {@code null} outside of {@link #onNewIntent}. + */ + private ComponentCaller mCurrentCaller; + @UnsupportedAppUsage private int mIdent; @UnsupportedAppUsage @@ -1117,23 +1138,71 @@ public class Activity extends ContextThemeWrapper private static native String getDlWarning(); - /** Return the intent that started this activity. */ + /** + * Returns the intent that started this activity. + * + * <p>To keep the Intent instance for future use, call {@link #setIntent(Intent)}, and use + * this method to retrieve it. + */ public Intent getIntent() { return mIntent; } /** - * Change the intent returned by {@link #getIntent}. This holds a - * reference to the given intent; it does not copy it. Often used in - * conjunction with {@link #onNewIntent}. + * Changes the intent returned by {@link #getIntent}. This holds a + * reference to the given intent; it does not copy it. Often used in + * conjunction with {@link #onNewIntent(Intent)}. * - * @param newIntent The new Intent object to return from getIntent + * @param newIntent The new Intent object to return from {@link #getIntent} * * @see #getIntent - * @see #onNewIntent + * @see #onNewIntent(Intent) */ public void setIntent(Intent newIntent) { + internalSetIntent(newIntent, /* newCaller */ null); + } + + /** + * Returns the ComponentCaller instance of the app that launched this activity with the intent + * from {@link #getIntent()}. To keep the value of the ComponentCaller instance for new intents, + * call {@link #setIntent(Intent, ComponentCaller)} instead of {@link #setIntent(Intent)}. + * + * @return {@link ComponentCaller} instance corresponding to the intent from + * {@link #getIntent()}, or {@code null} if the activity was not launched with that + * intent + * + * @see ComponentCaller + * @see #getIntent + * @see #setIntent(Intent, ComponentCaller) + */ + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + public @Nullable ComponentCaller getCaller() { + return mCaller; + } + + /** + * Changes the intent returned by {@link #getIntent}, and ComponentCaller returned by + * {@link #getCaller}. This holds references to the given intent, and ComponentCaller; it does + * not copy them. Often used in conjunction with {@link #onNewIntent(Intent)}. To retrieve the + * caller from {@link #onNewIntent(Intent)}, use {@link #getCurrentCaller}, otherwise override + * {@link #onNewIntent(Intent, ComponentCaller)}. + * + * @param newIntent The new Intent object to return from {@link #getIntent} + * @param newCaller The new {@link ComponentCaller} object to return from + * {@link #getCaller} + * + * @see #getIntent + * @see #onNewIntent(Intent, ComponentCaller) + * @see #getCaller + */ + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + public void setIntent(@Nullable Intent newIntent, @Nullable ComponentCaller newCaller) { + internalSetIntent(newIntent, newCaller); + } + + private void internalSetIntent(Intent newIntent, ComponentCaller newCaller) { mIntent = newIntent; + mCaller = newCaller; } /** @@ -2284,18 +2353,42 @@ public class Activity extends ContextThemeWrapper * sometime later when activity becomes active again. * * <p>Note that {@link #getIntent} still returns the original Intent. You - * can use {@link #setIntent} to update it to this new Intent. + * can use {@link #setIntent(Intent)} to update it to this new Intent. * - * @param intent The new intent that was started for the activity. + * @param intent The new intent that was used to start the activity * * @see #getIntent - * @see #setIntent + * @see #setIntent(Intent) * @see #onResume */ protected void onNewIntent(Intent intent) { } /** + * Same as {@link #onNewIntent(Intent)}, but with an extra parameter for the ComponentCaller + * instance associated with the app that sent the intent. + * + * <p>If you want to retrieve the caller without overriding this method, call + * {@link #getCurrentCaller} inside your existing {@link #onNewIntent(Intent)}. + * + * <p>Note that you should only override one {@link #onNewIntent} method. + * + * @param intent The new intent that was used to start the activity + * @param caller The {@link ComponentCaller} instance associated with the app that sent the + * intent + * + * @see ComponentCaller + * @see #onNewIntent(Intent) + * @see #getCurrentCaller + * @see #setIntent(Intent, ComponentCaller) + * @see #getCaller + */ + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + public void onNewIntent(@NonNull Intent intent, @NonNull ComponentCaller caller) { + onNewIntent(intent); + } + + /** * The hook for {@link ActivityThread} to save the state of this activity. * * Calls {@link #onSaveInstanceState(android.os.Bundle)} @@ -7044,6 +7137,35 @@ public class Activity extends ContextThemeWrapper } /** + * Returns the ComponentCaller instance of the app that re-launched this activity with a new + * intent via {@link #onNewIntent}. + * + * <p>Note that this method only works within the {@link #onNewIntent} method. If you call this + * method outside {@link #onNewIntent}, it will throw an {@link IllegalStateException}. + * + * <p>You can also retrieve the caller if you override + * {@link #onNewIntent(Intent, ComponentCaller)}. + * + * <p>To keep the ComponentCaller instance for future use, call + * {@link #setIntent(Intent, ComponentCaller)}, and use {@link #getCaller} to retrieve it. + * + * @return {@link ComponentCaller} instance + * @throws IllegalStateException if the caller is {@code null}, indicating the method was called + * outside {@link #onNewIntent} + * @see ComponentCaller + * @see #setIntent(Intent, ComponentCaller) + * @see #getCaller + */ + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + public @NonNull ComponentCaller getCurrentCaller() { + if (mCurrentCaller == null) { + throw new IllegalStateException("The caller is null because #getCurrentCaller should be" + + " called within #onNewIntent method"); + } + return mCurrentCaller; + } + + /** * Control whether this activity's main window is visible. This is intended * only for the special case of an activity that is not going to show a * UI itself, but can't just finish prior to onResume() because it needs @@ -8740,6 +8862,7 @@ public class Activity extends ContextThemeWrapper if (android.security.Flags.contentUriPermissionApis()) { mInitialCaller = new ComponentCaller(getActivityToken(), initialCallerInfoAccessToken); + mCaller = mInitialCaller; } } @@ -8815,6 +8938,16 @@ public class Activity extends ContextThemeWrapper Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + final void performNewIntent(@NonNull Intent intent, @NonNull ComponentCaller caller) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performNewIntent"); + mCanEnterPictureInPicture = true; + mCurrentCaller = caller; + onNewIntent(intent, caller); + mCurrentCaller = null; + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); + } + final void performStart(String reason) { if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStart:" diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index a59f04bf4f3a..10dc3c6c1360 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -299,6 +299,26 @@ public class ActivityClient { } } + /** Returns the uid of the app that launched the activity. */ + public int getActivityCallerUid(IBinder activityToken, IBinder callerToken) { + try { + return getActivityClientController().getActivityCallerUid(activityToken, + callerToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Returns the package of the app that launched the activity. */ + public String getActivityCallerPackage(IBinder activityToken, IBinder callerToken) { + try { + return getActivityClientController().getActivityCallerPackage(activityToken, + callerToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** Checks if the app that launched the activity has access to the URI. */ public int checkActivityCallerContentUriPermission(IBinder activityToken, IBinder callerToken, Uri uri, int modeFlags) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index a8d183a1d6dd..237d31c67fe2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.media.audio.Flags.FLAG_FOREGROUND_AUDIO_CONTROL; import android.Manifest; import android.annotation.ColorInt; @@ -794,6 +795,7 @@ public class ActivityManager { PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK, PROCESS_CAPABILITY_BFSL, PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK, + PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL, }) @Retention(RetentionPolicy.SOURCE) public @interface ProcessCapability {} @@ -943,6 +945,14 @@ public class ActivityManager { public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 1 << 5; /** + * @hide + * Process can access volume APIs and can request audio focus with GAIN. + */ + @FlaggedApi(FLAG_FOREGROUND_AUDIO_CONTROL) + @SystemApi + public static final int PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL = 1 << 6; + + /** * @hide all capabilities, the ORing of all flags in {@link ProcessCapability}. * * Don't expose it as TestApi -- we may add new capabilities any time, which could @@ -953,7 +963,8 @@ public class ActivityManager { | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE | PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK | PROCESS_CAPABILITY_BFSL - | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; + | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK + | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; /** * All implicit capabilities. There are capabilities that process automatically have. @@ -975,6 +986,7 @@ public class ActivityManager { pw.print((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-'); pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-'); pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-'); + pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-'); } /** @hide */ @@ -986,6 +998,7 @@ public class ActivityManager { sb.append((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-'); sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-'); sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-'); + sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0 ? 'A' : '-'); } /** diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index e14bf68bde53..2a2c5f05f122 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIO import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.view.Display.INVALID_DISPLAY; @@ -1849,7 +1850,7 @@ public class ActivityOptions extends ComponentOptions { public int getPendingIntentLaunchFlags() { // b/243794108: Ignore all flags except the new task flag, to be reconsidered in b/254490217 return mPendingIntentLaunchFlags & - (FLAG_ACTIVITY_NEW_TASK | FLAG_RECEIVER_FOREGROUND); + (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_RECEIVER_FOREGROUND); } /** diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b25d5ebf61a0..00ccd043a5a7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4203,7 +4203,12 @@ public final class ActivityThread extends ClientTransactionHandler intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo), r.activity.getAttributionSource()); r.activity.mFragments.noteStateNotSaved(); - mInstrumentation.callActivityOnNewIntent(r.activity, intent); + if (android.security.Flags.contentUriPermissionApis()) { + ComponentCaller caller = new ComponentCaller(r.token, intent.mCallerToken); + mInstrumentation.callActivityOnNewIntent(r.activity, intent, caller); + } else { + mInstrumentation.callActivityOnNewIntent(r.activity, intent); + } } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2100425a6771..16c7753c7f46 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -16,7 +16,9 @@ package android.app; + import static android.location.flags.Flags.FLAG_LOCATION_BYPASS; +import static android.media.audio.Flags.foregroundAudioControl; import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER; import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED; import static android.view.contentprotection.flags.Flags.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED; @@ -70,6 +72,8 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.permission.PermissionGroupUsage; +import android.permission.PermissionUsageHelper; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; @@ -224,6 +228,7 @@ public class AppOpsManager { private static Boolean sFullLog = null; final Context mContext; + private PermissionUsageHelper mUsageHelper; @UnsupportedAppUsage final IAppOpsService mService; @@ -3229,6 +3234,10 @@ public class AppOpsManager { * @hide */ public static @Mode int opToDefaultMode(int op) { + if (op == OP_TAKE_AUDIO_FOCUS && foregroundAudioControl()) { + // when removing the flag, change the entry in sAppOpInfos for OP_TAKE_AUDIO_FOCUS + return AppOpsManager.MODE_FOREGROUND; + } return sAppOpInfos[op].defaultMode; } @@ -7759,6 +7768,44 @@ public class AppOpsManager { } /** + * Retrieve current operation state for all applications for a device. + * + * The mode of the ops returned are set for the package but may not reflect their effective + * state due to UID policy or because it's controlled by a different global op. + * + * Use {@link #unsafeCheckOp(String, int, String)}} or + * {@link #noteOp(String, int, String, String, String)} if the effective mode is needed. + * + * @param ops The set of operations you are interested in, or null if you want all of them. + * @param persistentDeviceId The device that the ops are attributed to. + * + * @hide + */ + @SystemApi + @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) + @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) + public @NonNull List<AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[] ops, + @NonNull String persistentDeviceId) { + final int[] opCodes; + if (ops != null) { + final int opCount = ops.length; + opCodes = new int[opCount]; + for (int i = 0; i < opCount; i++) { + opCodes[i] = sOpStrToOp.get(ops[i]); + } + } else { + opCodes = null; + } + final List<AppOpsManager.PackageOps> result; + try { + result = mService.getPackagesForOpsForDevice(opCodes, persistentDeviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return (result != null) ? result : Collections.emptyList(); + } + + /** * Retrieve current operation state for all applications. * * The mode of the ops returned are set for the package but may not reflect their effective @@ -7774,7 +7821,8 @@ public class AppOpsManager { @UnsupportedAppUsage public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) { try { - return mService.getPackagesForOps(ops); + return mService.getPackagesForOpsForDevice(ops, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9940,6 +9988,30 @@ public class AppOpsManager { appOpsNotedForAttribution.set(op); } + /** + * Get recent op usage data for CAMERA, MICROPHONE and LOCATION from all connected devices + * to power privacy indicator. + * + * @param includeMicrophoneUsage whether to retrieve microphone usage + * @return A list of permission groups currently or recently used by all apps by all users in + * the current profile group. + * + * @hide + */ + @SystemApi + @NonNull + @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) + @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS) + public List<PermissionGroupUsage> getPermissionGroupUsageForPrivacyIndicator( + boolean includeMicrophoneUsage) { + // Lazily initialize the usage helper + if (mUsageHelper == null) { + mUsageHelper = new PermissionUsageHelper(mContext); + } + + return mUsageHelper.getOpUsageDataForAllDevices(includeMicrophoneUsage); + } + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index c6712c044539..3715c6e633dc 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.icu.text.SimpleDateFormat; import android.os.Parcel; import android.os.Parcelable; @@ -249,7 +250,7 @@ public final class ApplicationStartInfo implements Parcelable { private @StartType int mStartType; /** - * @see #getStartIntent + * @see #getIntent */ private Intent mStartIntent; @@ -259,6 +260,11 @@ public final class ApplicationStartInfo implements Parcelable { private @LaunchMode int mLaunchMode; /** + * @see #wasForceStopped() + */ + private boolean mWasForceStopped; + + /** * @hide * */ @IntDef( @@ -427,6 +433,15 @@ public final class ApplicationStartInfo implements Parcelable { } /** + * @see #wasForceStopped() + * @param wasForceStopped whether the app had been force-stopped in the past + * @hide + */ + public void setForceStopped(boolean wasForceStopped) { + mWasForceStopped = wasForceStopped; + } + + /** * Current state of startup. * * Can be used to determine whether the object will have additional fields added as it may be @@ -578,6 +593,20 @@ public final class ApplicationStartInfo implements Parcelable { return mLaunchMode; } + /** + * Informs whether this is the first process launch for an app since it was + * {@link ApplicationInfo#FLAG_STOPPED force-stopped} for some reason. + * This allows the app to know if it should re-register for any alarms, jobs and other callbacks + * that were cleared when the app was force-stopped. + * + * @return {@code true} if this is the first process launch of the app after having been + * stopped, {@code false} otherwise. + */ + @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED) + public boolean wasForceStopped() { + return mWasForceStopped; + } + @Override public int describeContents() { return 0; @@ -603,6 +632,7 @@ public final class ApplicationStartInfo implements Parcelable { dest.writeInt(mStartType); dest.writeParcelable(mStartIntent, flags); dest.writeInt(mLaunchMode); + dest.writeBoolean(mWasForceStopped); } /** @hide */ @@ -622,6 +652,7 @@ public final class ApplicationStartInfo implements Parcelable { mStartType = other.mStartType; mStartIntent = other.mStartIntent; mLaunchMode = other.mLaunchMode; + mWasForceStopped = other.mWasForceStopped; } private ApplicationStartInfo(@NonNull Parcel in) { @@ -643,6 +674,7 @@ public final class ApplicationStartInfo implements Parcelable { mStartIntent = in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class); mLaunchMode = in.readInt(); + mWasForceStopped = in.readBoolean(); } private static String intern(@Nullable String source) { @@ -720,6 +752,7 @@ public final class ApplicationStartInfo implements Parcelable { intentOut.close(); } proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode); + proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped); proto.end(token); } @@ -799,6 +832,10 @@ public final class ApplicationStartInfo implements Parcelable { case (int) ApplicationStartInfoProto.LAUNCH_MODE: mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE); break; + case (int) ApplicationStartInfoProto.WAS_FORCE_STOPPED: + mWasForceStopped = proto.readBoolean( + ApplicationStartInfoProto.WAS_FORCE_STOPPED); + break; } } proto.end(token); @@ -823,6 +860,7 @@ public final class ApplicationStartInfo implements Parcelable { .append(" reason=").append(reasonToString(mReason)) .append(" startType=").append(startTypeToString(mStartType)) .append(" launchMode=").append(mLaunchMode) + .append(" wasForceStopped=").append(mWasForceStopped) .append('\n'); if (mStartIntent != null) { sb.append(" intent=").append(mStartIntent.toString()) @@ -878,7 +916,7 @@ public final class ApplicationStartInfo implements Parcelable { && mDefiningUid == o.mDefiningUid && mReason == o.mReason && mStartupState == o.mStartupState && mStartType == o.mStartType && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName) - && timestampsEquals(o); + && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped; } @Override diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index f727ee5a95fe..1b5b0fc5d917 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -16,6 +16,8 @@ package android.app; +import android.annotation.CurrentTimeMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -65,6 +67,8 @@ public class BroadcastOptions extends ComponentOptions { private @Nullable BundleMerger mDeliveryGroupExtrasMerger; private @Nullable IntentFilter mDeliveryGroupMatchingFilter; private @DeferralPolicy int mDeferralPolicy; + private @CurrentTimeMillisLong long mEventTriggerTimestampMillis; + private @CurrentTimeMillisLong long mRemoteEventTriggerTimestampMillis; /** @hide */ @IntDef(flag = true, prefix = { "FLAG_" }, value = { @@ -190,6 +194,18 @@ public class BroadcastOptions extends ComponentOptions { "android:broadcast.idForResponseEvent"; /** + * Corresponds to {@link #setEventTriggerTimestampMillis(long)}. + */ + private static final String KEY_EVENT_TRIGGER_TIMESTAMP = + "android:broadcast.eventTriggerTimestamp"; + + /** + * Corresponds to {@link #setRemoteEventTriggerTimestampMillis(long)}. + */ + private static final String KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP = + "android:broadcast.remoteEventTriggerTimestamp"; + + /** * Corresponds to {@link #setDeliveryGroupPolicy(int)}. */ private static final String KEY_DELIVERY_GROUP_POLICY = @@ -341,6 +357,8 @@ public class BroadcastOptions extends ComponentOptions { mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS); mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID); mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT); + mEventTriggerTimestampMillis = opts.getLong(KEY_EVENT_TRIGGER_TIMESTAMP); + mRemoteEventTriggerTimestampMillis = opts.getLong(KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP); mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY, DELIVERY_GROUP_POLICY_ALL); mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE); @@ -787,6 +805,60 @@ public class BroadcastOptions extends ComponentOptions { } /** + * Set the timestamp for the event that triggered this broadcast, in + * {@link System#currentTimeMillis()} timebase. + * + * <p> For instance, if this broadcast is for a push message, then this timestamp + * could correspond to when the device received the message. + * + * @param timestampMillis the timestamp in {@link System#currentTimeMillis()} timebase that + * correspond to the event that triggered this broadcast. + */ + @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS) + public void setEventTriggerTimestampMillis(@CurrentTimeMillisLong long timestampMillis) { + mEventTriggerTimestampMillis = timestampMillis; + } + + /** + * Return the timestamp for the event that triggered this broadcast, in + * {@link System#currentTimeMillis()} timebase. + * + * @return the timestamp in {@link System#currentTimeMillis()} timebase that was previously + * set using {@link #setEventTriggerTimestampMillis(long)}. + */ + @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS) + public @CurrentTimeMillisLong long getEventTriggerTimestampMillis() { + return mEventTriggerTimestampMillis; + } + + /** + * Set the timestamp for the remote event, if any, that triggered this broadcast, in + * {@link System#currentTimeMillis()} timebase. + * + * <p> For instance, if this broadcast is for a push message, then this timestamp + * could correspond to when the message originated remotely. + * + * @param timestampMillis the timestamp in {@link System#currentTimeMillis()} timebase that + * correspond to the remote event that triggered this broadcast. + */ + @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS) + public void setRemoteEventTriggerTimestampMillis(@CurrentTimeMillisLong long timestampMillis) { + mRemoteEventTriggerTimestampMillis = timestampMillis; + } + + /** + * Return the timestamp for the remote event that triggered this broadcast, in + * {@link System#currentTimeMillis()} timebase. + * + * @return the timestamp in {@link System#currentTimeMillis()} timebase that was previously + * set using {@link #setRemoteEventTriggerTimestampMillis(long)}}. + */ + @FlaggedApi(android.app.Flags.FLAG_BCAST_EVENT_TIMESTAMPS) + public @CurrentTimeMillisLong long getRemoteEventTriggerTimestampMillis() { + return mRemoteEventTriggerTimestampMillis; + } + + /** * Sets deferral policy for this broadcast that specifies how this broadcast * can be deferred for delivery at some future point. */ @@ -1120,6 +1192,12 @@ public class BroadcastOptions extends ComponentOptions { if (mIdForResponseEvent != 0) { b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent); } + if (mEventTriggerTimestampMillis > 0) { + b.putLong(KEY_EVENT_TRIGGER_TIMESTAMP, mEventTriggerTimestampMillis); + } + if (mRemoteEventTriggerTimestampMillis > 0) { + b.putLong(KEY_REMOTE_EVENT_TRIGGER_TIMESTAMP, mRemoteEventTriggerTimestampMillis); + } if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) { b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy); } diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java index 44e8a0a3a20c..14bc0038883a 100644 --- a/core/java/android/app/ComponentCaller.java +++ b/core/java/android/app/ComponentCaller.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.pm.PackageManager; @@ -24,8 +25,6 @@ import android.net.Uri; import android.os.IBinder; import android.os.Process; -import androidx.annotation.NonNull; - import java.util.Objects; /** @@ -45,7 +44,7 @@ public final class ComponentCaller { /** * @hide */ - public ComponentCaller(@NonNull IBinder activityToken, @Nullable IBinder callerToken) { + public ComponentCaller(@Nullable IBinder activityToken, @Nullable IBinder callerToken) { mActivityToken = activityToken; mCallerToken = callerToken; } @@ -83,7 +82,7 @@ public final class ComponentCaller { * @see Activity#getLaunchedFromUid() */ public int getUid() { - return ActivityClient.getInstance().getLaunchedFromUid(mActivityToken); + return ActivityClient.getInstance().getActivityCallerUid(mActivityToken, mCallerToken); } /** @@ -121,7 +120,7 @@ public final class ComponentCaller { */ @Nullable public String getPackage() { - return ActivityClient.getInstance().getLaunchedFromPackage(mActivityToken); + return ActivityClient.getInstance().getActivityCallerPackage(mActivityToken, mCallerToken); } /** diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 05fee72b7e61..9c8fea15c712 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -90,7 +90,9 @@ interface IActivityClientController { ComponentName getCallingActivity(in IBinder token); String getCallingPackage(in IBinder token); int getLaunchedFromUid(in IBinder token); + int getActivityCallerUid(in IBinder activityToken, in IBinder callerToken); String getLaunchedFromPackage(in IBinder token); + String getActivityCallerPackage(in IBinder activityToken, in IBinder callerToken); int checkActivityCallerContentUriPermission(in IBinder activityToken, in IBinder callerToken, in Uri uri, int modeFlags, int userId); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 454d60534476..be7199b9a0fc 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1624,26 +1625,75 @@ public class Instrumentation { * @param intent The new intent being received. */ public void callActivityOnNewIntent(Activity activity, Intent intent) { - activity.performNewIntent(intent); + if (android.security.Flags.contentUriPermissionApis()) { + activity.performNewIntent(intent, new ComponentCaller(activity.getActivityToken(), + /* callerToken */ null)); + } else { + activity.performNewIntent(intent); + } + } + + /** + * Same as {@link #callActivityOnNewIntent(Activity, Intent)}, but with an extra parameter for + * the {@link ComponentCaller} instance associated with the app that sent the intent. + * + * @param activity The activity receiving a new Intent. + * @param intent The new intent being received. + * @param caller The {@link ComponentCaller} instance that launched the activity with the new + * intent. + */ + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + public void callActivityOnNewIntent(@NonNull Activity activity, @NonNull Intent intent, + @NonNull ComponentCaller caller) { + activity.performNewIntent(intent, caller); } /** * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent) { + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent, + @NonNull ComponentCaller caller) { + internalCallActivityOnNewIntent(activity, intent, caller); + } + + @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + private void internalCallActivityOnNewIntent(Activity activity, ReferrerIntent intent, + @NonNull ComponentCaller caller) { final String oldReferrer = activity.mReferrer; try { if (intent != null) { activity.mReferrer = intent.mReferrer; } - callActivityOnNewIntent(activity, intent != null ? new Intent(intent) : null); + Intent newIntent = intent != null ? new Intent(intent) : null; + callActivityOnNewIntent(activity, newIntent, caller); } finally { activity.mReferrer = oldReferrer; } } /** + * @hide + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent) { + if (android.security.Flags.contentUriPermissionApis()) { + internalCallActivityOnNewIntent(activity, intent, new ComponentCaller( + activity.getActivityToken(), /* callerToken */ null)); + } else { + final String oldReferrer = activity.mReferrer; + try { + if (intent != null) { + activity.mReferrer = intent.mReferrer; + } + callActivityOnNewIntent(activity, intent != null ? new Intent(intent) : null); + } finally { + activity.mReferrer = oldReferrer; + } + } + } + + /** * Perform calling of an activity's {@link Activity#onStart} * method. The default implementation simply calls through to that method. * diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index f92ff83919c7..da0cc01e363d 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -31,6 +31,7 @@ per-file Service* = file:/services/core/java/com/android/server/am/OWNERS per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS per-file *UiAutomation* = file:/services/accessibility/OWNERS +per-file *UiAutomation* = file:/core/java/android/permission/OWNERS per-file GameManager* = file:/GAME_MANAGER_OWNERS per-file GameMode* = file:/GAME_MANAGER_OWNERS per-file GameState* = file:/GAME_MANAGER_OWNERS diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index ff23f09f78a5..350b1edf2129 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -34,3 +34,10 @@ flag { description: "Add a new callback in Service to indicate a FGS has reached its timeout." bug: "317799821" } + +flag { + name: "bcast_event_timestamps" + namespace: "backstage_power" + description: "Add APIs for clients to provide broadcast event trigger timestamps" + bug: "325136414" +} diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index a884ab0c4099..3c56aaf33ef3 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -20,6 +20,7 @@ import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.admin.flags.Flags; import android.os.UserManager; @@ -53,6 +54,15 @@ public final class DevicePolicyIdentifiers { public static final String SECURITY_LOGGING_POLICY = "securityLogging"; /** + * String identifier for {@link DevicePolicyManager#setAuditLogEnabled}. + * + * @hide + */ + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @SystemApi + public static final String AUDIT_LOGGING_POLICY = "auditLogging"; + + /** * String identifier for {@link DevicePolicyManager#setLockTaskPackages}. */ public static final String LOCK_TASK_POLICY = "lockTask"; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c53b54c9af2e..a6fda9d23aca 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -37,6 +37,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MTE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PACKAGE_STATE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE; @@ -44,6 +45,7 @@ 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; @@ -53,7 +55,9 @@ import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED; import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; +import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; +import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED; import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM; import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; @@ -152,6 +156,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.org.conscrypt.TrustedCertificateStore; +import com.android.internal.os.Zygote; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; @@ -229,7 +234,6 @@ public class DevicePolicyManager { private final boolean mParentInstance; private final DevicePolicyResourcesManager mResourcesManager; - /** @hide */ public DevicePolicyManager(Context context, IDevicePolicyManager service) { this(context, service, false); @@ -4116,6 +4120,19 @@ public class DevicePolicyManager { return MTE_NOT_CONTROLLED_BY_POLICY; } + /** + * Get the current MTE state of the device. + * + * <a href="https://source.android.com/docs/security/test/memory-safety/arm-mte"> + * Learn more about MTE</a> + * + * @return whether MTE is currently enabled on the device. + */ + @FlaggedApi(FLAG_IS_MTE_POLICY_ENFORCED) + public static boolean isMtePolicyEnforced() { + return Zygote.nativeSupportsMemoryTagging(); + } + /** Indicates that content protection is not controlled by policy, allowing user to choose. */ @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0; @@ -13416,17 +13433,25 @@ public class DevicePolicyManager { } /** - * Called by device or profile owners to get information about a pending system update. + * Get information about a pending system update. + * + * Can be called by device or profile owners, and starting from Android + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, holders of the permission + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}. * * @param admin Which profile or device owner this request is associated with. * @return Information about a pending system update or {@code null} if no update pending. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a device, profile owner or holders of + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}. * @see DeviceAdminReceiver#onSystemUpdatePending(Context, Intent, long) */ - public @Nullable SystemUpdateInfo getPendingSystemUpdate(@NonNull ComponentName admin) { + @RequiresPermission(value = MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional = true) + @SuppressLint("RequiresPermission") + @FlaggedApi(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED) + public @Nullable SystemUpdateInfo getPendingSystemUpdate(@Nullable ComponentName admin) { throwIfParentInstance("getPendingSystemUpdate"); try { - return mService.getPendingSystemUpdate(admin); + return mService.getPendingSystemUpdate(admin, mContext.getPackageName()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -14034,6 +14059,74 @@ public class DevicePolicyManager { } /** + * Controls whether audit logging is enabled. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) + public void setAuditLogEnabled(boolean enabled) { + throwIfParentInstance("setAuditLogEnabled"); + try { + mService.setAuditLogEnabled(mContext.getPackageName(), true); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + } + } + + /** + * @return Whether audit logging is enabled. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) + public boolean isAuditLogEnabled() { + throwIfParentInstance("isAuditLogEnabled"); + try { + return mService.isAuditLogEnabled(mContext.getPackageName()); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + // unreachable + return false; + } + } + + /** + * Sets audit log event callback. Only one callback per UID is active at any time, when a new + * callback is set, the previous one is forgotten. Should only be called when audit log policy + * is enforced by the caller. Disabling the policy clears the callback. Each time a new callback + * is set, it will first be invoked with all the audit log events available at the time. + * + * @param callback callback to invoke when new audit log events become available or {@code null} + * to clear the callback. + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) + public void setAuditLogEventCallback( + @NonNull @CallbackExecutor Executor executor, + @Nullable Consumer<List<SecurityEvent>> callback) { + throwIfParentInstance("setAuditLogEventCallback"); + final IAuditLogEventsCallback wrappedCallback = callback == null + ? null + : new IAuditLogEventsCallback.Stub() { + @Override + public void onNewAuditLogEvents(List<SecurityEvent> events) { + executor.execute(() -> callback.accept(events)); + } + }; + try { + mService.setAuditLogEventsCallback(mContext.getPackageName(), wrappedCallback); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + } + } + + /** * Called by device owner or profile owner of an organization-owned managed profile to retrieve * all new security logging entries since the last call to this API after device boots. * @@ -16495,8 +16588,9 @@ public class DevicePolicyManager { * The identifier would be consistent even if the work profile is removed and enrolled again * (to the same organization), or the device is factory reset and re-enrolled. * - * Can only be called by the Profile Owner or Device Owner, if the - * {@link #setOrganizationId(String)} was previously called. + * Can only be called by the Profile Owner and Device Owner, and starting from Android + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, holders of the permission + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES}. * If {@link #setOrganizationId(String)} was not called, then the returned value will be an * empty string. * @@ -16509,8 +16603,12 @@ public class DevicePolicyManager { * and must switch to using this method. * * @return A stable, enrollment-specific identifier. - * @throws SecurityException if the caller is not a profile owner or device owner. + * @throws SecurityException if the caller is not a profile owner, device owner or holding the + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission */ + @RequiresPermission(value = MANAGE_DEVICE_POLICY_CERTIFICATES, conditional = true) + @SuppressLint("RequiresPermission") + @FlaggedApi(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED) @NonNull public String getEnrollmentSpecificId() { throwIfParentInstance("getEnrollmentSpecificId"); if (mService == null) { @@ -17030,6 +17128,26 @@ public class DevicePolicyManager { } /** + * + * Returns whether the device considers itself to be potentially stolen. + * @hide + */ + @SystemApi + @RequiresPermission(value = MANAGE_DEVICE_POLICY_THEFT_DETECTION) + @FlaggedApi(Flags.FLAG_DEVICE_THEFT_API_ENABLED) + public boolean isTheftDetectionTriggered() { + throwIfParentInstance("isTheftDetectionTriggered"); + if (mService == null) { + return false; + } + try { + return mService.isTheftDetectionTriggered(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns a {@link DevicePolicyResourcesManager} containing the required APIs to set, reset, * and get device policy related resources. */ diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 07ee8de587f8..1aee9fe57466 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -338,4 +338,9 @@ public abstract class DevicePolicyManagerInternal { * Enforces resolved security logging policy, should only be invoked from device policy engine. */ public abstract void enforceSecurityLoggingPolicy(boolean enabled); + + /** + * Enforces resolved audit logging policy, should only be invoked from device policy engine. + */ + public abstract void enforceAuditLoggingPolicy(boolean enabled); } diff --git a/core/java/android/app/admin/IAuditLogEventsCallback.aidl b/core/java/android/app/admin/IAuditLogEventsCallback.aidl new file mode 100644 index 000000000000..ab871178a9b9 --- /dev/null +++ b/core/java/android/app/admin/IAuditLogEventsCallback.aidl @@ -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. + */ + +package android.app.admin; + +import android.app.admin.SecurityLog; + +/** @hide */ +oneway interface IAuditLogEventsCallback { + void onNewAuditLogEvents(in List<SecurityLog.SecurityEvent> events); +}
\ No newline at end of file diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index c4cbdd6bc1f4..3a7a891c7995 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -32,6 +32,7 @@ import android.app.admin.SystemUpdatePolicy; import android.app.admin.PackagePolicy; import android.app.admin.PasswordMetrics; import android.app.admin.FactoryResetProtectionPolicy; +import android.app.admin.IAuditLogEventsCallback; import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.ManagedSubscriptionsPolicy; @@ -392,7 +393,7 @@ interface IDevicePolicyManager { boolean getDoNotAskCredentialsOnBoot(); void notifyPendingSystemUpdate(in SystemUpdateInfo info); - SystemUpdateInfo getPendingSystemUpdate(in ComponentName admin); + SystemUpdateInfo getPendingSystemUpdate(in ComponentName admin, in String callerPackage); void setPermissionPolicy(in ComponentName admin, in String callerPackage, int policy); int getPermissionPolicy(in ComponentName admin); @@ -441,6 +442,10 @@ interface IDevicePolicyManager { long forceNetworkLogs(); long forceSecurityLogs(); + void setAuditLogEnabled(String callerPackage, boolean enabled); + boolean isAuditLogEnabled(String callerPackage); + void setAuditLogEventsCallback(String callerPackage, in IAuditLogEventsCallback callback); + boolean isUninstallInQueue(String packageName); void uninstallPackageWithActiveAdmins(String packageName); @@ -576,6 +581,8 @@ interface IDevicePolicyManager { void setWifiSsidPolicy(String callerPackageName, in WifiSsidPolicy policy); WifiSsidPolicy getWifiSsidPolicy(String callerPackageName); + boolean isTheftDetectionTriggered(String callerPackageName); + List<UserHandle> listForegroundAffiliatedUsers(); void setDrawables(in List<DevicePolicyDrawableResource> drawables); void resetDrawables(in List<String> drawableIds); diff --git a/core/java/android/app/admin/SecurityLog.aidl b/core/java/android/app/admin/SecurityLog.aidl new file mode 100644 index 000000000000..e5ae2df8c21b --- /dev/null +++ b/core/java/android/app/admin/SecurityLog.aidl @@ -0,0 +1,20 @@ +/* + * 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.admin; + +/** @hide */ +parcelable SecurityLog.SecurityEvent;
\ No newline at end of file diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 30cd1b72fd49..cbd8e5b2ec3e 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -92,6 +92,13 @@ flag { } flag { + name: "allow_querying_profile_type" + namespace: "enterprise" + description: "Public APIs to query if a user is a profile and what kind of profile type it is." + bug: "323001115" +} + +flag { name: "quiet_mode_credential_bug_fix" namespace: "enterprise" description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped." @@ -132,3 +139,10 @@ flag { description: "Add Headless DO support." bug: "289515470" } + +flag { + name: "is_mte_policy_enforced" + namespace: "enterprise" + description: "Allow to query whether MTE is enabled or not to check for compliance for enterprise policy" + bug: "322777918" +} diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS new file mode 100644 index 000000000000..6932ba23a8ac --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/OWNERS @@ -0,0 +1,7 @@ +# Bug component: 1363385 + +sandeepbandaru@google.com +shivanker@google.com +hackz@google.com +volnov@google.com + diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e8031a374310..0bcbb8e1868c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -19,6 +19,7 @@ package android.content; import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY; import static android.content.ContentProvider.maybeAddUserId; import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; +import static android.security.Flags.FLAG_FRP_ENFORCEMENT; import static android.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA; import android.Manifest; @@ -3902,6 +3903,26 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.ACTION_IDLE_MAINTENANCE_END"; /** + * Broadcast Action: A broadcast sent to the main user when the main user changes their + * Lock Screen Knowledge Factor, either because they changed the current value, or because + * they added or removed it. + * + * <p class="note">At present, this intent is only broadcast to listeners with the + * CONFIGURE_FACTORY_RESET_PROTECTION signature|privileged permiession.</p> + * + * <p class="note">This is a protected intent that can only be sent by the system.</p> + * + * @hide + */ + @FlaggedApi(FLAG_FRP_ENFORCEMENT) + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(protectedBroadcast = true) + public static final String + ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED = + "android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED"; + + /** * Broadcast Action: a remote intent is to be broadcasted. * * A remote intent is used for remote RPC between devices. The remote intent diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index a8dba5182e90..cae4fab0099c 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1565,6 +1565,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { private Boolean requestRawExternalStorageAccess; /** + * If {@code false}, this app does not allow its activities to be replaced by another app. + * Is set from application manifest application tag's allowCrossUidActivitySwitchFromBelow + * attribute. + * @hide + */ + public boolean allowCrossUidActivitySwitchFromBelow = true; + + /** * Represents the default policy. The actual policy used will depend on other properties of * the application, e.g. the target SDK version. * @hide @@ -1760,6 +1768,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { + Integer.toHexString(localeConfigRes)); } pw.println(prefix + "enableOnBackInvokedCallback=" + isOnBackInvokedCallbackEnabled()); + pw.println(prefix + "allowCrossUidActivitySwitchFromBelow=" + + allowCrossUidActivitySwitchFromBelow); + } pw.println(prefix + "createTimestamp=" + createTimestamp); if (mKnownActivityEmbeddingCerts != null) { @@ -1877,6 +1888,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { proto.write(ApplicationInfoProto.Detail.NATIVE_HEAP_ZERO_INIT, nativeHeapZeroInitialized); } + proto.write(ApplicationInfoProto.Detail.ALLOW_CROSS_UID_ACTIVITY_SWITCH_FROM_BELOW, + allowCrossUidActivitySwitchFromBelow); proto.end(detailToken); } if (!ArrayUtils.isEmpty(mKnownActivityEmbeddingCerts)) { @@ -2002,6 +2015,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized; requestRawExternalStorageAccess = orig.requestRawExternalStorageAccess; localeConfigRes = orig.localeConfigRes; + allowCrossUidActivitySwitchFromBelow = orig.allowCrossUidActivitySwitchFromBelow; createTimestamp = SystemClock.uptimeMillis(); } @@ -2106,6 +2120,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } dest.writeInt(localeConfigRes); + dest.writeInt(allowCrossUidActivitySwitchFromBelow ? 1 : 0); + sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags); } @@ -2204,6 +2220,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } } localeConfigRes = source.readInt(); + allowCrossUidActivitySwitchFromBelow = source.readInt() != 0; + mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source); if (mKnownActivityEmbeddingCerts.isEmpty()) { mKnownActivityEmbeddingCerts = null; diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 529363f828bb..8220313a9197 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -19,7 +19,9 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL; import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; +import static android.app.admin.flags.Flags.FLAG_ALLOW_QUERYING_PROFILE_TYPE; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -314,6 +316,41 @@ public class CrossProfileApps { } } + + /** + * Checks if the specified user is a profile, i.e. not the parent user. + * + * @param userHandle The UserHandle of the target profile, must be one of the users returned by + * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will + * be thrown. + * @return whether the specified user is a profile. + */ + @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE) + public boolean isProfile(@NonNull UserHandle userHandle) { + // Note that this is not a security check, but rather a check for correct use. + // The actual security check is performed by UserManager. + verifyCanAccessUser(userHandle); + + return mUserManager.isProfile(userHandle.getIdentifier()); + } + + /** + * Checks if the specified user is a managed profile. + * + * @param userHandle The UserHandle of the target profile, must be one of the users returned by + * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will + * be thrown. + * @return whether the specified user is a managed profile. + */ + @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE) + public boolean isManagedProfile(@NonNull UserHandle userHandle) { + // Note that this is not a security check, but rather a check for correct use. + // The actual security check is performed by UserManager. + verifyCanAccessUser(userHandle); + + return mUserManager.isManagedProfile(userHandle.getIdentifier()); + } + /** * Return a label that calling app can show to user for the semantic of profile switching -- * launching its own activity in specified user profile. For example, it may return @@ -677,6 +714,11 @@ public class CrossProfileApps { } } + /** + * A validation method to check that the methods in this class are only being applied to user + * handles returned by {@link #getTargetUserProfiles()}. As this is run client-side for + * input validation purposes, this should never replace a real security check service-side. + */ private void verifyCanAccessUser(UserHandle userHandle) { if (!getTargetUserProfiles().contains(userHandle)) { throw new SecurityException("Not allowed to access " + userHandle); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 7c264f65d471..9c859c442355 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -17,6 +17,8 @@ package android.content.pm; import static android.Manifest.permission; +import static android.Manifest.permission.ACCESS_HIDDEN_PROFILES; +import static android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL; import static android.Manifest.permission.READ_FRAME_BUFFER; import android.annotation.CallbackExecutor; @@ -779,15 +781,20 @@ public class LauncherApps { /** * Returns information related to a user which is useful for displaying UI elements - * to distinguish it from other users (eg, badges). Only system launchers should - * call this API. + * to distinguish it from other users (eg, badges). * - * @param userHandle user handle of the user for which LauncherUserInfo is requested - * @return the LauncherUserInfo object related to the user specified. - * @hide + * <p>If the user in question is a hidden profile like + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * + * @param userHandle user handle of the user for which LauncherUserInfo is requested. + * @return the {@link LauncherUserInfo} object related to the user specified, null in case + * the user is inaccessible. */ @Nullable @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) { if (DEBUG) { Log.i(TAG, "getLauncherUserInfo " + userHandle); @@ -823,17 +830,20 @@ public class LauncherApps { * </ul> * </p> * - * + * <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 intent sender to launch App Market Activity is * required. * @param user the profile for which intent sender to launch App Market Activity is required. * @return {@link IntentSender} object which launches the App Market Activity, null in case * there is no such activity. - * @hide */ @Nullable @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public IntentSender getAppMarketActivityIntent(@Nullable String packageName, @NonNull UserHandle user) { if (DEBUG) { @@ -851,15 +861,21 @@ public class LauncherApps { /** * Returns the list of the system packages that are installed at user creation. * - * <p>An empty list denotes that all system packages are installed for that user at creation. - * This behaviour is inherited from the underlining UserManager API. + * <p>An empty list denotes that all system packages should be treated as pre-installed for that + * user at creation. + * + * <p>If the user in question is a hidden profile like + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. * * @param userHandle the user for which installed system packages are required. * @return {@link List} of {@link String}, representing the package name of the installed * package. Can be empty but not null. - * @hide */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + @NonNull + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public List<String> getPreInstalledSystemPackages(@NonNull UserHandle userHandle) { if (DEBUG) { Log.i(TAG, "getPreInstalledSystemPackages for user: " + userHandle); diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java index 214c3e48db71..8426f54d4754 100644 --- a/core/java/android/content/pm/LauncherUserInfo.java +++ b/core/java/android/content/pm/LauncherUserInfo.java @@ -27,8 +27,6 @@ import android.os.UserManager; /** * The LauncherUserInfo object holds information about an Android user that is required to display * the Launcher related UI elements specific to the user (like badges). - * - * @hide */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) public final class LauncherUserInfo implements Parcelable { @@ -41,11 +39,9 @@ public final class LauncherUserInfo implements Parcelable { /** * Returns type of the user as defined in {@link UserManager}. e.g., * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE} - * TODO(b/303812736): Make the return type public and update javadoc here once the linked bug - * is resolved. + * or {@link UserManager.USER_TYPE_PROFILE_PRIVATE} * * @return the userType for the user whose LauncherUserInfo this is - * @hide */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) @NonNull @@ -58,7 +54,6 @@ public final class LauncherUserInfo implements Parcelable { * {@link UserManager#getSerialNumberForUser(UserHandle)} * * @return the serial number associated with the user - * @hide */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) public int getUserSerialNumber() { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 49c8a7cad57a..2a673530578a 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2554,6 +2554,15 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST = -130; /** + * Installation failed return code: if the system failed to install the package that + * {@link android.R.attr#multiArch} is true in its manifest because its packaged + * native code did not match all of the natively ABIs supported by the system. + * + * @hide + */ + public static final int INSTALL_FAILED_MULTI_ARCH_NOT_MATCH_ALL_NATIVE_ABIS = -131; + + /** * App minimum aspect ratio set by the user which will override app-defined aspect ratio. * * @hide @@ -10452,6 +10461,8 @@ public abstract class PackageManager { case INSTALL_FAILED_SESSION_INVALID: return "INSTALL_FAILED_SESSION_INVALID"; case INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST: return "INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST"; + case INSTALL_FAILED_MULTI_ARCH_NOT_MATCH_ALL_NATIVE_ABIS: + return "INSTALL_FAILED_MULTI_ARCH_NOT_MATCH_ALL_NATIVE_ABIS"; default: return Integer.toString(status); } } diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index 870546a6fd2b..ba356bb55194 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -40,7 +40,7 @@ import dalvik.system.CloseGuard; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.CursorWindow_host") + "com.android.platform.test.ravenwood.nativesubstitution.CursorWindow_host") public class CursorWindow extends SQLiteClosable implements Parcelable { private static final String STATS_TAG = "CursorWindowStats"; diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl new file mode 100644 index 000000000000..838e41ee1c08 --- /dev/null +++ b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl @@ -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. + */ + +package android.hardware; + +/** @hide */ +parcelable CameraPrivacyAllowlistEntry { + String packageName; + boolean isMandatory; +} + diff --git a/core/java/android/hardware/ISensorPrivacyListener.aidl b/core/java/android/hardware/ISensorPrivacyListener.aidl index 2ac21d2615aa..19ae302355be 100644 --- a/core/java/android/hardware/ISensorPrivacyListener.aidl +++ b/core/java/android/hardware/ISensorPrivacyListener.aidl @@ -25,5 +25,6 @@ oneway interface ISensorPrivacyListener { // frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl // =============== Beginning of transactions used on native side as well ====================== void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled); + void onSensorPrivacyStateChanged(int toggleType, int sensor, int state); // =============== End of transactions used on native side as well ============================ } diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index 9cf329ca3d3d..851ce2add94f 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -16,6 +16,7 @@ package android.hardware; +import android.hardware.CameraPrivacyAllowlistEntry; import android.hardware.ISensorPrivacyListener; /** @hide */ @@ -45,6 +46,22 @@ interface ISensorPrivacyManager { void setToggleSensorPrivacy(int userId, int source, int sensor, boolean enable); void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") + List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist(); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") + int getToggleSensorPrivacyState(int toggleType, int sensor); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)") + void setToggleSensorPrivacyState(int userId, int source, int sensor, int state); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)") + void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor, int state); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") + boolean isCameraPrivacyEnabled(String packageName); + // =============== End of transactions used on native side as well ============================ void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token, @@ -53,4 +70,4 @@ interface ISensorPrivacyManager { boolean requiresAuthentication(); void showSensorUseDialog(int sensor); -}
\ No newline at end of file +} diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index 18c95bfbb297..6294a8d617de 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -17,6 +17,7 @@ package android.hardware; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -38,9 +39,11 @@ import android.util.Log; import android.util.Pair; import com.android.internal.annotations.GuardedBy; +import com.android.internal.camera.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; @@ -215,13 +218,41 @@ public final class SensorPrivacyManager { public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED; /** + * Constant indicating privacy is enabled except for the automotive driver assistance apps + * which are helpful for driving. + */ + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = + SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS; + + /** + * Constant indicating privacy is enabled except for the automotive driver assistance apps + * which are required by car manufacturer for driving. + */ + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = + SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS; + + /** + * Constant indicating privacy is enabled except for the automotive driver assistance apps + * which are both helpful for driving and also apps required by car manufacturer for + * driving. + */ + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = + SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS; + + /** * Types of state which can exist for a sensor privacy toggle * * @hide */ @IntDef(value = { ENABLED, - DISABLED + DISABLED, + AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS, + AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS, + AUTOMOTIVE_DRIVER_ASSISTANCE_APPS }) @Retention(RetentionPolicy.SOURCE) public @interface StateType {} @@ -266,6 +297,19 @@ public final class SensorPrivacyManager { private int mToggleType; private int mSensor; private boolean mEnabled; + private int mState; + + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private SensorPrivacyChangedParams(int toggleType, int sensor, int state) { + mToggleType = toggleType; + mSensor = sensor; + mState = state; + if (state == StateTypes.ENABLED) { + mEnabled = true; + } else { + mEnabled = false; + } + } private SensorPrivacyChangedParams(int toggleType, int sensor, boolean enabled) { mToggleType = toggleType; @@ -284,6 +328,12 @@ public final class SensorPrivacyManager { public boolean isEnabled() { return mEnabled; } + + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public @StateTypes.StateType int getState() { + return mState; + } + } } @@ -319,6 +369,9 @@ public final class SensorPrivacyManager { private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>, OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>(); + @GuardedBy("mLock") + private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null; + /** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local * listeners */ @NonNull @@ -328,12 +381,33 @@ public final class SensorPrivacyManager { synchronized (mLock) { for (int i = 0; i < mToggleListeners.size(); i++) { OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i); + if (Flags.cameraPrivacyAllowlist()) { + int state = enabled ? StateTypes.ENABLED : StateTypes.DISABLED; + mToggleListeners.valueAt(i).execute(() -> listener + .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener + .SensorPrivacyChangedParams(toggleType, sensor, state))); + } else { + mToggleListeners.valueAt(i).execute(() -> listener + .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener + .SensorPrivacyChangedParams(toggleType, sensor, enabled))); + } + } + } + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) { + synchronized (mLock) { + for (int i = 0; i < mToggleListeners.size(); i++) { + OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i); mToggleListeners.valueAt(i).execute(() -> listener .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener - .SensorPrivacyChangedParams(toggleType, sensor, enabled))); + .SensorPrivacyChangedParams(toggleType, sensor, state))); } } } + }; /** Whether the singleton ISensorPrivacyListener has been registered */ @@ -649,6 +723,73 @@ public final class SensorPrivacyManager { } /** + * Returns sensor privacy state for a specific sensor. + * + * @return int sensor privacy state. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public @StateTypes.StateType int getSensorPrivacyState(@ToggleType int toggleType, + @Sensors.Sensor int sensor) { + try { + return mService.getToggleSensorPrivacyState(toggleType, sensor); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns if camera privacy is enabled for a specific package. + * + * @return boolean sensor privacy state. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public boolean isCameraPrivacyEnabled(@NonNull String packageName) { + try { + return mService.isCameraPrivacyEnabled(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns camera privacy allowlist. + * + * @return List of automotive driver assistance packages for + * privacy allowlisting. The returned map includes the package + * name as key and the value is a Boolean which tells if that package + * is required by the car manufacturer as mandatory package for driving. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public @NonNull Map<String, Boolean> getCameraPrivacyAllowlist() { + synchronized (mLock) { + if (mCameraPrivacyAllowlist == null) { + mCameraPrivacyAllowlist = new ArrayMap<>(); + try { + for (CameraPrivacyAllowlistEntry entry : + mService.getCameraPrivacyAllowlist()) { + mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return mCameraPrivacyAllowlist; + } + } + + /** * Sets sensor privacy to the specified state for an individual sensor. * * @param sensor the sensor which to change the state for @@ -677,6 +818,22 @@ public final class SensorPrivacyManager { * Sets sensor privacy to the specified state for an individual sensor. * * @param sensor the sensor which to change the state for + * @param state the state to which sensor privacy should be set. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void setSensorPrivacyState(@Sensors.Sensor int sensor, + @StateTypes.StateType int state) { + setSensorPrivacyState(resolveSourceFromCurrentContext(), sensor, state); + } + + /** + * Sets sensor privacy to the specified state for an individual sensor. + * + * @param sensor the sensor which to change the state for * @param enable the state to which sensor privacy should be set. * * @hide @@ -708,6 +865,27 @@ public final class SensorPrivacyManager { } /** + * Sets sensor privacy to the specified state for an individual sensor. + * + * @param sensor the sensor which to change the state for + * @param state the state to which sensor privacy should be set. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void setSensorPrivacyState(@Sources.Source int source, @Sensors.Sensor int sensor, + @StateTypes.StateType int state) { + try { + mService.setToggleSensorPrivacyState(mContext.getUserId(), source, sensor, state); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + + /** * Sets sensor privacy to the specified state for an individual sensor for the profile group of * context's user. * @@ -745,6 +923,28 @@ public final class SensorPrivacyManager { } /** + * Sets sensor privacy to the specified state for an individual sensor for the profile group of + * context's user. + * + * @param source the source using which the sensor is toggled. + * @param sensor the sensor which to change the state for + * @param state the state to which sensor privacy should be set. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void setSensorPrivacyStateForProfileGroup(@Sources.Source int source, + @Sensors.Sensor int sensor, @StateTypes.StateType int state) { + try { + mService.setToggleSensorPrivacyStateForProfileGroup(mContext.getUserId(), source, + sensor, state); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Don't show dialogs to turn off sensor privacy for this package. * * @param suppress Whether to suppress or re-enable. @@ -865,6 +1065,12 @@ public final class SensorPrivacyManager { boolean enabled) { listener.onAllSensorPrivacyChanged(enabled); } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void onSensorPrivacyStateChanged(int toggleType, int sensor, + int state) { + } }; mListeners.put(listener, iListener); } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 451d6fba0105..2add77e44dd3 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.DeviceStateSensorOrientationMap; @@ -6076,6 +6077,28 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION = new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + /** + * <p>Minimum and maximum padding zoom factors supported by this camera device for + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension.</p> + * <p>The minimum and maximum padding zoom factors supported by the device for + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension feature. This extension specific camera characteristic can be queried using + * {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p> + * <p><b>Units</b>: A pair of padding zoom factors in floating-points: + * (minPaddingZoomFactor, maxPaddingZoomFactor)</p> + * <p><b>Range of valid values:</b><br></p> + * <p>1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE = + new Key<android.util.Range<Float>>("android.efv.paddingZoomFactorRange", new TypeReference<android.util.Range<Float>>() {{ }}); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 3b10e0dd516a..f6b22edaafb1 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.ImageFormat; +import android.hardware.camera2.CameraCharacteristics.Key; import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.ICameraExtensionsProxyService; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; @@ -35,6 +36,8 @@ import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.SizeList; import android.hardware.camera2.impl.CameraExtensionUtils; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.ExtensionKey; +import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Binder; @@ -1497,4 +1500,28 @@ public final class CameraExtensionCharacteristics { return Collections.unmodifiableSet(ret); } + + + /** + * <p>Minimum and maximum padding zoom factors supported by this camera device for + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for + * the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension.</p> + * <p>The minimum and maximum padding zoom factors supported by the device for + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension feature. This extension specific camera characteristic can be queried using + * {@link android.hardware.camera2.CameraExtensionCharacteristics#get}.</p> + * <p><b>Units</b>: A pair of padding zoom factors in floating-points: + * (minPaddingZoomFactor, maxPaddingZoomFactor)</p> + * <p><b>Range of valid values:</b><br></p> + * <p>1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE = + CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE; } diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 7cf10d89004f..e24c98e98c5d 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.util.Log; @@ -276,8 +277,11 @@ public abstract class CameraMetadata<TKey> { throw new IllegalArgumentException("key type must be that of a metadata key"); } - if (field.getAnnotation(PublicKey.class) == null) { - // Never expose @hide keys up to the API user + if (field.getAnnotation(PublicKey.class) == null + && field.getAnnotation(ExtensionKey.class) == null) { + // Never expose @hide keys to the API user unless they are + // marked as @ExtensionKey, as these keys are publicly accessible via + // the extension key classes. return false; } @@ -3893,6 +3897,36 @@ public abstract class CameraMetadata<TKey> { public static final int DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2; // + // Enumeration values for CaptureRequest#EFV_STABILIZATION_MODE + // + + /** + * <p>No stabilization.</p> + * @see CaptureRequest#EFV_STABILIZATION_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_OFF = 0; + + /** + * <p>Gimbal stabilization mode.</p> + * @see CaptureRequest#EFV_STABILIZATION_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; + + /** + * <p>Locked stabilization mode which uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization to directionally steady the target region.</p> + * @see CaptureRequest#EFV_STABILIZATION_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_LOCKED = 2; + + // // Enumeration values for CaptureResult#CONTROL_AE_STATE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index ded96a23e11e..66efccd14097 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.OutputConfiguration; @@ -4292,6 +4293,146 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> public static final Key<Integer> EXTENSION_STRENGTH = new Key<Integer>("android.extension.strength", int.class); + /** + * <p>Used to apply an additional digital zoom factor for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. + * This additional zoom factor serves as a buffer to provide more flexibility for the + * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } + * mode. If android.efv.paddingZoomFactor is not set, the default will be used. + * The effectiveness of the stabilization may be influenced by the amount of padding zoom + * applied. A higher padding zoom factor can stabilize the target region more effectively + * with greater flexibility but may potentially impact image quality. Conversely, a lower + * padding zoom factor may be used to prioritize preserving image quality, albeit with less + * leeway in stabilizing the target region. It is recommended to set the + * android.efv.paddingZoomFactor to at least 1.5.</p> + * <p>If android.efv.autoZoom is enabled, the requested android.efv.paddingZoomFactor will be overridden. + * android.efv.maxPaddingZoomFactor can be checked for more details on controlling the + * padding zoom factor during android.efv.autoZoom.</p> + * <p><b>Range of valid values:</b><br> + * android.efv.paddingZoomFactorRange</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = + new Key<Float>("android.efv.paddingZoomFactor", float.class); + + /** + * <p>Used to enable or disable auto zoom for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Turn on auto zoom to let the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature decide at any given point a combination of + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and android.efv.paddingZoomFactor + * to keep the target region in view and stabilized. The combination chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested + * android.efv.paddingZoomFactor. A limit can be set on the padding zoom if wanting + * to control image quality further using android.efv.maxPaddingZoomFactor.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Boolean> EFV_AUTO_ZOOM = + new Key<Boolean>("android.efv.autoZoom", boolean.class); + + /** + * <p>Used to limit the android.efv.paddingZoomFactor if + * android.efv.autoZoom is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>If android.efv.autoZoom is enabled, this key can be used to set a limit + * on the android.efv.paddingZoomFactor chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode + * to control image quality.</p> + * <p><b>Range of valid values:</b><br> + * The range of android.efv.paddingZoomFactorRange. Use a value greater than or equal to + * the android.efv.paddingZoomFactor to effectively utilize this key.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = + new Key<Float>("android.efv.maxPaddingZoomFactor", float.class); + + /** + * <p>Set the stabilization mode for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension</p> + * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked + * video stabilization. Locked mode uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization feature to fixate on the current region, utilizing it as the target area for + * stabilization.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li> + * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li> + * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EFV_STABILIZATION_MODE_OFF + * @see #EFV_STABILIZATION_MODE_GIMBAL + * @see #EFV_STABILIZATION_MODE_LOCKED + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Integer> EFV_STABILIZATION_MODE = + new Key<Integer>("android.efv.stabilizationMode", int.class); + + /** + * <p>Used to update the target region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A android.util.Pair<Integer,Integer> that represents the desired + * <Horizontal,Vertical> shift of the current locked view (or target region) in + * pixels. Negative values indicate left and upward shifts, while positive values indicate + * right and downward shifts in the active array coordinate system.</p> + * <p><b>Range of valid values:</b><br> + * android.util.Pair<Integer,Integer> represents the + * <Horizontal,Vertical> shift. The range for the horizontal shift is + * [-max(android.efv.paddingRegion-left), max(android.efv.paddingRegion-right)]. + * The range for the vertical shift is + * [-max(android.efv.paddingRegion-top), max(android.efv.paddingRegion-bottom)]</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = + new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }}); + + /** + * <p>Representing the desired clockwise rotation + * of the target region in degrees for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Value representing the desired clockwise rotation of the target + * region in degrees.</p> + * <p><b>Range of valid values:</b><br> + * 0 to 360</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_ROTATE_VIEWPORT = + new Key<Float>("android.efv.rotateViewport", float.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 7cf5a7f2e445..a01c23d984f4 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.utils.TypeReference; @@ -5919,6 +5920,214 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { public static final Key<Integer> EXTENSION_STRENGTH = new Key<Integer>("android.extension.strength", int.class); + /** + * <p>The padding region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides + * before the target region starts to go out of bounds.</p> + * <p>The padding region denotes the area surrounding the stabilized target region within which + * the camera can be moved while maintaining the target region in view. As the camera moves, + * the padding region adjusts to represent the proximity of the target region to the + * boundary, which is the point at which the target region will start to go out of bounds.</p> + * <p><b>Range of valid values:</b><br> + * The padding is the number of remaining pixels of padding in each direction. + * The pixels reference the active array coordinate system. Negative values indicate the target + * region is out of bounds. The value for this key may be null for when the stabilization mode is + * in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_OFF } + * or {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_GIMBAL } mode.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<int[]> EFV_PADDING_REGION = + new Key<int[]>("android.efv.paddingRegion", int[].class); + + /** + * <p>The padding region when android.efv.autoZoom is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides + * before the target region starts to go out of bounds.</p> + * <p>This may differ from android.efv.paddingRegion as the field of view can change + * during android.efv.autoZoom, altering the boundary region and thus updating the padding between the + * target region and the boundary.</p> + * <p><b>Range of valid values:</b><br> + * The padding is the number of remaining pixels of padding in each direction + * when android.efv.autoZoom is enabled. Negative values indicate the target region is out of bounds. + * The value for this key may be null for when the android.efv.autoZoom is not enabled.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = + new Key<int[]>("android.efv.autoZoomPaddingRegion", int[].class); + + /** + * <p>List of coordinates representing the target region relative to the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE } + * for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in + * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A list of android.graphics.PointF that define the coordinates of the target region + * relative to the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }. + * The array represents the target region coordinates as: top-left, top-right, bottom-left, + * bottom-right.</p> + * <p><b>Range of valid values:</b><br> + * The list of target coordinates will define a region within the bounds of the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = + new Key<android.graphics.PointF[]>("android.efv.targetCoordinates", android.graphics.PointF[].class); + + /** + * <p>Used to apply an additional digital zoom factor for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. + * This additional zoom factor serves as a buffer to provide more flexibility for the + * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } + * mode. If android.efv.paddingZoomFactor is not set, the default will be used. + * The effectiveness of the stabilization may be influenced by the amount of padding zoom + * applied. A higher padding zoom factor can stabilize the target region more effectively + * with greater flexibility but may potentially impact image quality. Conversely, a lower + * padding zoom factor may be used to prioritize preserving image quality, albeit with less + * leeway in stabilizing the target region. It is recommended to set the + * android.efv.paddingZoomFactor to at least 1.5.</p> + * <p>If android.efv.autoZoom is enabled, the requested android.efv.paddingZoomFactor will be overridden. + * android.efv.maxPaddingZoomFactor can be checked for more details on controlling the + * padding zoom factor during android.efv.autoZoom.</p> + * <p><b>Range of valid values:</b><br> + * android.efv.paddingZoomFactorRange</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = + new Key<Float>("android.efv.paddingZoomFactor", float.class); + + /** + * <p>Set the stabilization mode for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension</p> + * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked + * video stabilization. Locked mode uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization feature to fixate on the current region, utilizing it as the target area for + * stabilization.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li> + * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li> + * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EFV_STABILIZATION_MODE_OFF + * @see #EFV_STABILIZATION_MODE_GIMBAL + * @see #EFV_STABILIZATION_MODE_LOCKED + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Integer> EFV_STABILIZATION_MODE = + new Key<Integer>("android.efv.stabilizationMode", int.class); + + /** + * <p>Used to enable or disable auto zoom for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Turn on auto zoom to let the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature decide at any given point a combination of + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and android.efv.paddingZoomFactor + * to keep the target region in view and stabilized. The combination chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested + * android.efv.paddingZoomFactor. A limit can be set on the padding zoom if wanting + * to control image quality further using android.efv.maxPaddingZoomFactor.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Boolean> EFV_AUTO_ZOOM = + new Key<Boolean>("android.efv.autoZoom", boolean.class); + + /** + * <p>Representing the desired clockwise rotation + * of the target region in degrees for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Value representing the desired clockwise rotation of the target + * region in degrees.</p> + * <p><b>Range of valid values:</b><br> + * 0 to 360</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_ROTATE_VIEWPORT = + new Key<Float>("android.efv.rotateViewport", float.class); + + /** + * <p>Used to update the target region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A android.util.Pair<Integer,Integer> that represents the desired + * <Horizontal,Vertical> shift of the current locked view (or target region) in + * pixels. Negative values indicate left and upward shifts, while positive values indicate + * right and downward shifts in the active array coordinate system.</p> + * <p><b>Range of valid values:</b><br> + * android.util.Pair<Integer,Integer> represents the + * <Horizontal,Vertical> shift. The range for the horizontal shift is + * [-max(android.efv.paddingRegion-left), max(android.efv.paddingRegion-right)]. + * The range for the vertical shift is + * [-max(android.efv.paddingRegion-top), max(android.efv.paddingRegion-bottom)]</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = + new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }}); + + /** + * <p>Used to limit the android.efv.paddingZoomFactor if + * android.efv.autoZoom is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>If android.efv.autoZoom is enabled, this key can be used to set a limit + * on the android.efv.paddingZoomFactor chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode + * to control image quality.</p> + * <p><b>Range of valid values:</b><br> + * The range of android.efv.paddingZoomFactorRange. Use a value greater than or equal to + * the android.efv.paddingZoomFactor to effectively utilize this key.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = + new Key<Float>("android.efv.maxPaddingZoomFactor", float.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java new file mode 100644 index 000000000000..32039c6ec0ba --- /dev/null +++ b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java @@ -0,0 +1,227 @@ +/* + * 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.camera2; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureRequest.Key; +import android.hardware.camera2.impl.ExtensionKey; +import android.hardware.camera2.impl.PublicKey; + +import com.android.internal.camera.flags.Flags; + +/** + * ExtensionCaptureRequest contains definitions for extension-specific CaptureRequest keys that + * can be used to configure a {@link android.hardware.camera2.CaptureRequest} during a + * {@link android.hardware.camera2.CameraExtensionSession}. + * + * Note that ExtensionCaptureRequest is not intended to be used as a replacement + * for CaptureRequest in the extensions. It serves as a supplementary class providing + * extension-specific CaptureRequest keys. Developers should use these keys in conjunction + * with regular CaptureRequest objects during a + * {@link android.hardware.camera2.CameraExtensionSession}. + * + * @see CaptureRequest + * @see CameraExtensionSession + */ +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public final class ExtensionCaptureRequest { + + /** + * <p>Used to apply an additional digital zoom factor for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. + * This additional zoom factor serves as a buffer to provide more flexibility for the + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } + * mode. If {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } is not set, the default will be used. + * The effectiveness of the stabilization may be influenced by the amount of padding zoom + * applied. A higher padding zoom factor can stabilize the target region more effectively + * with greater flexibility but may potentially impact image quality. Conversely, a lower + * padding zoom factor may be used to prioritize preserving image quality, albeit with less + * leeway in stabilizing the target region. It is recommended to set the + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to at least 1.5.</p> + * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, the requested {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } will be overridden. + * {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR } can be checked for more details on controlling the + * padding zoom factor during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }.</p> + * <p><b>Range of valid values:</b><br> + * {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR; + + /** + * <p>Used to enable or disable auto zoom for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Turn on auto zoom to let the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature decide at any given point a combination of + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } + * to keep the target region in view and stabilized. The combination chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested + * {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }. A limit can be set on the padding zoom if wanting + * to control image quality further using {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM; + + /** + * <p>Used to limit the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } if + * {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, this key can be used to set a limit + * on the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode + * to control image quality.</p> + * <p><b>Range of valid values:</b><br> + * The range of {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE Range}. Use a value greater than or equal to + * the {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to + * effectively utilize this key.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR; + + /** + * <p>Set the stabilization mode for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension</p> + * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked + * video stabilization. Locked mode uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization feature to fixate on the current region, utilizing it as the target area for + * stabilization.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li> + * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li> + * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EFV_STABILIZATION_MODE_OFF + * @see #EFV_STABILIZATION_MODE_GIMBAL + * @see #EFV_STABILIZATION_MODE_LOCKED + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE; + + /** + * <p>Used to update the target region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A android.util.Pair<Integer,Integer> that represents the desired + * <Horizontal,Vertical> shift of the current locked view (or target region) in + * pixels. Negative values indicate left and upward shifts, while positive values indicate + * right and downward shifts in the active array coordinate system.</p> + * <p><b>Range of valid values:</b><br> + * android.util.Pair<Integer,Integer> represents the + * <Horizontal,Vertical> shift. The range for the horizontal shift is + * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-left), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-right)]. + * The range for the vertical shift is + * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-top), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-bottom)]</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureResult#EFV_PADDING_REGION + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT; + + /** + * <p>Representing the desired clockwise rotation + * of the target region in degrees for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Value representing the desired clockwise rotation of the target + * region in degrees.</p> + * <p><b>Range of valid values:</b><br> + * 0 to 360</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT; + + + // + // Enumeration values for CaptureRequest#EFV_STABILIZATION_MODE + // + + /** + * <p>No stabilization.</p> + * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF; + + /** + * <p>Gimbal stabilization mode.</p> + * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL; + + /** + * <p>Locked stabilization mode which uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization to directionally steady the target region.</p> + * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED; + +}
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java new file mode 100644 index 000000000000..5c9990975a9b --- /dev/null +++ b/core/java/android/hardware/camera2/ExtensionCaptureResult.java @@ -0,0 +1,272 @@ +/* + * 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.camera2; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraExtensionCharacteristics; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.CaptureResult.Key; +import android.hardware.camera2.impl.ExtensionKey; +import android.hardware.camera2.impl.PublicKey; + +import com.android.internal.camera.flags.Flags; + +/** + * ExtensionCaptureResult contains definitions for extension-specific CaptureResult keys that + * are available during a {@link android.hardware.camera2.CameraExtensionSession} after a + * {@link android.hardware.camera2.CaptureRequest} is processed. + * + * Note that ExtensionCaptureResult is not intended to be used as a replacement + * for CaptureResult in the extensions. It serves as a supplementary class providing + * extension-specific CaptureResult keys. Developers should use these keys in conjunction + * with regular CaptureResult objects during a + * {@link android.hardware.camera2.CameraExtensionSession}. + * + * @see CaptureResult + * @see CaptureRequest + * @see CameraExtensionSession + */ +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public final class ExtensionCaptureResult { + + /** + * <p>The padding region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides + * before the target region starts to go out of bounds.</p> + * <p>The padding region denotes the area surrounding the stabilized target region within which + * the camera can be moved while maintaining the target region in view. As the camera moves, + * the padding region adjusts to represent the proximity of the target region to the + * boundary, which is the point at which the target region will start to go out of bounds.</p> + * <p><b>Range of valid values:</b><br> + * The padding is the number of remaining pixels of padding in each direction. + * The pixels reference the active array coordinate system. Negative values indicate the target region + * is out of bounds. The value for this key may be null for when the stabilization mode is + * in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_OFF } + * or {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_GIMBAL } mode.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<int[]> EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION; + + /** + * <p>The padding region when {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides + * before the target region starts to go out of bounds.</p> + * <p>This may differ from {@link ExtensionCaptureResult#EFV_PADDING_REGION } as the field of view can change + * during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }, altering the boundary region and thus updating the padding between the + * target region and the boundary.</p> + * <p><b>Range of valid values:</b><br> + * The padding is the number of remaining pixels of padding in each direction + * when {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled. Negative values indicate the target region is out of bounds. + * The value for this key may be null for when the {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is not enabled.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureResult#EFV_PADDING_REGION + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION; + + /** + * <p>List of coordinates representing the target region relative to the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE } + * for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A list of android.graphics.PointF that define the coordinates of the target region + * relative to the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }. + * The array represents the target region coordinates as: top-left, top-right, bottom-left, + * bottom-right.</p> + * <p><b>Range of valid values:</b><br> + * The list of target coordinates will define a region within the bounds of the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES; + + /** + * <p>Used to apply an additional digital zoom factor for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. + * This additional zoom factor serves as a buffer to provide more flexibility for the + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } + * mode. If {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } is not set, the default will be used. + * The effectiveness of the stabilization may be influenced by the amount of padding zoom + * applied. A higher padding zoom factor can stabilize the target region more effectively + * with greater flexibility but may potentially impact image quality. Conversely, a lower + * padding zoom factor may be used to prioritize preserving image quality, albeit with less + * leeway in stabilizing the target region. It is recommended to set the + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to at least 1.5.</p> + * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, the requested {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } will be overridden. + * {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR } can be checked for more details on controlling the + * padding zoom factor during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }.</p> + * <p><b>Range of valid values:</b><br> + * {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR; + + /** + * <p>Set the stabilization mode for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension</p> + * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked + * video stabilization. Locked mode uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization feature to fixate on the current region, utilizing it as the target area for + * stabilization.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li> + * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li> + * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EFV_STABILIZATION_MODE_OFF + * @see #EFV_STABILIZATION_MODE_GIMBAL + * @see #EFV_STABILIZATION_MODE_LOCKED + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE; + + /** + * <p>Used to enable or disable auto zoom for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Turn on auto zoom to let the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature decide at any given point a combination of + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } + * to keep the target region in view and stabilized. The combination chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested + * {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }. A limit can be set on the padding zoom if wanting + * to control image quality further using {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM; + + /** + * <p>Representing the desired clockwise rotation + * of the target region in degrees for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Value representing the desired clockwise rotation of the target + * region in degrees.</p> + * <p><b>Range of valid values:</b><br> + * 0 to 360</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT; + + /** + * <p>Used to update the target region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A android.util.Pair<Integer,Integer> that represents the desired + * <Horizontal,Vertical> shift of the current locked view (or target region) in + * pixels. Negative values indicate left and upward shifts, while positive values indicate + * right and downward shifts in the active array coordinate system.</p> + * <p><b>Range of valid values:</b><br> + * android.util.Pair<Integer,Integer> represents the + * <Horizontal,Vertical> shift. The range for the horizontal shift is + * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-left), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-right)]. + * The range for the vertical shift is + * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-top), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-bottom)]</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureResult#EFV_PADDING_REGION + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT; + + /** + * <p>Used to limit the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } if + * {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, this key can be used to set a limit + * on the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode + * to control image quality.</p> + * <p><b>Range of valid values:</b><br> + * The range of {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }. Use a value greater than or equal to + * the {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to + * effectively utilize this key.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR; + +}
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java index b4fe7fe1f0d1..53f56bc9f896 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java +++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java @@ -18,8 +18,11 @@ package android.hardware.camera2.extension; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.graphics.ImageFormat; +import android.hardware.camera2.params.ColorSpaceProfiles; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.utils.SurfaceUtils; import android.util.Size; import android.view.Surface; @@ -65,6 +68,8 @@ public final class CameraOutputSurface { mOutputSurface.size = new android.hardware.camera2.extension.Size(); mOutputSurface.size.width = size.getWidth(); mOutputSurface.size.height = size.getHeight(); + mOutputSurface.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + mOutputSurface.colorSpace = ColorSpaceProfiles.UNSPECIFIED; } /** @@ -95,4 +100,48 @@ public final class CameraOutputSurface { public @ImageFormat.Format int getImageFormat() { return mOutputSurface.imageFormat; } + + /** + * Return the dynamic range profile. The default + * dynamicRangeProfile is + * {@link android.hardware.camera2.params.DynamicRangeProfiles.STANDARD} + * unless specified by CameraOutputSurface.setDynamicRangeProfile. + */ + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + public @DynamicRangeProfiles.Profile long getDynamicRangeProfile() { + return mOutputSurface.dynamicRangeProfile; + } + + /** + * Return the color space. The default colorSpace is + * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED} + * unless specified by CameraOutputSurface.setColorSpace. + */ + @SuppressLint("MethodNameUnits") + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + public int getColorSpace() { + return mOutputSurface.colorSpace; + } + + /** + * Set the dynamic range profile. The default dynamicRangeProfile + * will be {@link android.hardware.camera2.params.DynamicRangeProfiles.STANDARD} + * unless explicitly set using this method. + */ + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + public void setDynamicRangeProfile( + @DynamicRangeProfiles.Profile long dynamicRangeProfile) { + mOutputSurface.dynamicRangeProfile = dynamicRangeProfile; + } + + /** + * Set the color space. The default colorSpace + * will be + * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED} + * unless explicitly set using this method. + */ + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + public void setColorSpace(int colorSpace) { + mOutputSurface.colorSpace = colorSpace; + } } diff --git a/core/java/android/hardware/camera2/extension/OutputSurface.aidl b/core/java/android/hardware/camera2/extension/OutputSurface.aidl index 841537918f65..02e160cdd8dd 100644 --- a/core/java/android/hardware/camera2/extension/OutputSurface.aidl +++ b/core/java/android/hardware/camera2/extension/OutputSurface.aidl @@ -24,4 +24,6 @@ parcelable OutputSurface Surface surface; Size size; int imageFormat; + long dynamicRangeProfile; + int colorSpace; } diff --git a/core/java/android/hardware/camera2/impl/ExtensionKey.java b/core/java/android/hardware/camera2/impl/ExtensionKey.java new file mode 100644 index 000000000000..15e8982c12b4 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/ExtensionKey.java @@ -0,0 +1,36 @@ +/* + * 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.camera2.impl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denote a static field {@code Key} as being an extension key (i.e. @hide as a CaptureRequest/ + * CaptureResult key but exposed as a @PublicKey through + * ExtensionCaptureRequest/ExtensionCaptureResult). + * + * <p>Keys with this annotation are assumed to always have a hidden key counter-part in + * CaptureRequest/CaptureResult.</p> + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ExtensionKey { + +} diff --git a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java index 0e6c1b39271c..69a6e9b0ab32 100644 --- a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java @@ -15,15 +15,20 @@ */ package android.hardware.camera2.params; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; - +import android.annotation.SuppressLint; +import android.graphics.ColorSpace; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraExtensionCharacteristics.Extension; import android.hardware.camera2.CameraExtensionSession; import java.util.List; import java.util.concurrent.Executor; +import com.android.internal.camera.flags.Flags; + /** * A class that aggregates all supported arguments for * {@link CameraExtensionSession} initialization. @@ -36,6 +41,7 @@ public final class ExtensionSessionConfiguration { private OutputConfiguration mPostviewOutput = null; private Executor mExecutor = null; private CameraExtensionSession.StateCallback mCallback = null; + private int mColorSpace; /** * Create a new ExtensionSessionConfiguration @@ -118,4 +124,55 @@ public final class ExtensionSessionConfiguration { Executor getExecutor() { return mExecutor; } + + /** + * Set a specific device-supported color space. + * + * <p>Clients can choose from any profile advertised as supported in + * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES} + * queried using {@link ColorSpaceProfiles#getSupportedColorSpaces}. + * When set, the colorSpace will override the default color spaces of the output targets, + * or the color space implied by the dataSpace passed into an {@link ImageReader}'s + * constructor.</p> + */ + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + public void setColorSpace(@NonNull ColorSpace.Named colorSpace) { + mColorSpace = colorSpace.ordinal(); + for (OutputConfiguration outputConfiguration : mOutputs) { + outputConfiguration.setColorSpace(colorSpace); + } + if (mPostviewOutput != null) { + mPostviewOutput.setColorSpace(colorSpace); + } + } + + /** + * Clear the color space, such that the default color space will be used. + */ + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + public void clearColorSpace() { + mColorSpace = ColorSpaceProfiles.UNSPECIFIED; + for (OutputConfiguration outputConfiguration : mOutputs) { + outputConfiguration.clearColorSpace(); + } + if (mPostviewOutput != null) { + mPostviewOutput.clearColorSpace(); + } + } + + /** + * Return the current color space. + * + * @return the currently set color space, or null + * if not set + */ + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + @SuppressLint("MethodNameUnits") + public @Nullable ColorSpace getColorSpace() { + if (mColorSpace != ColorSpaceProfiles.UNSPECIFIED) { + return ColorSpace.get(ColorSpace.Named.values()[mColorSpace]); + } else { + return null; + } + } } diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index fbec518e4a29..3950c25675d8 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -42,7 +42,7 @@ import java.util.ArrayList; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.MessageQueue_host") + "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host") public final class MessageQueue { private static final String TAG = "MessageQueue"; private static final boolean DEBUG = false; diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 8e860c35388d..ccfb6326d941 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -233,7 +233,8 @@ import java.util.function.IntFunction; * {@link #readSparseArray(ClassLoader, Class)}. */ @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.Parcel_host") +@RavenwoodNativeSubstitutionClass( + "com.android.platform.test.ravenwood.nativesubstitution.Parcel_host") public final class Parcel { private static final boolean DEBUG_RECYCLE = false; diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 6532d5c8784a..17dfdda7dffc 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -75,7 +75,8 @@ import java.nio.ByteOrder; * you to close it when done with it. */ @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.ParcelFileDescriptor_host") +@RavenwoodNativeSubstitutionClass( + "com.android.platform.test.ravenwood.nativesubstitution.ParcelFileDescriptor_host") public class ParcelFileDescriptor implements Parcelable, Closeable { private static final String TAG = "ParcelFileDescriptor"; diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index a818919d184e..0a386913de59 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -56,7 +56,8 @@ import java.util.function.Predicate; */ @SystemApi @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host") +@RavenwoodNativeSubstitutionClass( + "com.android.platform.test.ravenwood.nativesubstitution.SystemProperties_host") public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 89576ed62afe..2b30a2ba2d4e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -163,19 +163,16 @@ public class UserManager { * User type representing a managed profile, which is a profile that is to be managed by a * device policy controller (DPC). * The intended purpose is for work profiles, which are managed by a corporate entity. - * @hide */ - @SystemApi + @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE) public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; /** * User type representing a clone profile. Clone profile is a user profile type used to run * second instance of an otherwise single user App (eg, messengers). Currently only the * {@link android.content.pm.UserInfo#isMain()} user can have a clone profile. - * - * @hide */ - @SystemApi + @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE) public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; @@ -184,10 +181,8 @@ public class UserManager { * as an alternative user-space to install and use sensitive apps. * UI surfaces can adopt an alternative strategy to show apps belonging to this profile, in line * with their sensitive nature. - * @hide */ @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE) - @SystemApi public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE"; /** @@ -1785,7 +1780,11 @@ public class UserManager { /** * Specifies whether the user is allowed to modify default apps in settings. * - * <p>This restriction can be set by device or profile owner. + * <p>A device owner and a profile owner can set this restriction. When it is set by a + * device owner, it applies globally - i.e., modifying of default apps in Settings for all + * users is disallowed. When it is set by a profile owner on the primary user or by a profile + * owner of an organization-owned managed profile on the parent profile, modifying of + * default apps in Settings for the primary user is disallowed. * * <p>The default value is <code>false</code>. * @@ -3259,7 +3258,11 @@ public class UserManager { return isProfile(mUserId); } - private boolean isProfile(@UserIdInt int userId) { + /** + * Returns whether the specified user is a profile. + * @hide + */ + public boolean isProfile(@UserIdInt int userId) { final String profileType = getProfileType(userId); return profileType != null && !profileType.equals(""); } diff --git a/core/java/android/permission/PermissionGroupUsage.java b/core/java/android/permission/PermissionGroupUsage.java index 49b7463533e4..6895d3c67f06 100644 --- a/core/java/android/permission/PermissionGroupUsage.java +++ b/core/java/android/permission/PermissionGroupUsage.java @@ -17,6 +17,7 @@ package android.permission; import android.annotation.CurrentTimeMillisLong; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -26,8 +27,8 @@ import com.android.internal.util.DataClass; /** * Represents the usage of a permission group by an app. Supports package name, user, permission - * group, whether or not the access is running or recent, whether the access is tied to a phone - * call, and an optional special attribution tag, label and proxy label. + * group, persistent device Id, whether or not the access is running or recent, whether the access + * is tied to a phone call, and an optional special attribution tag, label and proxy label. * * @hide */ @@ -48,6 +49,7 @@ public final class PermissionGroupUsage implements Parcelable { private final @Nullable CharSequence mAttributionTag; private final @Nullable CharSequence mAttributionLabel; private final @Nullable CharSequence mProxyLabel; + private final @NonNull String mPersistentDeviceId; @@ -79,7 +81,8 @@ public final class PermissionGroupUsage implements Parcelable { boolean phoneCall, @Nullable CharSequence attributionTag, @Nullable CharSequence attributionLabel, - @Nullable CharSequence proxyLabel) { + @Nullable CharSequence proxyLabel, + @NonNull String persistentDeviceId) { this.mPackageName = packageName; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPackageName); @@ -93,6 +96,9 @@ public final class PermissionGroupUsage implements Parcelable { this.mAttributionTag = attributionTag; this.mAttributionLabel = attributionLabel; this.mProxyLabel = proxyLabel; + this.mPersistentDeviceId = persistentDeviceId; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPersistentDeviceId); // onConstructed(); // You can define this method to get a callback } @@ -170,6 +176,12 @@ public final class PermissionGroupUsage implements Parcelable { return mProxyLabel; } + @DataClass.Generated.Member + @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) + public @NonNull String getPersistentDeviceId() { + return mPersistentDeviceId; + } + @Override @DataClass.Generated.Member public String toString() { @@ -185,7 +197,8 @@ public final class PermissionGroupUsage implements Parcelable { "phoneCall = " + mPhoneCall + ", " + "attributionTag = " + mAttributionTag + ", " + "attributionLabel = " + mAttributionLabel + ", " + - "proxyLabel = " + mProxyLabel + + "proxyLabel = " + mProxyLabel + ", " + + "persistentDeviceId = " + mPersistentDeviceId + " }"; } @@ -210,7 +223,8 @@ public final class PermissionGroupUsage implements Parcelable { && mPhoneCall == that.mPhoneCall && java.util.Objects.equals(mAttributionTag, that.mAttributionTag) && java.util.Objects.equals(mAttributionLabel, that.mAttributionLabel) - && java.util.Objects.equals(mProxyLabel, that.mProxyLabel); + && java.util.Objects.equals(mProxyLabel, that.mProxyLabel) + && java.util.Objects.equals(mPersistentDeviceId, that.mPersistentDeviceId); } @Override @@ -229,6 +243,7 @@ public final class PermissionGroupUsage implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mAttributionTag); _hash = 31 * _hash + java.util.Objects.hashCode(mAttributionLabel); _hash = 31 * _hash + java.util.Objects.hashCode(mProxyLabel); + _hash = 31 * _hash + java.util.Objects.hashCode(mPersistentDeviceId); return _hash; } @@ -252,6 +267,7 @@ public final class PermissionGroupUsage implements Parcelable { if (mAttributionTag != null) dest.writeCharSequence(mAttributionTag); if (mAttributionLabel != null) dest.writeCharSequence(mAttributionLabel); if (mProxyLabel != null) dest.writeCharSequence(mProxyLabel); + dest.writeString(mPersistentDeviceId); } @Override @@ -275,6 +291,7 @@ public final class PermissionGroupUsage implements Parcelable { CharSequence attributionTag = (flg & 0x40) == 0 ? null : (CharSequence) in.readCharSequence(); CharSequence attributionLabel = (flg & 0x80) == 0 ? null : (CharSequence) in.readCharSequence(); CharSequence proxyLabel = (flg & 0x100) == 0 ? null : (CharSequence) in.readCharSequence(); + String persistentDeviceId = in.readString(); this.mPackageName = packageName; com.android.internal.util.AnnotationValidations.validate( @@ -289,6 +306,9 @@ public final class PermissionGroupUsage implements Parcelable { this.mAttributionTag = attributionTag; this.mAttributionLabel = attributionLabel; this.mProxyLabel = proxyLabel; + this.mPersistentDeviceId = persistentDeviceId; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPersistentDeviceId); // onConstructed(); // You can define this method to get a callback } @@ -308,10 +328,10 @@ public final class PermissionGroupUsage implements Parcelable { }; @DataClass.Generated( - time = 1645067417023L, + time = 1706285211875L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/permission/PermissionGroupUsage.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final int mUid\nprivate final long mLastAccessTimeMillis\nprivate final @android.annotation.NonNull java.lang.String mPermissionGroupName\nprivate final boolean mActive\nprivate final boolean mPhoneCall\nprivate final @android.annotation.Nullable java.lang.CharSequence mAttributionTag\nprivate final @android.annotation.Nullable java.lang.CharSequence mAttributionLabel\nprivate final @android.annotation.Nullable java.lang.CharSequence mProxyLabel\nclass PermissionGroupUsage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genEqualsHashCode=true, genToString=true)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final int mUid\nprivate final long mLastAccessTimeMillis\nprivate final @android.annotation.NonNull java.lang.String mPermissionGroupName\nprivate final boolean mActive\nprivate final boolean mPhoneCall\nprivate final @android.annotation.Nullable java.lang.CharSequence mAttributionTag\nprivate final @android.annotation.Nullable java.lang.CharSequence mAttributionLabel\nprivate final @android.annotation.Nullable java.lang.CharSequence mProxyLabel\nprivate final @android.annotation.NonNull java.lang.String mPersistentDeviceId\nclass PermissionGroupUsage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genEqualsHashCode=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index e6b8102764eb..fd52c769e408 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1329,7 +1329,9 @@ public final class PermissionManager { public List<PermissionGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) { // Lazily initialize the usage helper initializeUsageHelper(); - return mUsageHelper.getOpUsageData(micMuted); + boolean includeMicrophoneUsage = !micMuted; + return mUsageHelper.getOpUsageDataByDevice(includeMicrophoneUsage, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); } /** diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index 1f798baf1bd6..460b4dd9b86c 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -41,6 +41,8 @@ import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_AC import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.companion.virtual.VirtualDevice; +import android.companion.virtual.VirtualDeviceManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.Attribution; @@ -52,10 +54,12 @@ import android.location.LocationManager; import android.media.AudioManager; import android.os.Process; import android.os.UserHandle; +import android.permission.flags.Flags; import android.provider.DeviceConfig; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -75,6 +79,8 @@ import java.util.Objects; public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedListener, AppOpsManager.OnOpStartedListener { + private static final String LOG_TAG = PermissionUsageHelper.class.getName(); + /** * Whether to show the mic and camera icons. */ @@ -159,6 +165,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis private ArrayMap<UserHandle, Context> mUserContexts; private PackageManager mPkgManager; private AppOpsManager mAppOpsManager; + private VirtualDeviceManager mVirtualDeviceManager; @GuardedBy("mAttributionChains") private final ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains = new ArrayMap<>(); @@ -172,6 +179,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis mContext = context; mPkgManager = context.getPackageManager(); mAppOpsManager = context.getSystemService(AppOpsManager.class); + mVirtualDeviceManager = context.getSystemService(VirtualDeviceManager.class); mUserContexts = new ArrayMap<>(); mUserContexts.put(Process.myUserHandle(), mContext); // TODO ntmyren: make this listen for flag enable/disable changes @@ -280,9 +288,11 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis } /** - * @see PermissionManager.getIndicatorAppOpUsageData + * Return Op usage for CAMERA, LOCATION AND MICROPHONE for all packages for a device. + * The returned data is to power privacy indicator. */ - public @NonNull List<PermissionGroupUsage> getOpUsageData(boolean isMicMuted) { + public @NonNull List<PermissionGroupUsage> getOpUsageDataByDevice( + boolean includeMicrophoneUsage, String deviceId) { List<PermissionGroupUsage> usages = new ArrayList<>(); if (!shouldShowIndicators()) { @@ -293,11 +303,11 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis if (shouldShowLocationIndicator()) { ops.addAll(LOCATION_OPS); } - if (!isMicMuted) { + if (includeMicrophoneUsage) { ops.addAll(MIC_OPS); } - Map<String, List<OpUsage>> rawUsages = getOpUsages(ops); + Map<String, List<OpUsage>> rawUsages = getOpUsagesByDevice(ops, deviceId); ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet()); @@ -349,13 +359,40 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis new PermissionGroupUsage(usage.packageName, usage.uid, usage.lastAccessTime, permGroup, usage.isRunning, isPhone, usage.attributionTag, attributionLabel, - usagesWithLabels.valueAt(usageNum))); + usagesWithLabels.valueAt(usageNum), + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)); } } return usages; } + /** + * Return Op usage for CAMERA, LOCATION AND MICROPHONE for all packages and all connected + * devices. + * The returned data is to power privacy indicator. + */ + public @NonNull List<PermissionGroupUsage> getOpUsageDataForAllDevices( + boolean includeMicrophoneUsage) { + List<PermissionGroupUsage> allUsages = new ArrayList<>(); + List<VirtualDevice> virtualDevices = mVirtualDeviceManager.getVirtualDevices(); + ArraySet<String> persistentDeviceIds = new ArraySet<>(); + + for (int num = 0; num < virtualDevices.size(); num++) { + persistentDeviceIds.add(virtualDevices.get(num).getPersistentDeviceId()); + } + persistentDeviceIds.add(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + + for (int index = 0; index < persistentDeviceIds.size(); index++) { + allUsages.addAll( + getOpUsageDataByDevice(includeMicrophoneUsage, + persistentDeviceIds.valueAt(index))); + } + + return allUsages; + } + + private void updateSubattributionLabelsMap(List<OpUsage> usages, ArrayMap<String, Map<String, String>> subAttributionLabelsMap) { if (usages == null || usages.isEmpty()) { @@ -443,12 +480,24 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis * running/recent info, if the usage is a phone call, per permission group. * * @param opNames a list of op names to get usage for + * @param deviceId which device to get op usage for * @return A map of permission group -> list of usages that are recent or running */ - private Map<String, List<OpUsage>> getOpUsages(List<String> opNames) { + private Map<String, List<OpUsage>> getOpUsagesByDevice(List<String> opNames, String deviceId) { List<AppOpsManager.PackageOps> ops; try { - ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()])); + if (Flags.deviceAwarePermissionApisEnabled()) { + ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()]), + deviceId); + } else if (!Objects.equals(deviceId, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) { + Slog.w(LOG_TAG, + "device_aware_permission_apis_enabled flag not enabled when deviceId is " + + "not default"); + return Collections.emptyMap(); + } else { + ops = mAppOpsManager.getPackagesForOps(opNames.toArray(new String[opNames.size()])); + } } catch (NullPointerException e) { // older builds might not support all the app-ops requested return Collections.emptyMap(); diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 9d7fb7018d52..9218cb8f497d 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -96,9 +96,37 @@ flag { } flag { + name: "sensitive_notification_app_protection" + namespace: "permissions" + description: "This flag controls the sensitive notification app protections while screen sharing" + bug: "312784351" + # Referenced in WM where WM starts before DeviceConfig + is_fixed_read_only: true +} + +flag { name: "device_aware_permissions_enabled" is_fixed_read_only: true namespace: "permissions" description: "When the flag is off no permissions can be device aware" bug: "274852670" -}
\ No newline at end of file +} + +flag { + name: "get_emergency_role_holder_api_enabled" + is_fixed_read_only: true + namespace: "permissions" + description: "Enables the getEmergencyRoleHolder API." + bug: "323157319" +} + +flag { + name: "new_permission_gid_enabled" + is_fixed_read_only: true + namespace: "permissions" + description: "Enable new permission GID implementation" + bug: "325137277" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 786d768bc55b..aa47d3a5c2af 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -673,6 +673,10 @@ public final class ZenPolicy implements Parcelable { mZenPolicy.mPriorityMessages = PEOPLE_TYPE_NONE; mZenPolicy.mPriorityCalls = PEOPLE_TYPE_NONE; mZenPolicy.mConversationSenders = CONVERSATION_SENDERS_NONE; + + if (Flags.modesApi()) { + mZenPolicy.mAllowChannels = CHANNEL_POLICY_NONE; + } return this; } diff --git a/core/java/android/service/ondeviceintelligence/OWNERS b/core/java/android/service/ondeviceintelligence/OWNERS new file mode 100644 index 000000000000..09774f78d712 --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/OWNERS @@ -0,0 +1 @@ +file:/core/java/android/app/ondeviceintelligence/OWNERS diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 2028c4057c01..9db8aa167498 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -273,7 +273,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback outerwidth /* ellipsizedWidth */, null /* ellipsize */, 1 /* maxLines */, BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */, null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, - null); + false /* shiftDrawingOffsetForStartOverhang */, null); mEllipsizedWidth = outerwidth; mEllipsizedStart = 0; @@ -346,7 +346,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback ellipsizedWidth, ellipsize, 1 /* maxLines */, BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */, null /* rightIndents */, JUSTIFICATION_MODE_NONE, - LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, null); + LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, + false /* shiftDrawingOffsetForStartOverhang */, null); } /** @hide */ @@ -363,12 +364,14 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback TextUtils.TruncateAt ellipsize, Metrics metrics, boolean useBoundsForWidth, + boolean shiftDrawingOffsetForStartOverhang, @Nullable Paint.FontMetrics minimumFontMetrics) { this(text, paint, width, align, TextDirectionHeuristics.LTR, spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth, ellipsize, 1 /* maxLines */, Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE, null, null, Layout.JUSTIFICATION_MODE_NONE, - LineBreakConfig.NONE, metrics, useBoundsForWidth, minimumFontMetrics); + LineBreakConfig.NONE, metrics, useBoundsForWidth, + shiftDrawingOffsetForStartOverhang, minimumFontMetrics); } /* package */ BoringLayout( @@ -392,12 +395,14 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback LineBreakConfig lineBreakConfig, Metrics metrics, boolean useBoundsForWidth, + boolean shiftDrawingOffsetForStartOverhang, @Nullable Paint.FontMetrics minimumFontMetrics) { super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy, hyphenationFrequency, leftIndents, rightIndents, justificationMode, - lineBreakConfig, useBoundsForWidth, minimumFontMetrics); + lineBreakConfig, useBoundsForWidth, shiftDrawingOffsetForStartOverhang, + minimumFontMetrics); boolean trust; @@ -712,7 +717,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback int cursorOffset) { if (mDirect != null && highlight == null) { float leftShift = 0; - if (getUseBoundsForWidth()) { + if (getUseBoundsForWidth() && getShiftDrawingOffsetForStartOverhang()) { RectF drawingRect = computeDrawingBoundingBox(); if (drawingRect.left < 0) { leftShift = -drawingRect.left; diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 928604983b70..cce4f7bf7b1f 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -25,6 +25,7 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Paint; import android.graphics.Rect; @@ -317,6 +318,35 @@ public class DynamicLayout extends Layout { } /** + * Set true for shifting the drawing x offset for showing overhang at the start position. + * + * This flag is ignored if the {@link #getUseBoundsForWidth()} is false. + * + * If this value is false, the Layout draws text from the zero even if there is a glyph + * stroke in a region where the x coordinate is negative. + * + * If this value is true, the Layout draws text with shifting the x coordinate of the + * drawing bounding box. + * + * This value is false by default. + * + * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for + * showing the stroke that is in the region where + * the x coordinate is negative. + * @see #setUseBoundsForWidth(boolean) + * @see #getUseBoundsForWidth() + */ + @NonNull + // The corresponding getter is getShiftDrawingOffsetForStartOverhang() + @SuppressLint("MissingGetterMatchingBuilder") + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) + public Builder setShiftDrawingOffsetForStartOverhang( + boolean shiftDrawingOffsetForStartOverhang) { + mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang; + return this; + } + + /** * Set the minimum font metrics used for line spacing. * * <p> @@ -386,6 +416,7 @@ public class DynamicLayout extends Layout { private int mEllipsizedWidth; private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE; private boolean mUseBoundsForWidth; + private boolean mShiftDrawingOffsetForStartOverhang; private @Nullable Paint.FontMetrics mMinimumFontMetrics; private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); @@ -462,7 +493,8 @@ public class DynamicLayout extends Layout { false /* fallbackLineSpacing */, ellipsizedWidth, ellipsize, Integer.MAX_VALUE /* maxLines */, breakStrategy, hyphenationFrequency, null /* leftIndents */, null /* rightIndents */, justificationMode, - lineBreakConfig, false /* useBoundsForWidth */, null /* minimumFontMetrics */); + lineBreakConfig, false /* useBoundsForWidth */, false, + null /* minimumFontMetrics */); final Builder b = Builder.obtain(base, paint, width) .setAlignment(align) @@ -488,7 +520,8 @@ public class DynamicLayout extends Layout { b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize, Integer.MAX_VALUE /* maxLines */, b.mBreakStrategy, b.mHyphenationFrequency, null /* leftIndents */, null /* rightIndents */, b.mJustificationMode, - b.mLineBreakConfig, b.mUseBoundsForWidth, b.mMinimumFontMetrics); + b.mLineBreakConfig, b.mUseBoundsForWidth, b.mShiftDrawingOffsetForStartOverhang, + b.mMinimumFontMetrics); mDisplay = b.mDisplay; mIncludePad = b.mIncludePad; @@ -516,6 +549,7 @@ public class DynamicLayout extends Layout { mBase = b.mBase; mFallbackLineSpacing = b.mFallbackLineSpacing; mUseBoundsForWidth = b.mUseBoundsForWidth; + mShiftDrawingOffsetForStartOverhang = b.mShiftDrawingOffsetForStartOverhang; mMinimumFontMetrics = b.mMinimumFontMetrics; if (b.mEllipsize != null) { mInts = new PackedIntVector(COLUMNS_ELLIPSIZE); @@ -713,6 +747,7 @@ public class DynamicLayout extends Layout { .setAddLastLineLineSpacing(!islast) .setIncludePad(false) .setUseBoundsForWidth(mUseBoundsForWidth) + .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang) .setMinimumFontMetrics(mMinimumFontMetrics) .setCalculateBounds(true); @@ -1392,6 +1427,7 @@ public class DynamicLayout extends Layout { private Rect mTempRect = new Rect(); private boolean mUseBoundsForWidth; + private boolean mShiftDrawingOffsetForStartOverhang; @Nullable Paint.FontMetrics mMinimumFontMetrics; @UnsupportedAppUsage diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index e5d199ad8e46..8e52af3fb57b 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -44,6 +44,7 @@ import android.text.style.LineBackgroundSpan; import android.text.style.ParagraphStyle; import android.text.style.ReplacementSpan; import android.text.style.TabStopSpan; +import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -299,7 +300,7 @@ public abstract class Layout { this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR, spacingMult, spacingAdd, false, false, 0, null, Integer.MAX_VALUE, BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null, null, - JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, null); + JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, false, null); } /** @@ -349,6 +350,7 @@ public abstract class Layout { int justificationMode, LineBreakConfig lineBreakConfig, boolean useBoundsForWidth, + boolean shiftDrawingOffsetForStartOverhang, Paint.FontMetrics minimumFontMetrics ) { @@ -384,6 +386,7 @@ public abstract class Layout { mJustificationMode = justificationMode; mLineBreakConfig = lineBreakConfig; mUseBoundsForWidth = useBoundsForWidth; + mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang; mMinimumFontMetrics = minimumFontMetrics; } @@ -465,7 +468,7 @@ public abstract class Layout { @Nullable Paint selectionPaint, int cursorOffsetVertical) { float leftShift = 0; - if (mUseBoundsForWidth) { + if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) { RectF drawingRect = computeDrawingBoundingBox(); if (drawingRect.left < 0) { leftShift = -drawingRect.left; @@ -3414,6 +3417,7 @@ public abstract class Layout { private int mJustificationMode; private LineBreakConfig mLineBreakConfig; private boolean mUseBoundsForWidth; + private boolean mShiftDrawingOffsetForStartOverhang; private @Nullable Paint.FontMetrics mMinimumFontMetrics; private TextLine.LineInfo mLineInfo = null; @@ -3873,6 +3877,35 @@ public abstract class Layout { } /** + * Set true for shifting the drawing x offset for showing overhang at the start position. + * + * This flag is ignored if the {@link #getUseBoundsForWidth()} is false. + * + * If this value is false, the Layout draws text from the zero even if there is a glyph + * stroke in a region where the x coordinate is negative. + * + * If this value is true, the Layout draws text with shifting the x coordinate of the + * drawing bounding box. + * + * This value is false by default. + * + * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for + * showing the stroke that is in the region where + * the x coordinate is negative. + * @see #setUseBoundsForWidth(boolean) + * @see #getUseBoundsForWidth() + */ + @NonNull + // The corresponding getter is getShiftDrawingOffsetForStartOverhang() + @SuppressLint("MissingGetterMatchingBuilder") + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) + public Builder setShiftDrawingOffsetForStartOverhang( + boolean shiftDrawingOffsetForStartOverhang) { + mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang; + return this; + } + + /** * Set the minimum font metrics used for line spacing. * * <p> @@ -3948,6 +3981,7 @@ public abstract class Layout { .setJustificationMode(mJustificationMode) .setLineBreakConfig(mLineBreakConfig) .setUseBoundsForWidth(mUseBoundsForWidth) + .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang) .build(); } else { return new BoringLayout( @@ -3955,7 +3989,7 @@ public abstract class Layout { mIncludePad, mFallbackLineSpacing, mEllipsizedWidth, mEllipsize, mMaxLines, mBreakStrategy, mHyphenationFrequency, mLeftIndents, mRightIndents, mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth, - mMinimumFontMetrics); + mShiftDrawingOffsetForStartOverhang, mMinimumFontMetrics); } } @@ -3980,6 +4014,7 @@ public abstract class Layout { private int mJustificationMode = JUSTIFICATION_MODE_NONE; private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE; private boolean mUseBoundsForWidth; + private boolean mShiftDrawingOffsetForStartOverhang; private Paint.FontMetrics mMinimumFontMetrics; } @@ -4294,6 +4329,20 @@ public abstract class Layout { } /** + * Returns true if shifting drawing offset for start overhang. + * + * @return True if shifting drawing offset for start overhang. + * @see android.widget.TextView#setShiftDrawingOffsetForStartOverhang(boolean) + * @see TextView#getShiftDrawingOffsetForStartOverhang() + * @see StaticLayout.Builder#setShiftDrawingOffsetForStartOverhang(boolean) + * @see DynamicLayout.Builder#setShiftDrawingOffsetForStartOverhang(boolean) + */ + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) + public boolean getShiftDrawingOffsetForStartOverhang() { + return mShiftDrawingOffsetForStartOverhang; + } + + /** * Get the minimum font metrics used for line spacing. * * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics) diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 5986238d3035..3dd3a9ea8baf 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -24,6 +24,7 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Paint; import android.graphics.RectF; @@ -454,6 +455,35 @@ public class StaticLayout extends Layout { } /** + * Set true for shifting the drawing x offset for showing overhang at the start position. + * + * This flag is ignored if the {@link #getUseBoundsForWidth()} is false. + * + * If this value is false, the Layout draws text from the zero even if there is a glyph + * stroke in a region where the x coordinate is negative. + * + * If this value is true, the Layout draws text with shifting the x coordinate of the + * drawing bounding box. + * + * This value is false by default. + * + * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for + * showing the stroke that is in the region where + * the x coordinate is negative. + * @see #setUseBoundsForWidth(boolean) + * @see #getUseBoundsForWidth() + */ + @NonNull + // The corresponding getter is getShiftDrawingOffsetForStartOverhang() + @SuppressLint("MissingGetterMatchingBuilder") + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) + public Builder setShiftDrawingOffsetForStartOverhang( + boolean shiftDrawingOffsetForStartOverhang) { + mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang; + return this; + } + + /** * Internal API that tells underlying line breaker that calculating bounding boxes even if * the line break is performed with advances. This is useful for DynamicLayout internal * implementation because it uses bounding box as well as advances. @@ -566,6 +596,7 @@ public class StaticLayout extends Layout { private boolean mAddLastLineLineSpacing; private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE; private boolean mUseBoundsForWidth; + private boolean mShiftDrawingOffsetForStartOverhang; private boolean mCalculateBounds; @Nullable private Paint.FontMetrics mMinimumFontMetrics; @@ -599,6 +630,7 @@ public class StaticLayout extends Layout { JUSTIFICATION_MODE_NONE, null, // lineBreakConfig, false, // useBoundsForWidth + false, // shiftDrawingOffsetForStartOverhang null // minimumFontMetrics ); @@ -677,7 +709,7 @@ public class StaticLayout extends Layout { b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize, b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents, b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth, - b.mMinimumFontMetrics); + b.mShiftDrawingOffsetForStartOverhang, b.mMinimumFontMetrics); mColumns = columnSize; if (b.mEllipsize != null) { diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index d2c5975ea356..0a73fd1689c3 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -50,7 +50,7 @@ import java.util.regex.Pattern; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.EventLog_host") + "com.android.platform.test.ravenwood.nativesubstitution.EventLog_host") public class EventLog { /** @hide */ public EventLog() {} diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 31576c5c74fc..b33214dffb7e 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -73,7 +73,7 @@ import java.net.UnknownHostException; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.Log_host") + "com.android.platform.test.ravenwood.nativesubstitution.Log_host") public final class Log { /** @hide */ @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE}) diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index 9413f5c0868e..5ec415910d63 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -202,38 +202,4 @@ public interface AttachedSurfaceControl { throw new UnsupportedOperationException("The getInputTransferToken needs to be " + "implemented before making this call."); } - - /** - * Transfer the currently in progress touch gesture from the host to the requested - * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the - * SurfaceControlViewHost was created with the current host's inputToken. - * <p> - * When the touch is transferred, the window currently receiving touch gets an ACTION_CANCEL - * and does not receive any further input events for this gesture. - * <p> - * The transferred-to window receives an ACTION_DOWN event and then the remainder of the - * input events for this gesture. It does not receive any of the previous events of this gesture - * that the originating window received. - * <p> - * The "transferTouch" API only works for the current gesture. When a new gesture arrives, - * input dispatcher will do a new round of hit testing. So, if the "host" window is still the - * first thing that's being touched, then it will receive the new gesture again. It will - * again be up to the host to transfer this new gesture to the embedded. - * <p> - * Once the transferred-to window receives the gesture, it can choose to give up this gesture - * and send it to another window that it's linked to (it can't be an arbitrary window for - * security reasons) using the same transferTouch API. Only the window currently receiving - * touch is allowed to transfer the gesture. - * - * @param surfacePackage The SurfacePackage to transfer the gesture to. - * @return Whether the touch stream was transferred. - * @see SurfaceControlViewHost#transferTouchGestureToHost() for the reverse to transfer touch - * gesture from the embedded to the host. - */ - @FlaggedApi(Flags.FLAG_TRANSFER_GESTURE_TO_EMBEDDED) - default boolean transferHostTouchGestureToEmbedded( - @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { - throw new UnsupportedOperationException( - "transferHostTouchGestureToEmbedded is unimplemented"); - } } diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index eb289204e481..676b903472c5 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -23,6 +23,9 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; +import android.text.TextUtils; +import android.view.inputmethod.ConnectionlessHandwritingCallback; +import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -225,15 +228,7 @@ public class HandwritingInitiator { } startHandwriting(candidateView); } else if (candidateView.getHandwritingDelegatorCallback() != null) { - String delegatePackageName = - candidateView.getAllowedHandwritingDelegatePackageName(); - if (delegatePackageName == null) { - delegatePackageName = candidateView.getContext().getOpPackageName(); - } - mImm.prepareStylusHandwritingDelegation( - candidateView, delegatePackageName); - candidateView.getHandwritingDelegatorCallback().run(); - mState.mHasPreparedHandwritingDelegation = true; + prepareDelegation(candidateView); } else { if (!mInitiateWithoutConnection) { mState.mPendingConnectedView = new WeakReference<>(candidateView); @@ -375,7 +370,7 @@ public class HandwritingInitiator { // A new view just gain focus. By default, we should show hover icon for it. mShowHoverIconForConnectedView = true; } - if (!fromTouchEvent) { + if (!fromTouchEvent && view.isHandwritingDelegate()) { tryAcceptStylusHandwritingDelegation(view); } return true; @@ -393,15 +388,33 @@ public class HandwritingInitiator { } } + private void prepareDelegation(View view) { + String delegatePackageName = view.getAllowedHandwritingDelegatePackageName(); + if (delegatePackageName == null) { + delegatePackageName = view.getContext().getOpPackageName(); + } + if (mImm.isConnectionlessStylusHandwritingAvailable()) { + // No other view should have focus during the connectionless handwriting session, as + // this could cause user confusion about the input target for the session. + view.getViewRootImpl().getView().clearFocus(); + mImm.startConnectionlessStylusHandwritingForDelegation( + view, getCursorAnchorInfoForConnectionless(view), delegatePackageName, + view::post, new DelegationCallback(view, delegatePackageName)); + mState.mHasInitiatedHandwriting = true; + mState.mShouldInitHandwriting = false; + } else { + mImm.prepareStylusHandwritingDelegation(view, delegatePackageName); + view.getHandwritingDelegatorCallback().run(); + mState.mHasPreparedHandwritingDelegation = true; + } + } + /** * Starts a stylus handwriting session for the delegate view, if {@link * InputMethodManager#prepareStylusHandwritingDelegation} was previously called. */ @VisibleForTesting public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) { - if (!view.isHandwritingDelegate() || (mState != null && mState.mHasInitiatedHandwriting)) { - return false; - } String delegatorPackageName = view.getAllowedHandwritingDelegatorPackageName(); if (delegatorPackageName == null) { @@ -807,6 +820,59 @@ public class HandwritingInitiator { && view.shouldInitiateHandwriting(); } + private CursorAnchorInfo getCursorAnchorInfoForConnectionless(View view) { + CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); + // Fake editor views will usually display hint text. The hint text view can be used to + // populate the CursorAnchorInfo. + TextView textView = findFirstTextViewDescendent(view); + if (textView != null) { + textView.getCursorAnchorInfo(0, builder, mTempMatrix); + if (textView.getSelectionStart() < 0) { + // Insertion marker location is not populated if selection start is negative, so + // make a best guess. + float bottom = textView.getHeight() - textView.getExtendedPaddingBottom(); + builder.setInsertionMarkerLocation( + /* horizontalPosition= */ textView.getCompoundPaddingStart(), + /* lineTop= */ textView.getExtendedPaddingTop(), + /* lineBaseline= */ bottom, + /* lineBottom= */ bottom, + /* flags= */ 0); + } + } else { + // If there is no TextView descendent, just populate the insertion marker with the start + // edge of the view. + mTempMatrix.reset(); + view.transformMatrixToGlobal(mTempMatrix); + builder.setMatrix(mTempMatrix); + builder.setInsertionMarkerLocation( + /* horizontalPosition= */ view.isLayoutRtl() ? view.getWidth() : 0, + /* lineTop= */ 0, + /* lineBaseline= */ view.getHeight(), + /* lineBottom= */ view.getHeight(), + /* flags= */ 0); + } + return builder.build(); + } + + @Nullable + private static TextView findFirstTextViewDescendent(View view) { + if (view instanceof ViewGroup viewGroup) { + TextView textView; + for (int i = 0; i < viewGroup.getChildCount(); ++i) { + View child = viewGroup.getChildAt(i); + textView = (child instanceof TextView tv) + ? tv : findFirstTextViewDescendent(viewGroup.getChildAt(i)); + if (textView != null + && textView.isAggregatedVisible() + && (!TextUtils.isEmpty(textView.getText()) + || !TextUtils.isEmpty(textView.getHint()))) { + return textView; + } + } + } + return null; + } + /** * A class used to track the handwriting areas set by the Views. * @@ -931,4 +997,35 @@ public class HandwritingInitiator { return true; } } + + private class DelegationCallback implements ConnectionlessHandwritingCallback { + private final View mView; + private final String mDelegatePackageName; + + private DelegationCallback(View view, String delegatePackageName) { + mView = view; + mDelegatePackageName = delegatePackageName; + } + + @Override + public void onResult(@NonNull CharSequence text) { + mView.getHandwritingDelegatorCallback().run(); + } + + @Override + public void onError(int errorCode) { + switch (errorCode) { + case CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED: + mView.getHandwritingDelegatorCallback().run(); + break; + case CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED: + // Fall back to the old delegation flow + mImm.prepareStylusHandwritingDelegation(mView, mDelegatePackageName); + mView.getHandwritingDelegatorCallback().run(); + mState.mHasInitiatedHandwriting = false; + mState.mHasPreparedHandwritingDelegation = true; + break; + } + } + } } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 29cc8594deec..c475f6babbf1 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -1098,4 +1098,7 @@ interface IWindowManager * (ie. not handled by any window which can handle the drag). */ void setUnhandledDragListener(IUnhandledDragListener listener); + + boolean transferTouchGesture(in InputTransferToken transferFromToken, + in InputTransferToken transferToToken); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 55e49f8f3ad9..d68a47c54d4b 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -370,11 +370,6 @@ interface IWindowSession { */ boolean cancelDraw(IWindow window); - boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow); - - boolean transferHostTouchGestureToEmbedded(IWindow hostWindow, - in InputTransferToken transferTouchToken); - /** * Moves the focus to the adjacent window if there is one in the given direction. This can only * move the focus to the window in the same leaf task. diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 1dd9cbb76a9f..06a923afffbb 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -293,12 +293,12 @@ public class SurfaceControlViewHost { /** * Gets an {@link InputTransferToken} which can be used to request focus on the embedded * surface or to transfer touch gesture to the embedded surface. - * @return the InputTransferToken associated with {@link SurfacePackage} - * @see AttachedSurfaceControl#transferHostTouchGestureToEmbedded(SurfacePackage) - * - * @hide + * @return the InputTransferToken associated with {@link SurfacePackage} or {@code null} if + * the embedded hasn't set up its view or doesn't have input. + * @see WindowManager#transferTouchGesture(InputTransferToken, InputTransferToken) */ @Nullable + @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) public InputTransferToken getInputTransferToken() { return mInputTransferToken; } @@ -577,9 +577,9 @@ public class SurfaceControlViewHost { } /** - * Transfer the currently in progress touch gesture to the parent - * (if any) of this SurfaceControlViewHost. This requires that the - * SurfaceControlViewHost was created with an associated hostInputToken. + * Transfer the currently in progress touch gesture to the parent (if any) of this + * SurfaceControlViewHost. This requires that the SurfaceControlViewHost was created with an + * associated host {@link InputTransferToken}. * * @return Whether the touch stream was transferred. */ @@ -587,13 +587,14 @@ public class SurfaceControlViewHost { if (mViewRoot == null) { return false; } - - final IWindowSession realWm = WindowManagerGlobal.getWindowSession(); - try { - return realWm.transferEmbeddedTouchFocusToHost(mViewRoot.mWindow); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + final WindowManager wm = + (WindowManager) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE); + InputTransferToken embeddedToken = getInputTransferToken(); + InputTransferToken hostToken = mWm.mHostInputTransferToken; + if (embeddedToken == null || hostToken == null) { + Log.w(TAG, "Failed to transferTouchGestureToHost. Host or embedded token is null"); + return false; } - return false; + return wm.transferTouchGesture(getInputTransferToken(), mWm.mHostInputTransferToken); } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 25ade62078fd..73b6ed650394 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5638,7 +5638,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_HIGH; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) - public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0; + public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) @@ -33457,7 +33457,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) { return FRAME_RATE_CATEGORY_NORMAL; } - return mLastFrameRateCategory; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 657c8e644f3a..9474a698c1a9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -27,6 +27,8 @@ import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; +import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -1031,6 +1033,15 @@ public final class ViewRootImpl implements ViewParent, private static final int FRAME_RATE_SETTING_REEVALUATE_TIME = 100; /* + * The variables below are used to update frame rate category + */ + private static final int FRAME_RATE_CATEGORY_COUNT = 5; + private int mFrameRateCategoryHighCount = 0; + private int mFrameRateCategoryHighHintCount = 0; + private int mFrameRateCategoryNormalCount = 0; + private int mFrameRateCategoryLowCount = 0; + + /* * the variables below are used to determine whther a dVRR feature should be enabled */ @@ -4084,7 +4095,14 @@ public final class ViewRootImpl implements ViewParent, // when the values are applicable. setPreferredFrameRate(mPreferredFrameRate); setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0 + ? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount; + mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0 + ? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount; + mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0 + ? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount; mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + mPreferredFrameRate = -1; } private void createSyncIfNeeded() { @@ -12296,7 +12314,8 @@ public final class ViewRootImpl implements ViewParent, } try { - if (mLastPreferredFrameRate != preferredFrameRate) { + if (mLastPreferredFrameRate != preferredFrameRate + && preferredFrameRate >= 0) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin( Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate " @@ -12346,7 +12365,25 @@ public final class ViewRootImpl implements ViewParent, */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) public void votePreferredFrameRateCategory(int frameRateCategory) { - mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory); + if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH) { + mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT; + } else if (frameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) { + mFrameRateCategoryHighHintCount = FRAME_RATE_CATEGORY_COUNT; + } else if (frameRateCategory == FRAME_RATE_CATEGORY_NORMAL) { + mFrameRateCategoryNormalCount = FRAME_RATE_CATEGORY_COUNT; + } else if (frameRateCategory == FRAME_RATE_CATEGORY_LOW) { + mFrameRateCategoryLowCount = FRAME_RATE_CATEGORY_COUNT; + } + + if (mFrameRateCategoryHighCount > 0) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH; + } else if (mFrameRateCategoryHighHintCount > 0) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT; + } else if (mFrameRateCategoryNormalCount > 0) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL; + } else if (mFrameRateCategoryLowCount > 0) { + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW; + } mHasInvalidation = true; } @@ -12368,13 +12405,7 @@ public final class ViewRootImpl implements ViewParent, return; } - if (mPreferredFrameRate == 0) { - mPreferredFrameRate = frameRate; - } else if (frameRate > 60 || mPreferredFrameRate > 60) { - mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate); - } else if (mPreferredFrameRate != frameRate) { - mPreferredFrameRate = 60; - } + mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate); mHasInvalidation = true; mHandler.removeMessages(MSG_FRAME_RATE_SETTING); @@ -12403,7 +12434,7 @@ public final class ViewRootImpl implements ViewParent, */ @VisibleForTesting public float getPreferredFrameRate() { - return mPreferredFrameRate; + return mPreferredFrameRate >= 0 ? mPreferredFrameRate : mLastPreferredFrameRate; } /** @@ -12431,19 +12462,6 @@ public final class ViewRootImpl implements ViewParent, boostTimeOut); } - @Override - public boolean transferHostTouchGestureToEmbedded( - @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { - final IWindowSession realWm = WindowManagerGlobal.getWindowSession(); - try { - return realWm.transferHostTouchGestureToEmbedded(mWindow, - surfacePackage.getInputTransferToken()); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); - } - return false; - } - /** * Set the default back key callback for windowless window, to forward the back key event * to host app. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 0302a0df35c0..e4be016dac7a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1548,6 +1548,48 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"; /** + * Application or Activity level + * {@link android.content.pm.PackageManager.Property PackageManager.Property} to provide any + * preferences for showing all or specific Activities on small cover displays of foldable + * style devices. + * + * <p>The only supported value for the property is {@link #COMPAT_SMALL_COVER_SCREEN_OPT_IN}. + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name="android.window.PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN" + * android:value=1 <!-- COMPAT_COVER_SCREEN_OPT_IN -->/> + * </application> + * </pre> + */ + @FlaggedApi(Flags.FLAG_COVER_DISPLAY_OPT_IN) + String PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN = + "android.window.PROPERTY_COMPAT_ALLOW_SMALL_COVER_SCREEN"; + + /** + * 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. + */ + @CompatSmallScreenPolicy + @FlaggedApi(Flags.FLAG_COVER_DISPLAY_OPT_IN) + int COMPAT_SMALL_COVER_SCREEN_OPT_IN = 1; + + /** + * @hide + */ + @IntDef({ + COMPAT_SMALL_COVER_SCREEN_OPT_IN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface CompatSmallScreenPolicy {} + + + + /** * Request for app's keyboard shortcuts to be retrieved asynchronously. * * @param receiver The callback to be triggered when the result is ready. @@ -6115,12 +6157,15 @@ public interface WindowManager extends ViewManager { * rendering Choreographer. * @param receiver The SurfaceControlInputReceiver that will receive the input * events + * @return Returns the {@link InputTransferToken} that can be used to transfer touch gesture + * to or from other windows. */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) - default void registerBatchedSurfaceControlInputReceiver(int displayId, + @NonNull + default InputTransferToken registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull InputTransferToken hostInputTransferToken, - @NonNull SurfaceControl surfaceControl, - @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { + @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, + @NonNull SurfaceControlInputReceiver receiver) { throw new UnsupportedOperationException( "registerBatchedSurfaceControlInputReceiver is not implemented"); } @@ -6145,9 +6190,12 @@ public interface WindowManager extends ViewManager { * @param looper The looper to use when invoking callbacks. * @param receiver The SurfaceControlInputReceiver that will receive the input * events. + * @return Returns the {@link InputTransferToken} that can be used to transfer touch gesture + * to or from other windows. */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) - default void registerUnbatchedSurfaceControlInputReceiver(int displayId, + @NonNull + default InputTransferToken registerUnbatchedSurfaceControlInputReceiver(int displayId, @NonNull InputTransferToken hostInputTransferToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { @@ -6196,6 +6244,70 @@ public interface WindowManager extends ViewManager { } /** + * Transfer the currently in progress touch gesture from the transferFromToken to the + * transferToToken. + * <p><br> + * This requires that the fromToken and toToken are associated with each other. The association + * can be done different ways, depending on how the embedded window is created. + * <ul> + * <li> + * Creating a {@link SurfaceControlViewHost} and passing the host's + * {@link InputTransferToken} for + * {@link SurfaceControlViewHost#SurfaceControlViewHost(Context, Display, InputTransferToken)}. + * </li> + * <li> + * Registering a SurfaceControl for input and passing the host's token to either + * {@link #registerBatchedSurfaceControlInputReceiver(int, InputTransferToken, SurfaceControl, + * Choreographer, SurfaceControlInputReceiver)} or + * {@link #registerUnbatchedSurfaceControlInputReceiver(int, InputTransferToken, + * SurfaceControl, Looper, SurfaceControlInputReceiver)}. + * </li> + * </ul> + * <p> + * The host is likely to be an {@link AttachedSurfaceControl} so the host token can be + * retrieved via {@link AttachedSurfaceControl#getInputTransferToken()}. + * <p><br> + * Only the window currently receiving touch is allowed to transfer the gesture so if the caller + * attempts to transfer touch gesture from a token that doesn't have touch, it will fail the + * transfer. + * <p><br> + * When the host wants to transfer touch gesture to the embedded, it can retrieve the embedded + * token via {@link SurfaceControlViewHost.SurfacePackage#getInputTransferToken()} or use the + * value returned from either + * {@link #registerBatchedSurfaceControlInputReceiver(int, InputTransferToken, SurfaceControl, + * Choreographer, SurfaceControlInputReceiver)} or + * {@link #registerUnbatchedSurfaceControlInputReceiver(int, InputTransferToken, SurfaceControl, + * Looper, SurfaceControlInputReceiver)} and pass its own token as the transferFromToken. + * <p> + * When the embedded wants to transfer touch gesture to the host, it can pass in its own + * token as the transferFromToken and use the associated host's {@link InputTransferToken} as + * the transferToToken + * <p><br> + * When the touch is transferred, the window currently receiving touch gets an ACTION_CANCEL + * and does not receive any further input events for this gesture. + * <p> + * The transferred-to window receives an ACTION_DOWN event and then the remainder of the input + * events for this gesture. It does not receive any of the previous events of this gesture that + * the originating window received. + * <p> + * The transferTouchGesture API only works for the current gesture. When a new gesture + * arrives, input dispatcher will do a new round of hit testing. So, if the host window is + * still the first thing that's being touched, then it will receive the new gesture again. It + * will again be up to the host to transfer this new gesture to the embedded. + * + * @param transferFromToken the InputTransferToken for the currently active gesture + * @param transferToToken the InputTransferToken to transfer the gesture to. + * @return Whether the touch stream was transferred. + * @see android.view.SurfaceControlViewHost.SurfacePackage#getInputTransferToken() + * @see AttachedSurfaceControl#getInputTransferToken() + */ + @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) + default boolean transferTouchGesture(@NonNull InputTransferToken transferFromToken, + @NonNull InputTransferToken transferToToken) { + throw new UnsupportedOperationException("transferTouchGesture is not implemented"); + } + + /** * @hide */ default @NonNull IBinder getDefaultToken() { diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 142896346bde..584219a55032 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -839,14 +839,15 @@ public final class WindowManagerGlobal { mTrustedPresentationListener.removeListener(listener); } - void registerBatchedSurfaceControlInputReceiver(int displayId, + InputTransferToken registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull InputTransferToken hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { IBinder clientToken = new Binder(); + InputTransferToken inputTransferToken = new InputTransferToken(); InputChannel inputChannel = new InputChannel(); try { WindowManagerGlobal.getWindowSession().grantInputChannel(displayId, surfaceControl, - clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, null, + clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, inputTransferToken, surfaceControl.getName(), inputChannel); } catch (RemoteException e) { Log.e(TAG, "Failed to create input channel", e); @@ -865,16 +866,18 @@ public final class WindowManagerGlobal { } })); } + return inputTransferToken; } - void registerUnbatchedSurfaceControlInputReceiver(int displayId, + InputTransferToken registerUnbatchedSurfaceControlInputReceiver(int displayId, @NonNull InputTransferToken hostToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { IBinder clientToken = new Binder(); + InputTransferToken inputTransferToken = new InputTransferToken(); InputChannel inputChannel = new InputChannel(); try { WindowManagerGlobal.getWindowSession().grantInputChannel(displayId, surfaceControl, - clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, null, + clientToken, hostToken, 0, 0, TYPE_APPLICATION, 0, null, inputTransferToken, surfaceControl.getName(), inputChannel); } catch (RemoteException e) { Log.e(TAG, "Failed to create input channel", e); @@ -892,6 +895,7 @@ public final class WindowManagerGlobal { } })); } + return inputTransferToken; } void unregisterSurfaceControlInputReceiver(SurfaceControl surfaceControl) { @@ -930,6 +934,17 @@ public final class WindowManagerGlobal { return surfaceControlInputReceiverInfo.mClientToken; } + boolean transferTouchGesture(InputTransferToken transferFromToken, + InputTransferToken transferToToken) { + try { + return getWindowManagerService().transferTouchGesture(transferFromToken, + transferToToken); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + return false; + } + private final class TrustedPresentationListener extends ITrustedPresentationListener.Stub { private static int sId = 0; diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 1e3d0624a95b..897222879e8f 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -533,22 +533,24 @@ public final class WindowManagerImpl implements WindowManager { mGlobal.unregisterTrustedPresentationListener(listener); } + @NonNull @Override - public void registerBatchedSurfaceControlInputReceiver(int displayId, + public InputTransferToken registerBatchedSurfaceControlInputReceiver(int displayId, @NonNull InputTransferToken hostInputTransferToken, @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { - mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostInputTransferToken, + return mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostInputTransferToken, surfaceControl, choreographer, receiver); } + @NonNull @Override - public void registerUnbatchedSurfaceControlInputReceiver(int displayId, + public InputTransferToken registerUnbatchedSurfaceControlInputReceiver(int displayId, @NonNull InputTransferToken hostInputTransferToken, @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { - mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostInputTransferToken, - surfaceControl, looper, receiver); + return mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, + hostInputTransferToken, surfaceControl, looper, receiver); } @Override @@ -563,6 +565,14 @@ public final class WindowManagerImpl implements WindowManager { } @Override + public boolean transferTouchGesture(@NonNull InputTransferToken transferFromToken, + @NonNull InputTransferToken transferToToken) { + Objects.requireNonNull(transferFromToken); + Objects.requireNonNull(transferToToken); + return mGlobal.transferTouchGesture(transferFromToken, transferToToken); + } + + @Override public @ScreenRecordingState int addScreenRecordingCallback( @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<@ScreenRecordingState Integer> callback) { diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 3f1ae51ef25e..2b2c50725749 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -651,21 +651,6 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public boolean transferEmbeddedTouchFocusToHost(IWindow window) { - Log.e(TAG, "Received request to transferEmbeddedTouch focus on WindowlessWindowManager" + - " we shouldn't get here!"); - return false; - } - - @Override - public boolean transferHostTouchGestureToEmbedded(IWindow hostWindow, - InputTransferToken embeddedInputToken) { - Log.e(TAG, "Received request to transferHostTouchGestureToEmbedded on" - + " WindowlessWindowManager. We shouldn't get here!"); - return false; - } - - @Override public boolean moveFocusToAdjacentWindow(IWindow fromWindow, @FocusDirection int direction) { Log.e(TAG, "Received request to moveFocusToAdjacentWindow on" + " WindowlessWindowManager. We shouldn't get here!"); diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index fa0052cf664a..749f977f5e50 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -93,6 +94,12 @@ public final class AccessibilityWindowInfo implements Parcelable { */ public static final int TYPE_MAGNIFICATION_OVERLAY = 6; + /** + * Window type: A system window that has the function to control an associated window. + */ + @FlaggedApi(Flags.FLAG_ADD_TYPE_WINDOW_CONTROL) + public static final int TYPE_WINDOW_CONTROL = 7; + /* Special values for window IDs */ /** @hide */ public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE; @@ -873,6 +880,10 @@ public final class AccessibilityWindowInfo implements Parcelable { * @hide */ public static String typeToString(int type) { + if (Flags.addTypeWindowControl() && type == TYPE_WINDOW_CONTROL) { + return "TYPE_WINDOW_CONTROL"; + } + switch (type) { case TYPE_APPLICATION: { return "TYPE_APPLICATION"; diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index a11ac7cb48ad..5b99c71f3a8b 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -102,6 +102,13 @@ flag { flag { namespace: "accessibility" + name: "add_type_window_control" + description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations" + bug: "320445550" +} + +flag { + namespace: "accessibility" name: "update_always_on_a11y_service" description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut." bug: "298869916" diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 57e4e6a2fa5b..9847cb14a70e 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -181,6 +181,7 @@ import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewDebug; +import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewHierarchyEncoder; import android.view.ViewParent; @@ -866,6 +867,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final boolean mUseTextPaddingForUiTranslation; private boolean mUseBoundsForWidth; + private boolean mShiftDrawingOffsetForStartOverhang; @Nullable private Paint.FontMetrics mMinimumFontMetrics; @Nullable private Paint.FontMetrics mLocalePreferredFontMetrics; private boolean mUseLocalePreferredLineHeightForMinimum; @@ -1621,6 +1623,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hasUseBoundForWidthValue = true; break; case com.android.internal.R.styleable + .TextView_shiftDrawingOffsetForStartOverhang: + mShiftDrawingOffsetForStartOverhang = a.getBoolean(attr, false); + break; + case com.android.internal.R.styleable .TextView_useLocalePreferredLineHeightForMinimum: mUseLocalePreferredLineHeightForMinimum = a.getBoolean(attr, false); break; @@ -4922,6 +4928,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @param useBoundsForWidth true for using bounding box for width. false for using advances for * width. * @see #getUseBoundsForWidth() + * @see #setShiftDrawingOffsetForStartOverhang(boolean) + * @see #getShiftDrawingOffsetForStartOverhang() */ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public void setUseBoundsForWidth(boolean useBoundsForWidth) { @@ -4939,6 +4947,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Returns true if using bounding box as a width, false for using advance as a width. * * @see #setUseBoundsForWidth(boolean) + * @see #setShiftDrawingOffsetForStartOverhang(boolean) + * @see #getShiftDrawingOffsetForStartOverhang() * @return True if using bounding box for width, false if using advance for width. */ @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @@ -4947,6 +4957,53 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Set true for shifting the drawing x offset for showing overhang at the start position. + * + * This flag is ignored if the {@link #getUseBoundsForWidth()} is false. + * + * If this value is false, the TextView draws text from the zero even if there is a glyph stroke + * in a region where the x coordinate is negative. TextView clips the stroke in the region where + * the X coordinate is negative unless the parents has {@link ViewGroup#getClipChildren()} to + * true. This is useful for aligning multiple TextViews vertically. + * + * If this value is true, the TextView draws text with shifting the x coordinate of the drawing + * bounding box. This prevents the clipping even if the parents doesn't have + * {@link ViewGroup#getClipChildren()} to true. + * + * This value is false by default. + * + * @param shiftDrawingOffsetForStartOverhang true for shifting the drawing offset for showing + * the stroke that is in the region whre the x + * coorinate is negative. + * @see #setUseBoundsForWidth(boolean) + * @see #getUseBoundsForWidth() + */ + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) + public void setShiftDrawingOffsetForStartOverhang(boolean shiftDrawingOffsetForStartOverhang) { + if (mShiftDrawingOffsetForStartOverhang != shiftDrawingOffsetForStartOverhang) { + mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang; + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + } + + /** + * Returns true if shifting the drawing x offset for start overhang. + * + * @see #setShiftDrawingOffsetForStartOverhang(boolean) + * @see #setUseBoundsForWidth(boolean) + * @see #getUseBoundsForWidth() + * @return True if shifting the drawing x offset for start overhang. + */ + @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) + public boolean getShiftDrawingOffsetForStartOverhang() { + return mShiftDrawingOffsetForStartOverhang; + } + + /** * Set the minimum font metrics used for line spacing. * * <p> @@ -11001,6 +11058,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener null, boring, mUseBoundsForWidth, + mShiftDrawingOffsetForStartOverhang, getResolvedMinimumFontMetrics()); } @@ -11028,6 +11086,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener effectiveEllipsize, boring, mUseBoundsForWidth, + mShiftDrawingOffsetForStartOverhang, getResolvedMinimumFontMetrics()); } } diff --git a/core/java/android/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java index bed0e0e8a225..e572853e5d5d 100644 --- a/core/java/android/window/InputTransferToken.java +++ b/core/java/android/window/InputTransferToken.java @@ -20,8 +20,12 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.os.Binder; import android.os.IBinder; +import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; +import android.view.Choreographer; +import android.view.SurfaceControl; +import android.view.SurfaceControlInputReceiver; import android.view.SurfaceControlViewHost; import com.android.window.flags.Flags; @@ -31,6 +35,19 @@ import java.util.Objects; /** * A token that can be used to request focus on or to transfer touch gesture to a * {@link SurfaceControlViewHost} or {@link android.view.SurfaceControl} that has an input channel. + * <p> + * The {@link android.view.SurfaceControl} needs to have been registered for input via + * {@link android.view.WindowManager#registerUnbatchedSurfaceControlInputReceiver(int, + * InputTransferToken, SurfaceControl, Looper, SurfaceControlInputReceiver)} or + * {@link android.view.WindowManager#registerBatchedSurfaceControlInputReceiver(int, + * InputTransferToken, SurfaceControl, Choreographer, SurfaceControlInputReceiver)} and the + * returned token can be used to call + * {@link android.view.WindowManager#transferTouchGesture(InputTransferToken, InputTransferToken)} + * <p> + * For {@link SurfaceControlViewHost}, the token can be retrieved via + * {@link SurfaceControlViewHost.SurfacePackage#getInputTransferToken()} + * + * @see android.view.WindowManager#transferTouchGesture(InputTransferToken, InputTransferToken) */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) public final class InputTransferToken implements Parcelable { diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 3ffa27451557..8b3bd974b3fa 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -20,13 +20,6 @@ flag { flag { namespace: "window_surfaces" - name: "transfer_gesture_to_embedded" - description: "Enable public API for Window Surfaces" - bug: "287076178" -} - -flag { - namespace: "window_surfaces" name: "delete_capture_display" description: "Delete uses of ScreenCapture#captureDisplay" is_fixed_read_only: true diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index bc63881163f0..ce74848705e4 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -69,4 +69,12 @@ flag { name: "embedded_activity_back_nav_flag" description: "Refines embedded activity back navigation behavior" bug: "293642394" +} + +flag { + namespace: "windowing_sdk" + name: "cover_display_opt_in" + description: "Properties to allow apps and activities to opt-in to cover display rendering" + bug: "312530526" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 3a321e5c26f7..3ec70649294b 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -162,4 +162,5 @@ interface IAppOpsService { int attributionFlags, int attributionChainId); void finishOperationForDevice(IBinder clientId, int code, int uid, String packageName, @nullable String attributionTag, int virtualDeviceId); + List<AppOpsManager.PackageOps> getPackagesForOpsForDevice(in int[] ops, String persistentDeviceId); } diff --git a/core/java/com/android/internal/content/ReferrerIntent.java b/core/java/com/android/internal/content/ReferrerIntent.java index 6af03dd29899..2c68203d9cae 100644 --- a/core/java/com/android/internal/content/ReferrerIntent.java +++ b/core/java/com/android/internal/content/ReferrerIntent.java @@ -18,6 +18,7 @@ package com.android.internal.content; import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; +import android.os.IBinder; import android.os.Parcel; import java.util.Objects; @@ -29,20 +30,29 @@ public class ReferrerIntent extends Intent { @UnsupportedAppUsage public final String mReferrer; + public final IBinder mCallerToken; + @UnsupportedAppUsage public ReferrerIntent(Intent baseIntent, String referrer) { + this(baseIntent, referrer, /* callerToken */ null); + } + + public ReferrerIntent(Intent baseIntent, String referrer, IBinder callerToken) { super(baseIntent); mReferrer = referrer; + mCallerToken = callerToken; } public void writeToParcel(Parcel dest, int parcelableFlags) { super.writeToParcel(dest, parcelableFlags); dest.writeString(mReferrer); + dest.writeStrongBinder(mCallerToken); } ReferrerIntent(Parcel in) { readFromParcel(in); mReferrer = in.readString(); + mCallerToken = in.readStrongBinder(); } public static final Creator<ReferrerIntent> CREATOR = new Creator<ReferrerIntent>() { @@ -60,7 +70,8 @@ public class ReferrerIntent extends Intent { return false; } final ReferrerIntent other = (ReferrerIntent) obj; - return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer); + return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer) + && Objects.equals(mCallerToken, other.mCallerToken); } @Override @@ -68,6 +79,7 @@ public class ReferrerIntent extends Intent { int result = 17; result = 31 * result + filterHashCode(); result = 31 * result + Objects.hashCode(mReferrer); + result = 31 * result + Objects.hashCode(mCallerToken); return result; } } diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index ed943cb2385d..eef6ce7619e8 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -57,7 +57,7 @@ import java.util.concurrent.atomic.AtomicReference; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.LongArrayMultiStateCounter_host") + "com.android.platform.test.ravenwood.nativesubstitution.LongArrayMultiStateCounter_host") public final class LongArrayMultiStateCounter implements Parcelable { /** @@ -65,7 +65,7 @@ public final class LongArrayMultiStateCounter implements Parcelable { */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution" + "com.android.platform.test.ravenwood.nativesubstitution" + ".LongArrayMultiStateCounter_host$LongArrayContainer_host") public static class LongArrayContainer { private static NativeAllocationRegistry sRegistry; diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java index 064609f9dfe4..e5662c7d5145 100644 --- a/core/java/com/android/internal/os/LongMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongMultiStateCounter.java @@ -57,7 +57,7 @@ import libcore.util.NativeAllocationRegistry; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.LongMultiStateCounter_host") + "com.android.platform.test.ravenwood.nativesubstitution.LongMultiStateCounter_host") public final class LongMultiStateCounter implements Parcelable { private static NativeAllocationRegistry sRegistry; diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java index d433ca3246b6..73df5e8e7b1b 100644 --- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java @@ -408,6 +408,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, // Derived fields private long mLongVersionCode; private int mLocaleConfigRes; + private boolean mAllowCrossUidActivitySwitchFromBelow; private List<AndroidPackageSplit> mSplits; @@ -1542,6 +1543,11 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } @Override + public boolean isAllowCrossUidActivitySwitchFromBelow() { + return mAllowCrossUidActivitySwitchFromBelow; + } + + @Override public boolean hasPreserveLegacyExternalStorage() { return getBoolean(Booleans.PRESERVE_LEGACY_EXTERNAL_STORAGE); } @@ -2199,6 +2205,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } @Override + public ParsingPackage setAllowCrossUidActivitySwitchFromBelow(boolean value) { + mAllowCrossUidActivitySwitchFromBelow = value; + return this; + } + + @Override public PackageImpl setResourceOverlay(boolean value) { return setBoolean(Booleans.OVERLAY, value); } @@ -2656,6 +2668,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, if (!mKnownActivityEmbeddingCerts.isEmpty()) { appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts); } + appInfo.allowCrossUidActivitySwitchFromBelow = mAllowCrossUidActivitySwitchFromBelow; return appInfo; } @@ -3250,6 +3263,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, dest.writeInt(this.uid); dest.writeLong(this.mBooleans); dest.writeLong(this.mBooleans2); + dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow); } public PackageImpl(Parcel in) { @@ -3411,6 +3425,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, this.uid = in.readInt(); this.mBooleans = in.readLong(); this.mBooleans2 = in.readLong(); + this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean(); assignDerivedFields(); assignDerivedFields2(); diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index ef106e0268a6..5d185af17d48 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -374,6 +374,9 @@ public interface ParsingPackage { ParsingPackage setZygotePreloadName(String zygotePreloadName); + ParsingPackage setAllowCrossUidActivitySwitchFromBelow( + boolean allowCrossUidActivitySwitchFromBelow); + ParsingPackage sortActivities(); ParsingPackage sortReceivers(); @@ -518,6 +521,8 @@ public interface ParsingPackage { @Nullable String getZygotePreloadName(); + boolean isAllowCrossUidActivitySwitchFromBelow(); + boolean isBackupAllowed(); boolean isTaskReparentingAllowed(); diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index e0fdbc68a41f..2e6053d3d904 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -2374,8 +2374,10 @@ public class ParsingPackageUtils { .setRestrictedAccountType(string(R.styleable.AndroidManifestApplication_restrictedAccountType, sa)) .setZygotePreloadName(string(R.styleable.AndroidManifestApplication_zygotePreloadName, sa)) // Non-Config String - .setPermission(nonConfigString(0, R.styleable.AndroidManifestApplication_permission, sa)); - // CHECKSTYLE:on + .setPermission(nonConfigString(0, R.styleable.AndroidManifestApplication_permission, sa)) + .setAllowCrossUidActivitySwitchFromBelow(bool(true, R.styleable.AndroidManifestApplication_allowCrossUidActivitySwitchFromBelow, sa)); + + // CHECKSTYLE:on //@formatter:on } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index b5b3a48dacb7..e46b8d7c5fae 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1615,7 +1615,8 @@ public class LockPatternUtils { STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT, - SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED}) + SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, + SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST}) @Retention(RetentionPolicy.SOURCE) public @interface StrongAuthFlags {} @@ -1641,7 +1642,8 @@ public class LockPatternUtils { /** * Strong authentication is required because the user has been locked out after too many - * attempts. + * attempts using primary auth methods (i.e. PIN/pattern/password) from the lock screen, + * Android Settings, and BiometricPrompt where user authentication is required. */ public static final int STRONG_AUTH_REQUIRED_AFTER_LOCKOUT = 0x8; @@ -1674,12 +1676,23 @@ public class LockPatternUtils { public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100; /** + * Some authentication is required because adaptive auth has requested to lock device due to + * repeated failed primary auth (i.e. PIN/pattern/password) or biometric auth attempts which + * can come from Android Settings or BiometricPrompt where user authentication is required, + * in addition to from the lock screen. When a risk is determined, adaptive auth will + * proactively prompt the lock screen and will require users to re-enter the device with + * either primary auth or biometric auth (if not prohibited by other flags). + */ + public static final int SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST = 0x200; + + /** * Strong auth flags that do not prevent biometric methods from being accepted as auth. * If any other flags are set, biometric authentication is disabled. */ private static final int ALLOWING_BIOMETRIC = STRONG_AUTH_NOT_REQUIRED | SOME_AUTH_REQUIRED_AFTER_USER_REQUEST - | SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED; + | SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED + | SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; private final SparseIntArray mStrongAuthRequiredForUser = new SparseIntArray(); private final H mHandler; diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java index 096f246b1bab..d430fe32a64e 100644 --- a/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -1507,4 +1507,11 @@ public interface AndroidPackage { * @hide */ boolean isVisibleToInstantApps(); + + /** + * @see ApplicationInfo#allowCrossUidActivitySwitchFromBelow + * @see R.styleable#AndroidManifestApplication_allowCrossUidActivitySwitchFromBelow + * @hide + */ + boolean isAllowCrossUidActivitySwitchFromBelow(); } diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto index 8c3304137904..d9ed911515ba 100644 --- a/core/proto/android/app/appstartinfo.proto +++ b/core/proto/android/app/appstartinfo.proto @@ -39,4 +39,5 @@ message ApplicationStartInfoProto { optional AppStartStartType start_type = 9; optional bytes start_intent = 10; optional AppStartLaunchMode launch_mode = 11; + optional bool was_force_stopped = 12; } diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto index b9905e8cf446..b7408a4da381 100644 --- a/core/proto/android/content/package_item_info.proto +++ b/core/proto/android/content/package_item_info.proto @@ -113,6 +113,7 @@ message ApplicationInfoProto { optional int32 enable_gwp_asan = 19; optional int32 enable_memtag = 20; optional bool native_heap_zero_init = 21; + optional bool allow_cross_uid_activity_switch_from_below = 22; } optional Detail detail = 17; repeated string overlay_paths = 18; diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto index 9359528b613b..e368c6a98a75 100644 --- a/core/proto/android/hardware/sensorprivacy.proto +++ b/core/proto/android/hardware/sensorprivacy.proto @@ -91,6 +91,9 @@ message SensorPrivacyIndividualEnabledSensorProto { enum StateType { ENABLED = 1; DISABLED = 2; + AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3; + AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4; + AUTO_DRIVER_ASSISTANCE_APPS = 5; } // DEPRECATED @@ -134,4 +137,4 @@ message SensorPrivacyToggleSourceProto { // Source for which sensor privacy was toggled. optional Source source = 1; -}
\ No newline at end of file +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e4e8f7ef5247..71f06f1106de 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -836,6 +836,7 @@ <protected-broadcast android:name="android.intent.action.PROFILE_AVAILABLE" /> <protected-broadcast android:name="android.intent.action.PROFILE_UNAVAILABLE" /> <protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" /> + <protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> @@ -1733,6 +1734,16 @@ android:protectionLevel="signature" android:featureFlag="com.android.internal.camera.flags.camera_hsum_permission" /> + + <!-- @SystemApi Allows camera access of allowlisted driver assistance apps + to be controlled separately. + <p> Not for use by third-party applications. + @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") + @hide + --> + <permission android:name="android.permission.CAMERA_PRIVACY_ALLOWLIST" + android:protectionLevel="signature|privileged" /> + <!-- ====================================================================== --> <!-- Permissions for accessing the device sensors --> <!-- ====================================================================== --> @@ -3679,6 +3690,14 @@ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SECURITY_LOGGING" android:protectionLevel="internal|role" /> + <!-- Allows an application to use audit logging API. + @hide + @SystemApi + @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") + --> + <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" + android:protectionLevel="internal|role" /> + <!-- Allows an application to set policy related to system updates. <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call APIs protected by this permission on users different to the calling user. @@ -3782,6 +3801,14 @@ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK" android:protectionLevel="internal|role" /> + <!-- Allows an application to manage policy related to theft detection. + @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") + @hide + @SystemApi + --> + <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION" + android:protectionLevel="internal|role" /> + <!-- Allows an application to manage policy related to system apps. <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call APIs protected by this permission on users different to the calling user. @@ -3819,6 +3846,24 @@ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS" android:protectionLevel="internal|role" /> + <!-- Allows an application to manage policy related to block package uninstallation. + @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") + --> + <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL" + android:protectionLevel="internal|role" /> + + <!-- Allows an application to manage policy related to camera toggle. + @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") + --> + <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE" + android:protectionLevel="internal|role" /> + + <!-- Allows an application to manage policy related to microphone toggle. + @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") + --> + <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE" + android:protectionLevel="internal|role" /> + <!-- Allows an application to set device policies outside the current user that are critical for securing data within the current user. <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_* diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 4ee03deab6c2..cc6460ea7d4d 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5891,6 +5891,17 @@ use glyph bound's as a source of text width. --> <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") --> <attr name="useBoundsForWidth" format="boolean" /> + + + <!-- Whether to shift the drawing offset for prevent clipping start drawing offset. + This value is ignored when the useBoundsForWidth attribute is false. + + If this value is false, the TextView draws text from the zero X coordinate. This is + useful for aligning multiple TextViews vertically. + If this value is true, the TextView shift the drawing offset not to clip the + stroke in the region where the X coordinate is negative. --> + <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") --> + <attr name="shiftDrawingOffsetForStartOverhang" format="boolean" /> <!-- Whether to use the locale preferred line height for the minimum line height. This flag is useful for preventing jitter of entering letters into empty EditText. diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 647bccfb0c89..b2e0be7c2201 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1919,6 +1919,12 @@ try to load its code when launching components. The default is true for normal behavior. --> <attr name="hasCode" format="boolean" /> + <!-- Specifies if activities can be launched on top of this application by activities from + other applications in the same task. If set to false, activity launches which would + replace this application with another when in the user's view will be blocked. + The default is true. --> + <!-- @FlaggedApi("android.security.asm_restrictions_enabled") --> + <attr name="allowCrossUidActivitySwitchFromBelow" format="boolean" /> <attr name="persistent" /> <attr name="persistentWhenFeatureAvailable" /> <attr name="requiredForAllUsers" /> @@ -3289,7 +3295,8 @@ {@link java.lang.SecurityException}. <p> Note that the enforcement works for content URIs inside - {@link android.content.Intent#getData} and {@link android.content.Intent#getClipData}. + {@link android.content.Intent#getData}, {@link android.content.Intent#EXTRA_STREAM}, + and {@link android.content.Intent#getClipData}. @FlaggedApi("android.security.content_uri_permission_apis") --> <attr name="requireContentUriPermissionFromCaller" format="string"> <!-- Default, no specific permissions are required. --> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index dcb6bb0cd743..8af8cb84a217 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -159,6 +159,12 @@ <public name="contentSensitivity" /> <!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") --> <public name="supportsConnectionlessStylusHandwriting" /> + <!-- @FlaggedApi("android.nfc.Flags.FLAG_OBSERVE_MODE") --> + <public name="defaultToObserveMode"/> + <!-- @FlaggedApi("android.security.asm_restrictions_enabled") --> + <public name="allowCrossUidActivitySwitchFromBelow"/> + <!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") --> + <public name="shiftDrawingOffsetForStartOverhang" /> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/core/tests/BroadcastRadioTests/TEST_MAPPING b/core/tests/BroadcastRadioTests/TEST_MAPPING index b085a27b25b8..563706374bf2 100644 --- a/core/tests/BroadcastRadioTests/TEST_MAPPING +++ b/core/tests/BroadcastRadioTests/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "BroadcastRadioTests" } diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 2327b20bada6..48ef7e6f11a8 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -40,6 +40,7 @@ import android.app.ActivityThread.ActivityClientRecord; import android.app.Application; import android.app.IApplicationThread; import android.app.PictureInPictureParams; +import android.app.PictureInPictureUiState; import android.app.ResourcesManager; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityLifecycleItem; @@ -706,6 +707,9 @@ public class ActivityThreadTest { final TestActivity activity = mActivityTestRule.launchActivity(startIntent); final ActivityThread activityThread = activity.getActivityThread(); final ActivityClientRecord r = getActivityClientRecord(activity); + if (android.app.Flags.enablePipUiStateCallbackOnEntering()) { + activity.mPipUiStateLatch = new CountDownLatch(1); + } InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { activityThread.handlePictureInPictureRequested(r); @@ -940,6 +944,11 @@ public class ActivityThreadTest { * latch reaches 0. */ volatile CountDownLatch mConfigLatch; + /** + * A latch used to notify tests that we're about to wait for the + * onPictureInPictureUiStateChanged callback. + */ + volatile CountDownLatch mPipUiStateLatch; @Override protected void onCreate(Bundle savedInstanceState) { @@ -974,6 +983,14 @@ public class ActivityThreadTest { if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) { enterPictureInPictureMode(new PictureInPictureParams.Builder().build()); mPipEntered = true; + // Await for onPictureInPictureUiStateChanged callback if applicable + if (mPipUiStateLatch != null) { + try { + mPipUiStateLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } return true; } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) { mPipEnterSkipped = true; @@ -982,6 +999,13 @@ public class ActivityThreadTest { return super.onPictureInPictureRequested(); } + @Override + public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) { + if (mPipUiStateLatch != null && pipState.isEnteringPip()) { + mPipUiStateLatch.countDown(); + } + } + boolean pipRequested() { return mPipRequested; } diff --git a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java index 661b210f3e18..402d08eb4a25 100644 --- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java +++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java @@ -27,6 +27,8 @@ import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyResourcesManager; @@ -118,12 +120,55 @@ public class CrossProfileAppsTest { public void initUsers() throws Exception { when(mUserManager.isManagedProfile(PERSONAL_PROFILE.getIdentifier())).thenReturn(false); when(mUserManager.isManagedProfile(MANAGED_PROFILE.getIdentifier())).thenReturn(true); + when(mUserManager.isProfile(PERSONAL_PROFILE.getIdentifier())).thenReturn(false); + when(mUserManager.isProfile(MANAGED_PROFILE.getIdentifier())).thenReturn(true); mTargetProfiles = new ArrayList<>(); when(mService.getTargetUserProfiles(MY_PACKAGE)).thenReturn(mTargetProfiles); } @Test + public void isProfile_managedProfile_returnsTrue() { + setValidTargetProfile(MANAGED_PROFILE); + + boolean result = mCrossProfileApps.isProfile(MANAGED_PROFILE); + + assertTrue(result); + } + + @Test + public void isProfile_personalProfile_returnsFalse() { + setValidTargetProfile(PERSONAL_PROFILE); + + boolean result = mCrossProfileApps.isProfile(PERSONAL_PROFILE); + + assertFalse(result); + } + + @Test + public void isManagedProfile_managedProfile_returnsTrue() { + setValidTargetProfile(MANAGED_PROFILE); + + boolean result = mCrossProfileApps.isManagedProfile(MANAGED_PROFILE); + + assertTrue(result); + } + + @Test + public void isManagedProfile_personalProfile_returnsFalse() { + setValidTargetProfile(PERSONAL_PROFILE); + + boolean result = mCrossProfileApps.isManagedProfile(PERSONAL_PROFILE); + + assertFalse(result); + } + + @Test(expected = SecurityException.class) + public void isManagedProfile_notValidTarget_throwsSecurityException() { + mCrossProfileApps.isManagedProfile(PERSONAL_PROFILE); + } + + @Test public void getProfileSwitchingLabel_managedProfile() { setValidTargetProfile(MANAGED_PROFILE); when(mApplicationInfo.loadSafeLabel(any(), anyFloat(), anyInt())).thenReturn("app"); diff --git a/core/tests/coretests/src/android/os/RemoteCallbackListTest.java b/core/tests/coretests/src/android/os/RemoteCallbackListTest.java index aa89e04b4b7e..cc342cf0fc80 100644 --- a/core/tests/coretests/src/android/os/RemoteCallbackListTest.java +++ b/core/tests/coretests/src/android/os/RemoteCallbackListTest.java @@ -17,6 +17,7 @@ package android.os; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import androidx.test.runner.AndroidJUnit4; @@ -24,6 +25,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -59,10 +62,17 @@ public class RemoteCallbackListTest { mList.register(mRed.mInterface); mList.register(mGreen.mInterface, mCookie); assertEquals(2, mList.getRegisteredCallbackCount()); - assertEquals(mRed.mInterface, mList.getRegisteredCallbackItem(0)); - assertEquals(null, mList.getRegisteredCallbackCookie(0)); - assertEquals(mGreen.mInterface, mList.getRegisteredCallbackItem(1)); - assertEquals(mCookie, mList.getRegisteredCallbackCookie(1)); + + final List<IRemoteCallback> list = new ArrayList<>(); + for (int i = 0; i < mList.getRegisteredCallbackCount(); i++) { + list.add(mList.getRegisteredCallbackItem(i)); + } + final int redIndex = list.indexOf(mRed.mInterface); + final int greenIndex = list.indexOf(mGreen.mInterface); + assertTrue(redIndex >= 0); + assertTrue(greenIndex >= 0); + assertEquals(null, mList.getRegisteredCallbackCookie(redIndex)); + assertEquals(mCookie, mList.getRegisteredCallbackCookie(greenIndex)); mList.unregister(mRed.mInterface); assertEquals(1, mList.getRegisteredCallbackCount()); diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 0657e4b9c619..433d353e5074 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -660,10 +660,22 @@ public class ViewRootImplTest { public void votePreferredFrameRate_voteFrameRateCategory_aggregate() { View view = new View(sContext); attachViewToWindow(view); + ViewRootImpl viewRootImpl = view.getViewRootImpl(); sInstrumentation.runOnMainSync(() -> { - ViewRootImpl viewRootImpl = view.getViewRootImpl(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NO_PREFERENCE); + }); + + // reset the frame rate category counts + for (int i = 0; i < 5; i++) { + sInstrumentation.runOnMainSync(() -> { + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + view.invalidate(); + }); + sInstrumentation.waitForIdleSync(); + } + + sInstrumentation.runOnMainSync(() -> { viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); @@ -699,6 +711,8 @@ public class ViewRootImplTest { viewRootImpl.votePreferredFrameRate(24); assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1); viewRootImpl.votePreferredFrameRate(30); + assertEquals(viewRootImpl.getPreferredFrameRate(), 30, 0.1); + viewRootImpl.votePreferredFrameRate(60); assertEquals(viewRootImpl.getPreferredFrameRate(), 60, 0.1); viewRootImpl.votePreferredFrameRate(120); assertEquals(viewRootImpl.getPreferredFrameRate(), 120, 0.1); @@ -721,6 +735,18 @@ public class ViewRootImplTest { sInstrumentation.runOnMainSync(() -> { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NO_PREFERENCE); + }); + + // reset the frame rate category counts + for (int i = 0; i < 5; i++) { + sInstrumentation.runOnMainSync(() -> { + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + view.invalidate(); + }); + sInstrumentation.waitForIdleSync(); + } + + sInstrumentation.runOnMainSync(() -> { view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_LOW); view.invalidate(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); @@ -847,7 +873,18 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NO_PREFERENCE); assertEquals(viewRootImpl.getPreferredFrameRate(), frameRate, 0.1); + }); + + // reset the frame rate category counts + for (int i = 0; i < 5; i++) { + sInstrumentation.runOnMainSync(() -> { + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + view.invalidate(); + }); + sInstrumentation.waitForIdleSync(); + } + sInstrumentation.runOnMainSync(() -> { view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_LOW); view.invalidate(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); @@ -882,28 +919,35 @@ public class ViewRootImplTest { ViewRootImpl viewRootImpl = view.getViewRootImpl(); - // Frequent update + // In transistion from frequent update to infrequent update + Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), - FRAME_RATE_CATEGORY_NO_PREFERENCE); - view.invalidate(); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); - view.invalidate(); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); view.invalidate(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); }); + // reset the frame rate category counts + for (int i = 0; i < 5; i++) { + sInstrumentation.runOnMainSync(() -> { + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + view.invalidate(); + }); + sInstrumentation.waitForIdleSync(); + } + // In transistion from frequent update to infrequent update Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); view.invalidate(); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_NO_PREFERENCE); }); // Infrequent update Thread.sleep(delay); sInstrumentation.runOnMainSync(() -> { + view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT); view.invalidate(); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); }); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index c8cbb9800626..42e3387e3eb5 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2077,6 +2077,12 @@ "group": "WM_DEBUG_LOCKTASK", "at": "com\/android\/server\/wm\/LockTaskController.java" }, + "-315778658": { + "message": "transferTouchGesture failed because args transferFromToken or transferToToken is null", + "level": "ERROR", + "group": "WM_DEBUG_EMBEDDED_WINDOWS", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "-312353598": { "message": "Executing finish of activity: %s", "level": "VERBOSE", @@ -2293,12 +2299,6 @@ "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowState.java" }, - "-90559682": { - "message": "Config is skipping already pausing %s", - "level": "VERBOSE", - "group": "WM_DEBUG_CONFIGURATION", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-87705714": { "message": "findFocusedWindow: focusedApp=null using new focus @ %s", "level": "VERBOSE", @@ -3547,12 +3547,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "1011462000": { - "message": "Re-launching after pause: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_STATES", - "at": "com\/android\/server\/wm\/TaskFragment.java" - }, "1015746067": { "message": "Display id=%d is ignoring orientation request for %d, return %d following a per-app override for %s", "level": "VERBOSE", diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index b33a5d2dcef8..f3bb21719890 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1128,6 +1128,30 @@ public class Canvas extends BaseCanvas { return false; } + /** + * Intersect the current clip with the specified shader. + * The shader will be treated as an alpha mask, taking the intersection of the two. + * + * @param shader The shader to intersect with the current clip + */ + @FlaggedApi(Flags.FLAG_CLIP_SHADER) + public void clipShader(@NonNull Shader shader) { + nClipShader(mNativeCanvasWrapper, shader.getNativeInstance(), + Region.Op.INTERSECT.nativeInt); + } + + /** + * Set the clip to the difference of the current clip and the shader. + * The shader will be treated as an alpha mask, taking the difference of the two. + * + * @param shader The shader to intersect with the current clip + */ + @FlaggedApi(Flags.FLAG_CLIP_SHADER) + public void clipOutShader(@NonNull Shader shader) { + nClipShader(mNativeCanvasWrapper, shader.getNativeInstance(), + Region.Op.DIFFERENCE.nativeInt); + } + public @Nullable DrawFilter getDrawFilter() { return mDrawFilter; } @@ -1472,6 +1496,8 @@ public class Canvas extends BaseCanvas { @CriticalNative private static native boolean nClipPath(long nativeCanvas, long nativePath, int regionOp); @CriticalNative + private static native void nClipShader(long nativeCanvas, long nativeShader, int regionOp); + @CriticalNative private static native void nSetDrawFilter(long nativeCanvas, long nativeFilter); @CriticalNative private static native void nGetMatrix(long nativeCanvas, long nativeMatrix); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index dffcc6df79d8..b5ea1b1b43ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; @@ -929,7 +930,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { Slog.e(TAG, "Duplicate call to finish"); return; } - if (!toHome) { + + boolean returningToApp = !toHome + && !mWillFinishToHome + && mPausingTasks != null + && mState == STATE_NORMAL; + if (returningToApp && allAppsAreTranslucent(mPausingTasks)) { + mHomeTransitionObserver.notifyHomeVisibilityChanged(true); + } else if (!toHome) { // For some transitions, we may have notified home activity that it became visible. // We need to notify the observer that we are no longer going home. mHomeTransitionObserver.notifyHomeVisibilityChanged(false); @@ -948,7 +956,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { if (toHome) wct.reorder(mRecentsTask, true /* toTop */); else wct.restoreTransientOrder(mRecentsTask); } - if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) { + if (returningToApp) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " returning to app"); // The gesture is returning to the pausing-task(s) rather than continuing with // recents, so end the transition by moving the app back to the top (and also @@ -1048,6 +1056,18 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } + private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) { + if (tasks == null || tasks.isEmpty()) { + return false; + } + for (int i = tasks.size() - 1; i >= 0; --i) { + if (!tasks.get(i).mIsTranslucent) { + return false; + } + } + return true; + } + private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) { if (!sendUserLeaveHint && task.isLeaf()) { @@ -1118,6 +1138,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { /** The surface/leash of the task provided by Core. */ SurfaceControl mTaskSurface; + /** True when the task is translucent. */ + final boolean mIsTranslucent; + /** The (local) animation-leash created for this task. Only non-null for leafs. */ @Nullable SurfaceControl mLeash; @@ -1126,6 +1149,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mToken = change.getContainer(); mTaskInfo = change.getTaskInfo(); mTaskSurface = change.getLeash(); + mIsTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0; mLeash = leash; } diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 14b8d8d0aa12..0b739c361d64 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -70,6 +70,8 @@ public: : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {} Clip(const SkPath& path, SkClipOp op, const SkMatrix& m) : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {} + Clip(const sk_sp<SkShader> shader, SkClipOp op, const SkMatrix& m) + : mType(Type::Shader), mOp(op), mMatrix(m), mShader(shader) {} void apply(SkCanvas* canvas) const { canvas->setMatrix(mMatrix); @@ -86,6 +88,8 @@ public: // Ensure path clips are anti-aliased canvas->clipPath(mPath.value(), mOp, true); break; + case Type::Shader: + canvas->clipShader(mShader, mOp); } } @@ -94,6 +98,7 @@ private: Rect, RRect, Path, + Shader, }; Type mType; @@ -103,6 +108,7 @@ private: // These are logically a union (tracked separately due to non-POD path). std::optional<SkPath> mPath; SkRRect mRRect; + sk_sp<SkShader> mShader; }; Canvas* Canvas::create_canvas(const SkBitmap& bitmap) { @@ -413,6 +419,11 @@ bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) { return !mCanvas->isClipEmpty(); } +void SkiaCanvas::clipShader(sk_sp<SkShader> shader, SkClipOp op) { + this->recordClip(shader, op); + mCanvas->clipShader(shader, op); +} + bool SkiaCanvas::replaceClipRect_deprecated(float left, float top, float right, float bottom) { SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 5e3553bbbbb4..4a012bc601c9 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -97,6 +97,7 @@ public: virtual bool quickRejectPath(const SkPath& path) const override; virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override; virtual bool clipPath(const SkPath* path, SkClipOp op) override; + virtual void clipShader(sk_sp<SkShader> shader, SkClipOp op) override; virtual bool replaceClipRect_deprecated(float left, float top, float right, float bottom) override; virtual bool replaceClipPath_deprecated(const SkPath* path) override; diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 20e3ad2c8202..14b4f584f0f3 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -188,6 +188,7 @@ public: virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) = 0; virtual bool clipPath(const SkPath* path, SkClipOp op) = 0; + virtual void clipShader(sk_sp<SkShader> shader, SkClipOp op) = 0; // Resets clip to wide open, used to emulate the now-removed SkClipOp::kReplace on // apps with compatibility < P. Canvases for version P and later are restricted to // intersect and difference at the Java level, matching SkClipOp's definition. diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index e5bdeeea75be..1fc34d633370 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -261,6 +261,23 @@ static jboolean clipPath(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong pat return nonEmptyClip ? JNI_TRUE : JNI_FALSE; } +static void clipShader(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jlong shaderHandle, + jint opHandle) { + SkRegion::Op rgnOp = static_cast<SkRegion::Op>(opHandle); + sk_sp<SkShader> shader = sk_ref_sp(reinterpret_cast<SkShader*>(shaderHandle)); + switch (rgnOp) { + case SkRegion::Op::kIntersect_Op: + case SkRegion::Op::kDifference_Op: + get_canvas(canvasHandle)->clipShader(shader, static_cast<SkClipOp>(rgnOp)); + break; + default: + ALOGW("Ignoring unsupported clip operation %d", opHandle); + SkRect clipBounds; // ignored + get_canvas(canvasHandle)->getClipBounds(&clipBounds); + break; + } +} + static void drawColor(JNIEnv* env, jobject, jlong canvasHandle, jint color, jint modeHandle) { SkBlendMode mode = static_cast<SkBlendMode>(modeHandle); get_canvas(canvasHandle)->drawColor(color, mode); @@ -797,6 +814,7 @@ static const JNINativeMethod gMethods[] = { {"nQuickReject", "(JFFFF)Z", (void*)CanvasJNI::quickRejectRect}, {"nClipRect", "(JFFFFI)Z", (void*)CanvasJNI::clipRect}, {"nClipPath", "(JJI)Z", (void*)CanvasJNI::clipPath}, + {"nClipShader", "(JJI)V", (void*)CanvasJNI::clipShader}, {"nSetDrawFilter", "(JJ)V", (void*)CanvasJNI::setPaintFilter}, }; diff --git a/location/api/current.txt b/location/api/current.txt index 5ed8c3c8eb4b..85e9f65a0718 100644 --- a/location/api/current.txt +++ b/location/api/current.txt @@ -88,12 +88,12 @@ package android.location { public final class Geocoder { ctor public Geocoder(@NonNull android.content.Context); ctor public Geocoder(@NonNull android.content.Context, @NonNull java.util.Locale); - method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange int) throws java.io.IOException; - method public void getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange int, @NonNull android.location.Geocoder.GeocodeListener); - method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange int) throws java.io.IOException; - method public void getFromLocationName(@NonNull String, @IntRange int, @NonNull android.location.Geocoder.GeocodeListener); - method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double) throws java.io.IOException; - method public void getFromLocationName(@NonNull String, @IntRange int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @NonNull android.location.Geocoder.GeocodeListener); + method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange(from=1) int) throws java.io.IOException; + method public void getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange(from=1) int, @NonNull android.location.Geocoder.GeocodeListener); + method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange(from=1) int) throws java.io.IOException; + method public void getFromLocationName(@NonNull String, @IntRange(from=1) int, @NonNull android.location.Geocoder.GeocodeListener); + method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange(from=1) int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double) throws java.io.IOException; + method public void getFromLocationName(@NonNull String, @IntRange(from=1) int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @NonNull android.location.Geocoder.GeocodeListener); method public static boolean isPresent(); } diff --git a/location/api/system-current.txt b/location/api/system-current.txt index b1cf96d41497..2e7a541ecb60 100644 --- a/location/api/system-current.txt +++ b/location/api/system-current.txt @@ -591,6 +591,36 @@ package android.location { package android.location.provider { + @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ForwardGeocodeRequest implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getCallingAttributionTag(); + method @NonNull public String getCallingPackage(); + method public int getCallingUid(); + method @NonNull public java.util.Locale getLocale(); + method @NonNull public String getLocationName(); + method @FloatRange(from=-90.0, to=90.0) public double getLowerLeftLatitude(); + method @FloatRange(from=-180.0, to=180.0) public double getLowerLeftLongitude(); + method @IntRange(from=1) public int getMaxResults(); + method @FloatRange(from=-90.0, to=90.0) public double getUpperRightLatitude(); + method @FloatRange(from=-180.0, to=180.0) public double getUpperRightLongitude(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.provider.ForwardGeocodeRequest> CREATOR; + } + + public static final class ForwardGeocodeRequest.Builder { + ctor public ForwardGeocodeRequest.Builder(@NonNull String, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange(from=1) int, @NonNull java.util.Locale, int, @NonNull String); + method @NonNull public android.location.provider.ForwardGeocodeRequest build(); + method @NonNull public android.location.provider.ForwardGeocodeRequest.Builder setCallingAttributionTag(@NonNull String); + } + + @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public abstract class GeocodeProviderBase { + ctor public GeocodeProviderBase(@NonNull android.content.Context, @NonNull String); + method @NonNull public final android.os.IBinder getBinder(); + method public abstract void onForwardGeocode(@NonNull android.location.provider.ForwardGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Exception>); + method public abstract void onReverseGeocode(@NonNull android.location.provider.ReverseGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Exception>); + field public static final String ACTION_GEOCODE_PROVIDER = "com.android.location.service.GeocodeProvider"; + } + public abstract class LocationProviderBase { ctor public LocationProviderBase(@NonNull android.content.Context, @NonNull String, @NonNull android.location.provider.ProviderProperties); method @Nullable public final android.os.IBinder getBinder(); @@ -642,5 +672,24 @@ package android.location.provider { method public void onProviderRequestChanged(@NonNull String, @NonNull android.location.provider.ProviderRequest); } + @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ReverseGeocodeRequest implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getCallingAttributionTag(); + method @NonNull public String getCallingPackage(); + method public int getCallingUid(); + method @FloatRange(from=-90.0, to=90.0) public double getLatitude(); + method @NonNull public java.util.Locale getLocale(); + method @FloatRange(from=-180.0, to=180.0) public double getLongitude(); + method @IntRange(from=1) public int getMaxResults(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.provider.ReverseGeocodeRequest> CREATOR; + } + + public static final class ReverseGeocodeRequest.Builder { + ctor public ReverseGeocodeRequest.Builder(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange(from=0) int, @NonNull java.util.Locale, int, @NonNull String); + method @NonNull public android.location.provider.ReverseGeocodeRequest build(); + method @NonNull public android.location.provider.ReverseGeocodeRequest.Builder setCallingAttributionTag(@NonNull String); + } + } diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java index a15834407315..cdde7c66e268 100644 --- a/location/java/android/location/Geocoder.java +++ b/location/java/android/location/Geocoder.java @@ -21,11 +21,13 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.location.provider.ForwardGeocodeRequest; +import android.location.provider.IGeocodeCallback; +import android.location.provider.ReverseGeocodeRequest; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import com.android.internal.util.Preconditions; - import java.io.IOException; import java.util.Collections; import java.util.List; @@ -33,6 +35,7 @@ import java.util.Locale; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * A class for handling geocoding and reverse geocoding. Geocoding is the process of transforming a @@ -42,9 +45,12 @@ import java.util.concurrent.TimeUnit; * example one might contain the full street address of the closest building, while another might * contain only a city name and postal code. * - * The Geocoder class requires a backend service that is not included in the core android framework. - * The Geocoder query methods will return an empty list if there no backend service in the platform. - * Use the isPresent() method to determine whether a Geocoder implementation exists. + * <p>Use the isPresent() method to determine whether a Geocoder implementation exists on the + * current device. If no implementation is present, any attempt to geocode will result in an error. + * + * <p>Geocoder implementations are only required to make a best effort to return results in the + * chosen locale. Note that geocoder implementations may return results in other locales if they + * have no information available for the chosen locale. * * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful or @@ -52,45 +58,53 @@ import java.util.concurrent.TimeUnit; */ public final class Geocoder { - /** A listener for asynchronous geocoding results. */ + /** + * A listener for asynchronous geocoding results. Only one of the methods will ever be invoked + * per geocoding attempt. There are no guarantees on how long it will take for a method to be + * invoked, nor any guarantees on the format or availability of error information. + */ public interface GeocodeListener { /** Invoked when geocoding completes successfully. May return an empty list. */ void onGeocode(@NonNull List<Address> addresses); - /** Invoked when geocoding fails, with a brief error message. */ + + /** Invoked when geocoding fails, with an optional error message. */ default void onError(@Nullable String errorMessage) {} } - private static final long TIMEOUT_MS = 60000; + private static final long TIMEOUT_MS = 15000; - private final GeocoderParams mParams; + private final Context mContext; + private final Locale mLocale; private final ILocationManager mService; /** - * Returns true if there is a geocoder implementation present that may return results. If true, - * there is still no guarantee that any individual geocoding attempt will succeed. + * Returns true if there is a geocoder implementation present on the device that may return + * results. If true, there is still no guarantee that any individual geocoding attempt will + * succeed. */ public static boolean isPresent() { ILocationManager lm = Objects.requireNonNull(ILocationManager.Stub.asInterface( ServiceManager.getService(Context.LOCATION_SERVICE))); try { - return lm.geocoderIsPresent(); + return lm.isGeocodeAvailable(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - /** - * Constructs a Geocoder localized for the default locale. - */ + /** Constructs a Geocoder localized for {@link Locale#getDefault()}. */ public Geocoder(@NonNull Context context) { this(context, Locale.getDefault()); } /** - * Constructs a Geocoder localized for the given locale. + * Constructs a Geocoder localized for the given locale. Note that geocoder implementations will + * only make a best effort to return results in the given locale, and there is no guarantee that + * returned results will be in the specific locale. */ public Geocoder(@NonNull Context context, @NonNull Locale locale) { - mParams = new GeocoderParams(context, locale); + mContext = Objects.requireNonNull(context); + mLocale = Objects.requireNonNull(locale); mService = ILocationManager.Stub.asInterface( ServiceManager.getService(Context.LOCATION_SERVICE)); } @@ -103,31 +117,28 @@ public final class Geocoder { * <p class="warning"><strong>Warning:</strong> Geocoding services may provide no guarantees on * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance - * purposes.</p> + * purposes. * * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for - * excessive amounts of time, up to 60 seconds or more. It's strongly encouraged to use the - * asynchronous version of this API. If that is not possible, this should be run on a background - * thread to avoid blocking other operations.</p> + * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this + * API. If that is not possible, this should be run on a background thread to avoid blocking + * other operations. * * @param latitude the latitude a point for the search * @param longitude the longitude a point for the search * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended - * - * @return a list of Address objects. Returns null or empty list if no matches were - * found or there is no backend service available. - * + * @return a list of Address objects. Returns null or empty list if no matches were found or + * there is no backend service available. * @throws IllegalArgumentException if latitude or longitude is invalid * @throws IOException if there is a failure - * * @deprecated Use {@link #getFromLocation(double, double, int, GeocodeListener)} instead to - * avoid blocking a thread waiting for results. + * avoid blocking a thread waiting for results. */ @Deprecated public @Nullable List<Address> getFromLocation( @FloatRange(from = -90D, to = 90D) double latitude, - @FloatRange(from = -180D, to = 180D)double longitude, - @IntRange int maxResults) + @FloatRange(from = -180D, to = 180D) double longitude, + @IntRange(from = 1) int maxResults) throws IOException { SynchronousGeocoder listener = new SynchronousGeocoder(); getFromLocation(latitude, longitude, maxResults, listener); @@ -142,26 +153,32 @@ public final class Geocoder { * <p class="warning"><strong>Warning:</strong> Geocoding services may provide no guarantees on * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance - * purposes.</p> + * purposes. * * @param latitude the latitude a point for the search * @param longitude the longitude a point for the search * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended * @param listener a listener for receiving results - * * @throws IllegalArgumentException if latitude or longitude is invalid */ public void getFromLocation( @FloatRange(from = -90D, to = 90D) double latitude, @FloatRange(from = -180D, to = 180D) double longitude, - @IntRange int maxResults, + @IntRange(from = 1) int maxResults, @NonNull GeocodeListener listener) { - Preconditions.checkArgumentInRange(latitude, -90.0, 90.0, "latitude"); - Preconditions.checkArgumentInRange(longitude, -180.0, 180.0, "longitude"); - + ReverseGeocodeRequest.Builder b = + new ReverseGeocodeRequest.Builder( + latitude, + longitude, + maxResults, + mLocale, + Process.myUid(), + mContext.getPackageName()); + if (mContext.getAttributionTag() != null) { + b.setCallingAttributionTag(mContext.getAttributionTag()); + } try { - mService.getFromLocation(latitude, longitude, maxResults, mParams, - new GeocoderImpl(listener)); + mService.reverseGeocode(b.build(), new GeocodeCallbackImpl(listener)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -176,29 +193,25 @@ public final class Geocoder { * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance - * purposes.</p> + * purposes. * * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for - * excessive amounts of time, up to 60 seconds or more. It's strongly encouraged to use the - * asynchronous version of this API. If that is not possible, this should be run on a background - * thread to avoid blocking other operations.</p> + * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this + * API. If that is not possible, this should be run on a background thread to avoid blocking + * other operations. * * @param locationName a user-supplied description of a location * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended - * - * @return a list of Address objects. Returns null or empty list if no matches were - * found or there is no backend service available. - * + * @return a list of Address objects. Returns null or empty list if no matches were found or + * there is no backend service available. * @throws IllegalArgumentException if locationName is null * @throws IOException if there is a failure - * * @deprecated Use {@link #getFromLocationName(String, int, GeocodeListener)} instead to avoid - * blocking a thread waiting for results. + * blocking a thread waiting for results. */ @Deprecated public @Nullable List<Address> getFromLocationName( - @NonNull String locationName, - @IntRange int maxResults) throws IOException { + @NonNull String locationName, @IntRange(from = 1) int maxResults) throws IOException { return getFromLocationName(locationName, maxResults, 0, 0, 0, 0); } @@ -211,17 +224,16 @@ public final class Geocoder { * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance - * purposes.</p> + * purposes. * * @param locationName a user-supplied description of a location * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended * @param listener a listener for receiving results - * * @throws IllegalArgumentException if locationName is null */ public void getFromLocationName( @NonNull String locationName, - @IntRange int maxResults, + @IntRange(from = 1) int maxResults, @NonNull GeocodeListener listener) { getFromLocationName(locationName, maxResults, 0, 0, 0, 0, listener); } @@ -232,45 +244,42 @@ public final class Geocoder { * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be * localized for the locale provided to this class's constructor. * - * <p> You may specify a bounding box for the search results by including the latitude and + * <p>You may specify a bounding box for the search results by including the latitude and * longitude of the lower left point and upper right point of the box. * * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance - * purposes.</p> + * purposes. * * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for - * excessive amounts of time, up to 60 seconds or more. It's strongly encouraged to use the - * asynchronous version of this API. If that is not possible, this should be run on a background - * thread to avoid blocking other operations.</p> + * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this + * API. If that is not possible, this should be run on a background thread to avoid blocking + * other operations. * - * @param locationName a user-supplied description of a location - * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are - * recommended - * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box - * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box - * @param upperRightLatitude the latitude of the upper right corner of the bounding box + * @param locationName a user-supplied description of a location + * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended + * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box + * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box + * @param upperRightLatitude the latitude of the upper right corner of the bounding box * @param upperRightLongitude the longitude of the upper right corner of the bounding box - * - * @return a list of Address objects. Returns null or empty list if no matches were - * found or there is no backend service available. - * + * @return a list of Address objects. Returns null or empty list if no matches were found or + * there is no backend service available. * @throws IllegalArgumentException if locationName is null * @throws IllegalArgumentException if any latitude or longitude is invalid - * @throws IOException if there is a failure - * + * @throws IOException if there is a failure * @deprecated Use {@link #getFromLocationName(String, int, double, double, double, double, - * GeocodeListener)} instead to avoid blocking a thread waiting for results. + * GeocodeListener)} instead to avoid blocking a thread waiting for results. */ @Deprecated public @Nullable List<Address> getFromLocationName( @NonNull String locationName, - @IntRange int maxResults, + @IntRange(from = 1) int maxResults, @FloatRange(from = -90D, to = 90D) double lowerLeftLatitude, @FloatRange(from = -180D, to = 180D) double lowerLeftLongitude, @FloatRange(from = -90D, to = 90D) double upperRightLatitude, - @FloatRange(from = -180D, to = 180D) double upperRightLongitude) throws IOException { + @FloatRange(from = -180D, to = 180D) double upperRightLongitude) + throws IOException { SynchronousGeocoder listener = new SynchronousGeocoder(); getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, listener); @@ -283,75 +292,79 @@ public final class Geocoder { * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be * localized for the locale provided to this class's constructor. * - * <p> You may specify a bounding box for the search results by including the latitude and + * <p>You may specify a bounding box for the search results by including the latitude and * longitude of the lower left point and upper right point of the box. * * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance - * purposes.</p> + * purposes. * - * @param locationName a user-supplied description of a location - * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are - * recommended - * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box - * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box - * @param upperRightLatitude the latitude of the upper right corner of the bounding box + * @param locationName a user-supplied description of a location + * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended + * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box + * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box + * @param upperRightLatitude the latitude of the upper right corner of the bounding box * @param upperRightLongitude the longitude of the upper right corner of the bounding box - * @param listener a listener for receiving results - * + * @param listener a listener for receiving results * @throws IllegalArgumentException if locationName is null * @throws IllegalArgumentException if any latitude or longitude is invalid */ public void getFromLocationName( @NonNull String locationName, - @IntRange int maxResults, + @IntRange(from = 1) int maxResults, @FloatRange(from = -90D, to = 90D) double lowerLeftLatitude, @FloatRange(from = -180D, to = 180D) double lowerLeftLongitude, @FloatRange(from = -90D, to = 90D) double upperRightLatitude, @FloatRange(from = -180D, to = 180D) double upperRightLongitude, @NonNull GeocodeListener listener) { - Preconditions.checkArgument(locationName != null); - Preconditions.checkArgumentInRange(lowerLeftLatitude, -90.0, 90.0, "lowerLeftLatitude"); - Preconditions.checkArgumentInRange(lowerLeftLongitude, -180.0, 180.0, "lowerLeftLongitude"); - Preconditions.checkArgumentInRange(upperRightLatitude, -90.0, 90.0, "upperRightLatitude"); - Preconditions.checkArgumentInRange(upperRightLongitude, -180.0, 180.0, - "upperRightLongitude"); - + ForwardGeocodeRequest.Builder b = + new ForwardGeocodeRequest.Builder( + locationName, + lowerLeftLatitude, + lowerLeftLongitude, + upperRightLatitude, + upperRightLongitude, + maxResults, + mLocale, + Process.myUid(), + mContext.getPackageName()); + if (mContext.getAttributionTag() != null) { + b.setCallingAttributionTag(mContext.getAttributionTag()); + } try { - mService.getFromLocationName(locationName, lowerLeftLatitude, lowerLeftLongitude, - upperRightLatitude, upperRightLongitude, maxResults, mParams, - new GeocoderImpl(listener)); + mService.forwardGeocode(b.build(), new GeocodeCallbackImpl(listener)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - private static class GeocoderImpl extends IGeocodeListener.Stub { + private static class GeocodeCallbackImpl extends IGeocodeCallback.Stub { - private GeocodeListener mListener; + @Nullable private GeocodeListener mListener; - GeocoderImpl(GeocodeListener listener) { + GeocodeCallbackImpl(GeocodeListener listener) { mListener = Objects.requireNonNull(listener); } @Override - public void onResults(String error, List<Address> addresses) throws RemoteException { + public void onError(@Nullable String error) { if (mListener == null) { return; } - GeocodeListener listener = mListener; + mListener.onError(error); mListener = null; + } - if (error != null) { - listener.onError(error); - } else { - if (addresses == null) { - addresses = Collections.emptyList(); - } - listener.onGeocode(addresses); + @Override + public void onResults(List<Address> addresses) { + if (mListener == null) { + return; } + + mListener.onGeocode(addresses); + mListener = null; } } @@ -364,21 +377,21 @@ public final class Geocoder { SynchronousGeocoder() {} @Override - public void onGeocode(List<Address> addresses) { + public void onGeocode(@NonNull List<Address> addresses) { mResults = addresses; mLatch.countDown(); } @Override - public void onError(String errorMessage) { - mError = errorMessage; + public void onError(@Nullable String error) { + mError = error; mLatch.countDown(); } public List<Address> getResults() throws IOException { try { if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - mError = "Service not Available"; + throw new IOException(new TimeoutException()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/location/java/android/location/IGeocodeProvider.aidl b/location/java/android/location/IGeocodeProvider.aidl deleted file mode 100644 index e661ca6cca2e..000000000000 --- a/location/java/android/location/IGeocodeProvider.aidl +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2009 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.location; - -import android.location.Address; -import android.location.IGeocodeListener; -import android.location.GeocoderParams; - -/** - * An interface for location providers implementing the Geocoder services. - * - * {@hide} - */ -interface IGeocodeProvider { - - oneway void getFromLocation(double latitude, double longitude, int maxResults, in GeocoderParams params, in IGeocodeListener listener); - oneway void getFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, - double upperRightLongitude, int maxResults, in GeocoderParams params, in IGeocodeListener listener); -} diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 72761ef47569..c96c11898671 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -19,13 +19,11 @@ package android.location; import android.app.PendingIntent; import android.location.Address; import android.location.Criteria; -import android.location.GeocoderParams; import android.location.Geofence; import android.location.GnssAntennaInfo; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementRequest; -import android.location.IGeocodeListener; import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; import android.location.IGnssStatusListener; @@ -37,8 +35,11 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationRequest; import android.location.LocationTime; +import android.location.provider.ForwardGeocodeRequest; +import android.location.provider.IGeocodeCallback; import android.location.provider.IProviderRequestListener; import android.location.provider.ProviderProperties; +import android.location.provider.ReverseGeocodeRequest; import android.os.Bundle; import android.os.ICancellationSignal; import android.os.PackageTagsList; @@ -68,13 +69,9 @@ interface ILocationManager void requestGeofence(in Geofence geofence, in PendingIntent intent, String packageName, String attributionTag); void removeGeofence(in PendingIntent intent); - boolean geocoderIsPresent(); - void getFromLocation(double latitude, double longitude, int maxResults, - in GeocoderParams params, in IGeocodeListener listener); - void getFromLocationName(String locationName, - double lowerLeftLatitude, double lowerLeftLongitude, - double upperRightLatitude, double upperRightLongitude, int maxResults, - in GeocoderParams params, in IGeocodeListener listener); + boolean isGeocodeAvailable(); + void reverseGeocode(in ReverseGeocodeRequest request, in IGeocodeCallback callback); + void forwardGeocode(in ForwardGeocodeRequest request, in IGeocodeCallback callback); GnssCapabilities getGnssCapabilities(); int getGnssYearOfHardware(); diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index 0fa641b926a9..156be389fe84 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -1,6 +1,13 @@ package: "android.location.flags" flag { + name: "new_geocoder" + namespace: "location" + description: "Flag for new Geocoder APIs" + bug: "229872126" +} + +flag { name: "location_bypass" namespace: "location" description: "Enable location bypass appops behavior" diff --git a/location/java/android/location/provider/ForwardGeocodeRequest.aidl b/location/java/android/location/provider/ForwardGeocodeRequest.aidl new file mode 100644 index 000000000000..acd6190aeec8 --- /dev/null +++ b/location/java/android/location/provider/ForwardGeocodeRequest.aidl @@ -0,0 +1,19 @@ +/* + * 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.location.provider; + +parcelable ForwardGeocodeRequest; diff --git a/location/java/android/location/provider/ForwardGeocodeRequest.java b/location/java/android/location/provider/ForwardGeocodeRequest.java new file mode 100644 index 000000000000..8f227b1604b7 --- /dev/null +++ b/location/java/android/location/provider/ForwardGeocodeRequest.java @@ -0,0 +1,286 @@ +/* + * Copyright 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 android.location.provider; + +import static java.lang.Math.max; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.Locale; +import java.util.Objects; + +/** + * Forward geocode (ie from address to lat/lng) provider request. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_NEW_GEOCODER) +@SystemApi +public final class ForwardGeocodeRequest implements Parcelable { + + private final String mLocationName; + private final double mLowerLeftLatitude; + private final double mLowerLeftLongitude; + private final double mUpperRightLatitude; + private final double mUpperRightLongitude; + private final int mMaxResults; + private final Locale mLocale; + private final int mCallingUid; + private final String mCallingPackage; + @Nullable private final String mCallingAttributionTag; + + private ForwardGeocodeRequest( + @NonNull String locationName, + double lowerLeftLatitude, + double lowerLeftLongitude, + double upperRightLatitude, + double upperRightLongitude, + int maxResults, + @NonNull Locale locale, + int callingUid, + @NonNull String callingPackage, + @Nullable String callingAttributionTag) { + Preconditions.checkArgument(locationName != null, "locationName must not be null"); + Preconditions.checkArgumentInRange(lowerLeftLatitude, -90.0, 90.0, "lowerLeftLatitude"); + Preconditions.checkArgumentInRange(lowerLeftLongitude, -180.0, 180.0, "lowerLeftLongitude"); + Preconditions.checkArgumentInRange(upperRightLatitude, -90.0, 90.0, "upperRightLatitude"); + Preconditions.checkArgumentInRange( + upperRightLongitude, -180.0, 180.0, "upperRightLongitude"); + + mLocationName = locationName; + mLowerLeftLatitude = lowerLeftLatitude; + mLowerLeftLongitude = lowerLeftLongitude; + mUpperRightLatitude = upperRightLatitude; + mUpperRightLongitude = upperRightLongitude; + mMaxResults = max(maxResults, 1); + mLocale = Objects.requireNonNull(locale); + + mCallingUid = callingUid; + mCallingPackage = Objects.requireNonNull(callingPackage); + mCallingAttributionTag = callingAttributionTag; + } + + /** + * The location name to be forward geocoded. An arbitrary user string that could have any value. + */ + @NonNull + public String getLocationName() { + return mLocationName; + } + + /** The lower left latitude of the bounding box that should constrain forward geocoding. */ + @FloatRange(from = -90.0, to = 90.0) + public double getLowerLeftLatitude() { + return mLowerLeftLatitude; + } + + /** The lower left longitude of the bounding box that should constrain forward geocoding. */ + @FloatRange(from = -180.0, to = 180.0) + public double getLowerLeftLongitude() { + return mLowerLeftLongitude; + } + + /** The upper right latitude of the bounding box that should constrain forward geocoding. */ + @FloatRange(from = -90.0, to = 90.0) + public double getUpperRightLatitude() { + return mUpperRightLatitude; + } + + /** The upper right longitude of the bounding box that should constrain forward geocoding. */ + @FloatRange(from = -180.0, to = 180.0) + public double getUpperRightLongitude() { + return mUpperRightLongitude; + } + + /** The maximum number of forward geocoding results that should be returned. */ + @IntRange(from = 1) + public int getMaxResults() { + return mMaxResults; + } + + /** The locale that results should be localized to (best effort). */ + @NonNull + public Locale getLocale() { + return mLocale; + } + + /** The UID of the caller this geocoding request is happening on behalf of. */ + public int getCallingUid() { + return mCallingUid; + } + + /** The package of the caller this geocoding request is happening on behalf of. */ + @NonNull + public String getCallingPackage() { + return mCallingPackage; + } + + /** The attribution tag of the caller this geocoding request is happening on behalf of. */ + @Nullable + public String getCallingAttributionTag() { + return mCallingAttributionTag; + } + + public static final @NonNull Creator<ForwardGeocodeRequest> CREATOR = + new Creator<>() { + @Override + public ForwardGeocodeRequest createFromParcel(Parcel in) { + return new ForwardGeocodeRequest( + Objects.requireNonNull(in.readString8()), + in.readDouble(), + in.readDouble(), + in.readDouble(), + in.readDouble(), + in.readInt(), + new Locale(in.readString8(), in.readString8(), in.readString8()), + in.readInt(), + Objects.requireNonNull(in.readString8()), + in.readString8()); + } + + @Override + public ForwardGeocodeRequest[] newArray(int size) { + return new ForwardGeocodeRequest[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeString8(mLocationName); + parcel.writeDouble(mLowerLeftLatitude); + parcel.writeDouble(mLowerLeftLongitude); + parcel.writeDouble(mUpperRightLatitude); + parcel.writeDouble(mUpperRightLongitude); + parcel.writeInt(mMaxResults); + parcel.writeString8(mLocale.getLanguage()); + parcel.writeString8(mLocale.getCountry()); + parcel.writeString8(mLocale.getVariant()); + parcel.writeInt(mCallingUid); + parcel.writeString8(mCallingPackage); + parcel.writeString8(mCallingAttributionTag); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof ForwardGeocodeRequest that) { + return mLowerLeftLatitude == that.mLowerLeftLatitude + && mLowerLeftLongitude == that.mLowerLeftLongitude + && mUpperRightLatitude == that.mUpperRightLatitude + && mUpperRightLongitude == that.mUpperRightLongitude + && mMaxResults == that.mMaxResults + && mCallingUid == that.mCallingUid + && mLocale.equals(that.mLocale) + && mCallingPackage.equals(that.mCallingPackage) + && mLocationName.equals(that.mLocationName) + && Objects.equals(mCallingAttributionTag, that.mCallingAttributionTag); + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash( + mLocationName, + mLowerLeftLatitude, + mLowerLeftLongitude, + mUpperRightLatitude, + mUpperRightLongitude, + mMaxResults, + mLocale, + mCallingUid, + mCallingPackage, + mCallingAttributionTag); + } + + /** A Builder for {@link ReverseGeocodeRequest}s. */ + public static final class Builder { + + private final String mLocationName; + private final double mLowerLeftLatitude; + private final double mLowerLeftLongitude; + private final double mUpperRightLatitude; + private final double mUpperRightLongitude; + private final int mMaxResults; + private final Locale mLocale; + + private final int mCallingUid; + private final String mCallingPackage; + @Nullable private String mCallingAttributionTag; + + /** Creates a new Builder instance with the given parameters. */ + public Builder( + @NonNull String locationName, + @FloatRange(from = -90.0, to = 90.0) double lowerLeftLatitude, + @FloatRange(from = -180.0, to = 180.0) double lowerLeftLongitude, + @FloatRange(from = -90.0, to = 90.0) double upperRightLatitude, + @FloatRange(from = -180.0, to = 180.0) double upperRightLongitude, + @IntRange(from = 1) int maxResults, + @NonNull Locale locale, + int callingUid, + @NonNull String callingPackage) { + mLocationName = locationName; + mLowerLeftLatitude = lowerLeftLatitude; + mLowerLeftLongitude = lowerLeftLongitude; + mUpperRightLatitude = upperRightLatitude; + mUpperRightLongitude = upperRightLongitude; + mMaxResults = maxResults; + mLocale = locale; + mCallingUid = callingUid; + mCallingPackage = callingPackage; + mCallingAttributionTag = null; + } + + /** Sets the attribution tag. */ + @NonNull + public Builder setCallingAttributionTag(@NonNull String attributionTag) { + mCallingAttributionTag = attributionTag; + return this; + } + + /** Builds a {@link ForwardGeocodeRequest}. */ + @NonNull + public ForwardGeocodeRequest build() { + return new ForwardGeocodeRequest( + mLocationName, + mLowerLeftLatitude, + mLowerLeftLongitude, + mUpperRightLatitude, + mUpperRightLongitude, + mMaxResults, + mLocale, + mCallingUid, + mCallingPackage, + mCallingAttributionTag); + } + } +} diff --git a/location/java/android/location/provider/GeocodeProviderBase.java b/location/java/android/location/provider/GeocodeProviderBase.java new file mode 100644 index 000000000000..e2c48b9c3515 --- /dev/null +++ b/location/java/android/location/provider/GeocodeProviderBase.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2010 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.location.provider; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.content.Context; +import android.content.Intent; +import android.location.Address; +import android.location.flags.Flags; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.OutcomeReceiver; +import android.os.RemoteException; +import android.util.Log; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Base class for geocode providers outside the system server. + * + * <p>Geocode providers should be wrapped in a non-exported service which returns the result of + * {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The + * service should not be exported so that components other than the system server cannot bind to it. + * Alternatively, the service may be guarded by a permission that only system server can obtain. The + * service may specify metadata on its capabilities: + * + * <ul> + * <li>"serviceVersion": An integer version code to help tie break if multiple services are + * capable of implementing the geocode provider. All else equal, the service with the highest + * version code will be chosen. Assumed to be 0 if not specified. + * <li>"serviceIsMultiuser": A boolean property, indicating if the service wishes to take + * responsibility for handling changes to the current user on the device. If true, the service + * will always be bound from the system user. If false, the service will always be bound from + * the current user. If the current user changes, the old binding will be released, and a new + * binding established under the new user. Assumed to be false if not specified. + * </ul> + * + * <p>The service should have an intent filter in place for the geocode provider as specified by the + * constant in this class. + * + * <p>Geocode providers are identified by their UID / package name / attribution tag. Based on this + * identity, geocode providers may be given some special privileges. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_NEW_GEOCODER) +@SystemApi +public abstract class GeocodeProviderBase { + + /** + * The action the wrapping service should have in its intent filter to implement the geocode + * provider. + */ + @SuppressLint("ActionValue") + public static final String ACTION_GEOCODE_PROVIDER = + "com.android.location.service.GeocodeProvider"; + + final String mTag; + @Nullable final String mAttributionTag; + final IBinder mBinder; + + /** + * Subclasses should pass in a context and an arbitrary tag that may be used for logcat logging + * of errors, and thus should uniquely identify the class. + */ + public GeocodeProviderBase(@NonNull Context context, @NonNull String tag) { + mTag = tag; + mAttributionTag = context.getAttributionTag(); + mBinder = new Service(); + } + + /** + * Returns the IBinder instance that should be returned from the {@link + * android.app.Service#onBind(Intent)} method of the wrapping service. + */ + @NonNull + public final IBinder getBinder() { + return mBinder; + } + + /** + * Requests forward geocoding of the given arguments. The given callback must be invoked once. + */ + public abstract void onForwardGeocode( + @NonNull ForwardGeocodeRequest request, + @NonNull OutcomeReceiver<List<Address>, Exception> callback); + + /** + * Requests reverse geocoding of the given arguments. The given callback must be invoked once. + */ + public abstract void onReverseGeocode( + @NonNull ReverseGeocodeRequest request, + @NonNull OutcomeReceiver<List<Address>, Exception> callback); + + private class Service extends IGeocodeProvider.Stub { + @Override + public void forwardGeocode(ForwardGeocodeRequest request, IGeocodeCallback callback) { + try { + onForwardGeocode(request, new SingleUseCallback(callback)); + } catch (RuntimeException e) { + // exceptions on one-way binder threads are dropped - move to a different thread + Log.w(mTag, e); + new Handler(Looper.getMainLooper()) + .post( + () -> { + throw new AssertionError(e); + }); + } + } + + @Override + public void reverseGeocode(ReverseGeocodeRequest request, IGeocodeCallback callback) { + try { + onReverseGeocode(request, new SingleUseCallback(callback)); + } catch (RuntimeException e) { + // exceptions on one-way binder threads are dropped - move to a different thread + Log.w(mTag, e); + new Handler(Looper.getMainLooper()) + .post( + () -> { + throw new AssertionError(e); + }); + } + } + } + + private static class SingleUseCallback implements OutcomeReceiver<List<Address>, Exception> { + + private final AtomicReference<IGeocodeCallback> mCallback; + + SingleUseCallback(IGeocodeCallback callback) { + mCallback = new AtomicReference<>(callback); + } + + @Override + public void onError(Exception e) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onError(e.toString()); + } catch (RemoteException r) { + throw r.rethrowFromSystemServer(); + } + } + + @Override + public void onResult(List<Address> results) { + try { + Objects.requireNonNull(mCallback.getAndSet(null)).onResults(results); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/location/java/android/location/IGeocodeListener.aidl b/location/java/android/location/provider/IGeocodeCallback.aidl index 8e104119a6f3..cf527130bcc3 100644 --- a/location/java/android/location/IGeocodeListener.aidl +++ b/location/java/android/location/provider/IGeocodeCallback.aidl @@ -14,16 +14,15 @@ * limitations under the License. */ -package android.location; +package android.location.provider; import android.location.Address; /** - * An interface for returning geocode results. - * - * {@hide} + * Binder interface for geocoding callbacks. + * @hide */ -interface IGeocodeListener { - - oneway void onResults(String error, in List<Address> results); +oneway interface IGeocodeCallback { + void onError(String error); + void onResults(in List<Address> results); } diff --git a/location/java/android/location/provider/IGeocodeProvider.aidl b/location/java/android/location/provider/IGeocodeProvider.aidl new file mode 100644 index 000000000000..92174387c578 --- /dev/null +++ b/location/java/android/location/provider/IGeocodeProvider.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2009 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.location.provider; + +import android.location.provider.IGeocodeCallback; +import android.location.provider.ForwardGeocodeRequest; +import android.location.provider.ReverseGeocodeRequest; + +/** + * Binder interface for services that implement geocode providers. Do not implement this directly, + * extend {@link GeocodeProviderBase} instead. + * @hide + */ +oneway interface IGeocodeProvider { + void forwardGeocode(in ForwardGeocodeRequest request, in IGeocodeCallback callback); + void reverseGeocode(in ReverseGeocodeRequest request, in IGeocodeCallback callback); +} diff --git a/location/java/android/location/GeocoderParams.aidl b/location/java/android/location/provider/ReverseGeocodeRequest.aidl index 2484e207dae7..015757ad5d2d 100644 --- a/location/java/android/location/GeocoderParams.aidl +++ b/location/java/android/location/provider/ReverseGeocodeRequest.aidl @@ -1,11 +1,10 @@ /* - * Copyright (C) 2010, The Android Open Source Project - * + * Copyright (C) 2024 * 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 + * 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, @@ -14,6 +13,6 @@ * limitations under the License. */ -package android.location; +package android.location.provider; -parcelable GeocoderParams; +parcelable ReverseGeocodeRequest; diff --git a/location/java/android/location/provider/ReverseGeocodeRequest.java b/location/java/android/location/provider/ReverseGeocodeRequest.java new file mode 100644 index 000000000000..57c9047f07ca --- /dev/null +++ b/location/java/android/location/provider/ReverseGeocodeRequest.java @@ -0,0 +1,230 @@ +/* + * Copyright 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 android.location.provider; + +import static java.lang.Math.max; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.Locale; +import java.util.Objects; + +/** + * Reverse geocode (ie from lat/lng to address) provider request. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_NEW_GEOCODER) +@SystemApi +public final class ReverseGeocodeRequest implements Parcelable { + + private final double mLatitude; + private final double mLongitude; + private final int mMaxResults; + private final Locale mLocale; + + private final int mCallingUid; + private final String mCallingPackage; + @Nullable private final String mCallingAttributionTag; + + private ReverseGeocodeRequest( + double latitude, + double longitude, + int maxResults, + Locale locale, + int callingUid, + String callingPackage, + @Nullable String callingAttributionTag) { + Preconditions.checkArgumentInRange(latitude, -90.0, 90.0, "latitude"); + Preconditions.checkArgumentInRange(longitude, -180.0, 180.0, "longitude"); + + mLatitude = latitude; + mLongitude = longitude; + mMaxResults = max(maxResults, 1); + mLocale = Objects.requireNonNull(locale); + + mCallingUid = callingUid; + mCallingPackage = Objects.requireNonNull(callingPackage); + mCallingAttributionTag = callingAttributionTag; + } + + /** The latitude of the point to be reverse geocoded. */ + @FloatRange(from = -90.0, to = 90.0) + public double getLatitude() { + return mLatitude; + } + + /** The longitude of the point to be reverse geocoded. */ + @FloatRange(from = -180.0, to = 180.0) + public double getLongitude() { + return mLongitude; + } + + /** The maximum number of reverse geocoding results that should be returned. */ + @IntRange(from = 1) + public int getMaxResults() { + return mMaxResults; + } + + /** The locale that results should be localized to (best effort). */ + @NonNull + public Locale getLocale() { + return mLocale; + } + + /** The UID of the caller this geocoding request is happening on behalf of. */ + public int getCallingUid() { + return mCallingUid; + } + + /** The package of the caller this geocoding request is happening on behalf of. */ + @NonNull + public String getCallingPackage() { + return mCallingPackage; + } + + /** The attribution tag of the caller this geocoding request is happening on behalf of. */ + @Nullable + public String getCallingAttributionTag() { + return mCallingAttributionTag; + } + + public static final @NonNull Creator<ReverseGeocodeRequest> CREATOR = + new Creator<>() { + @Override + public ReverseGeocodeRequest createFromParcel(Parcel in) { + return new ReverseGeocodeRequest( + in.readDouble(), + in.readDouble(), + in.readInt(), + new Locale(in.readString8(), in.readString8(), in.readString8()), + in.readInt(), + Objects.requireNonNull(in.readString8()), + in.readString8()); + } + + @Override + public ReverseGeocodeRequest[] newArray(int size) { + return new ReverseGeocodeRequest[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeDouble(mLatitude); + parcel.writeDouble(mLongitude); + parcel.writeInt(mMaxResults); + parcel.writeString8(mLocale.getLanguage()); + parcel.writeString8(mLocale.getCountry()); + parcel.writeString8(mLocale.getVariant()); + parcel.writeInt(mCallingUid); + parcel.writeString8(mCallingPackage); + parcel.writeString8(mCallingAttributionTag); + } + + @Override + public boolean equals(@Nullable Object object) { + if (object instanceof ReverseGeocodeRequest that) { + return mLatitude == that.mLatitude + && mLongitude == that.mLongitude + && mMaxResults == that.mMaxResults + && mCallingUid == that.mCallingUid + && mLocale.equals(that.mLocale) + && mCallingPackage.equals(that.mCallingPackage) + && Objects.equals(mCallingAttributionTag, that.mCallingAttributionTag); + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash( + mLatitude, + mLongitude, + mMaxResults, + mLocale, + mCallingUid, + mCallingPackage, + mCallingAttributionTag); + } + + /** A Builder for {@link ReverseGeocodeRequest}s. */ + public static final class Builder { + + private final double mLatitude; + private final double mLongitude; + private final int mMaxResults; + private final Locale mLocale; + + private final int mCallingUid; + private final String mCallingPackage; + @Nullable private String mCallingAttributionTag; + + /** Creates a new Builder instance with the given parameters. */ + public Builder( + @FloatRange(from = -90.0, to = 90.0) double latitude, + @FloatRange(from = -180.0, to = 180.0) double longitude, + @IntRange(from = 0) int maxResults, + @NonNull Locale locale, + int callingUid, + @NonNull String callingPackage) { + mLatitude = latitude; + mLongitude = longitude; + mMaxResults = maxResults; + mLocale = locale; + mCallingUid = callingUid; + mCallingPackage = callingPackage; + mCallingAttributionTag = null; + } + + /** Sets the attribution tag. */ + @NonNull + public Builder setCallingAttributionTag(@NonNull String attributionTag) { + mCallingAttributionTag = attributionTag; + return this; + } + + /** Builds a {@link ReverseGeocodeRequest}. */ + @NonNull + public ReverseGeocodeRequest build() { + return new ReverseGeocodeRequest( + mLatitude, + mLongitude, + mMaxResults, + mLocale, + mCallingUid, + mCallingPackage, + mCallingAttributionTag); + } + } +} diff --git a/location/lib/Android.bp b/location/lib/Android.bp index c9be5797fdbc..b10019a94209 100644 --- a/location/lib/Android.bp +++ b/location/lib/Android.bp @@ -30,6 +30,7 @@ java_sdk_library { "androidx.annotation_annotation", ], api_packages: [ + "android.location", "com.android.location.provider", "com.android.location.timezone.provider", ], diff --git a/location/lib/README.txt b/location/lib/README.txt index 400a7dd3f6ba..ae5187245163 100644 --- a/location/lib/README.txt +++ b/location/lib/README.txt @@ -1,30 +1,12 @@ This library (com.android.location.provider.jar) is a shared java library -containing classes required by unbundled location providers. - ---- Rules of this library --- -o This library is effectively a PUBLIC API for unbundled location providers - that may be distributed outside the system image. So it MUST BE API STABLE. - You can add but not remove. The rules are the same as for the - public platform SDK API. -o This library can see and instantiate internal platform classes (such as - ProviderRequest.java), but it must not expose them in any public method - (or by extending them via inheritance). This would break clients of the - library because they cannot see the internal platform classes. - -This library is distributed in the system image, and loaded as -a shared library. So you can change the implementation, but not -the interface. In this way it is like framework.jar. - ---- Why does this library exists? --- - -Unbundled location providers (such as the NetworkLocationProvider) -can not use internal platform classes. - -So ideally all of these classes would be part of the public platform SDK API, -but that doesn't seem like a great idea when only applications with a special -signature can implement this API. - -The compromise is this library. - -It wraps internal platform classes (like ProviderRequest) with a stable -API that does not leak the internal classes. +containing classes required by unbundled providers. The library was created +as a way of exposing API classes outside of the public API before SystemApi +was possible. Now that SystemApi exists, no new classes should ever be added +to this library, and all classes in this library should eventually be +deprecated and new SystemApi replacements offered. + +Whether or not classes in this library can ever be removed must be answered on +a case by case basis. Most of the classes are usually referenced by Google Play +services (in which case references can be removed from that code base), but +these classes may also be referenced by OEM code, which must be considered +before any removal. diff --git a/location/lib/api/system-current.txt b/location/lib/api/system-current.txt index 7046abd8fd3b..75e6bb437ca4 100644 --- a/location/lib/api/system-current.txt +++ b/location/lib/api/system-current.txt @@ -1,4 +1,21 @@ // Signature format: 2.0 +package android.location { + + @Deprecated public class GeocoderParams implements android.os.Parcelable { + ctor @Deprecated public GeocoderParams(android.content.Context); + ctor @Deprecated public GeocoderParams(android.content.Context, java.util.Locale); + ctor @Deprecated public GeocoderParams(int, String, @Nullable String, java.util.Locale); + method @Deprecated public int describeContents(); + method @Deprecated @Nullable public String getClientAttributionTag(); + method @Deprecated @NonNull public String getClientPackage(); + method @Deprecated public int getClientUid(); + method @Deprecated @NonNull public java.util.Locale getLocale(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.location.GeocoderParams> CREATOR; + } + +} + package com.android.location.provider { @Deprecated public final class FusedLocationHardware { @@ -25,6 +42,13 @@ package com.android.location.provider { method @Deprecated public void onStatusChanged(int); } + @Deprecated public abstract class GeocodeProvider { + ctor @Deprecated public GeocodeProvider(); + method @Deprecated public android.os.IBinder getBinder(); + method @Deprecated public abstract String onGetFromLocation(double, double, int, android.location.GeocoderParams, java.util.List<android.location.Address>); + method @Deprecated public abstract String onGetFromLocationName(String, double, double, double, double, int, android.location.GeocoderParams, java.util.List<android.location.Address>); + } + @Deprecated public class GmsFusedBatchOptions { ctor @Deprecated public GmsFusedBatchOptions(); method @Deprecated public int getFlags(); diff --git a/core/java/android/location/GeocoderParams.java b/location/lib/java/android/location/GeocoderParams.java index 3ea6364e07c5..780ccf4ca53c 100644 --- a/core/java/android/location/GeocoderParams.java +++ b/location/lib/java/android/location/GeocoderParams.java @@ -18,7 +18,7 @@ package android.location; import android.annotation.NonNull; import android.annotation.Nullable; -import android.compat.annotation.UnsupportedAppUsage; +import android.annotation.SystemApi; import android.content.Context; import android.os.Parcel; import android.os.Parcelable; @@ -28,15 +28,15 @@ import java.util.Locale; import java.util.Objects; /** - * This class contains extra parameters to pass to an IGeocodeProvider - * implementation from the Geocoder class. Currently this contains the - * language, country and variant information from the Geocoder's locale - * as well as the Geocoder client's package name for geocoder server - * logging. This information is kept in a separate class to allow for - * future expansion of the IGeocodeProvider interface. + * This class was originally shipped out-of-band from the normal API processes as a separate drop + * before SystemApi existed. Now that SystemApi does exist, this class has been retroactively + * published through SystemApi. * + * @deprecated Do not use. * @hide */ +@Deprecated +@SystemApi public class GeocoderParams implements Parcelable { private final int mUid; @@ -52,7 +52,8 @@ public class GeocoderParams implements Parcelable { this(Process.myUid(), context.getPackageName(), context.getAttributionTag(), locale); } - private GeocoderParams(int uid, String packageName, String attributionTag, Locale locale) { + public GeocoderParams( + int uid, String packageName, @Nullable String attributionTag, Locale locale) { mUid = uid; mPackageName = Objects.requireNonNull(packageName); mAttributionTag = attributionTag; @@ -62,7 +63,6 @@ public class GeocoderParams implements Parcelable { /** * Returns the client UID. */ - @UnsupportedAppUsage public int getClientUid() { return mUid; } @@ -70,7 +70,6 @@ public class GeocoderParams implements Parcelable { /** * Returns the client package name. */ - @UnsupportedAppUsage public @NonNull String getClientPackage() { return mPackageName; } @@ -78,7 +77,6 @@ public class GeocoderParams implements Parcelable { /** * Returns the client attribution tag. */ - @UnsupportedAppUsage public @Nullable String getClientAttributionTag() { return mAttributionTag; } @@ -86,34 +84,38 @@ public class GeocoderParams implements Parcelable { /** * Returns the locale. */ - @UnsupportedAppUsage public @NonNull Locale getLocale() { return mLocale; } public static final @NonNull Parcelable.Creator<GeocoderParams> CREATOR = - new Parcelable.Creator<GeocoderParams>() { - public GeocoderParams createFromParcel(Parcel in) { - int uid = in.readInt(); - String packageName = in.readString8(); - String attributionTag = in.readString8(); - String language = in.readString8(); - String country = in.readString8(); - String variant = in.readString8(); - - return new GeocoderParams(uid, packageName, attributionTag, - new Locale(language, country, variant)); - } - - public GeocoderParams[] newArray(int size) { - return new GeocoderParams[size]; - } - }; - + new Parcelable.Creator<>() { + public GeocoderParams createFromParcel(Parcel in) { + int uid = in.readInt(); + String packageName = in.readString8(); + String attributionTag = in.readString8(); + String language = in.readString8(); + String country = in.readString8(); + String variant = in.readString8(); + + return new GeocoderParams( + uid, + packageName, + attributionTag, + new Locale(language, country, variant)); + } + + public GeocoderParams[] newArray(int size) { + return new GeocoderParams[size]; + } + }; + + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mUid); parcel.writeString8(mPackageName); diff --git a/location/lib/java/com/android/location/provider/GeocodeProvider.java b/location/lib/java/com/android/location/provider/GeocodeProvider.java index 05d793542202..45077ba8adc5 100644 --- a/location/lib/java/com/android/location/provider/GeocodeProvider.java +++ b/location/lib/java/com/android/location/provider/GeocodeProvider.java @@ -16,10 +16,13 @@ package com.android.location.provider; +import android.annotation.SystemApi; import android.location.Address; import android.location.GeocoderParams; -import android.location.IGeocodeListener; -import android.location.IGeocodeProvider; +import android.location.provider.ForwardGeocodeRequest; +import android.location.provider.IGeocodeCallback; +import android.location.provider.IGeocodeProvider; +import android.location.provider.ReverseGeocodeRequest; import android.os.IBinder; import android.os.RemoteException; @@ -27,47 +30,74 @@ import java.util.ArrayList; import java.util.List; /** - * Base class for geocode providers implemented as unbundled services. + * This class was originally shipped out-of-band from the normal API processes as a separate drop + * before SystemApi existed. Now that SystemApi does exist, this class has been retroactively + * published through SystemApi. * - * <p>Geocode providers can be implemented as services and return the result of - * {@link GeocodeProvider#getBinder()} in its getBinder() method. - * - * <p>IMPORTANT: This class is effectively a public API for unbundled - * applications, and must remain API stable. See README.txt in the root - * of this package for more information. + * @deprecated Use {@link android.location.provider.GeocodeProviderBase} instead. * @hide */ +@Deprecated +@SystemApi public abstract class GeocodeProvider { - private IGeocodeProvider.Stub mProvider = new IGeocodeProvider.Stub() { - @Override - public void getFromLocation(double latitude, double longitude, int maxResults, - GeocoderParams params, IGeocodeListener listener) { - List<Address> results = new ArrayList<>(); - String error = onGetFromLocation(latitude, longitude, maxResults, params, results); - try { - listener.onResults(error, results); - } catch (RemoteException e) { - // ignore - } - } + private final IGeocodeProvider.Stub mProvider = + new IGeocodeProvider.Stub() { + @Override + public void reverseGeocode( + ReverseGeocodeRequest request, IGeocodeCallback callback) { + List<Address> results = new ArrayList<>(); + String error = + onGetFromLocation( + request.getLatitude(), + request.getLongitude(), + request.getMaxResults(), + new GeocoderParams( + request.getCallingUid(), + request.getCallingPackage(), + request.getCallingAttributionTag(), + request.getLocale()), + results); + try { + if (error != null) { + callback.onError(error); + } else { + callback.onResults(results); + } + } catch (RemoteException e) { + // ignore + } + } - @Override - public void getFromLocationName(String locationName, - double lowerLeftLatitude, double lowerLeftLongitude, - double upperRightLatitude, double upperRightLongitude, int maxResults, - GeocoderParams params, IGeocodeListener listener) { - List<Address> results = new ArrayList<>(); - String error = onGetFromLocationName(locationName, lowerLeftLatitude, - lowerLeftLongitude, upperRightLatitude, upperRightLongitude, - maxResults, params, results); - try { - listener.onResults(error, results); - } catch (RemoteException e) { - // ignore - } - } - }; + @Override + public void forwardGeocode( + ForwardGeocodeRequest request, IGeocodeCallback callback) { + List<Address> results = new ArrayList<>(); + String error = + onGetFromLocationName( + request.getLocationName(), + request.getLowerLeftLatitude(), + request.getLowerLeftLongitude(), + request.getUpperRightLatitude(), + request.getUpperRightLongitude(), + request.getMaxResults(), + new GeocoderParams( + request.getCallingUid(), + request.getCallingPackage(), + request.getCallingAttributionTag(), + request.getLocale()), + results); + try { + if (error != null) { + callback.onError(error); + } else { + callback.onResults(results); + } + } catch (RemoteException e) { + // ignore + } + } + }; /** * This method is overridden to implement the diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index ac94a6f6ad3c..9548525d68d1 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -3557,6 +3557,36 @@ final public class MediaCodec { } /** + * Set a linear block that contain multiple non-encrypted access unit to this + * queue request. Exactly one buffer must be set for a queue request before + * calling {@link #queue}. Multiple access units if present must be laid out contiguously + * and without gaps and in order. An IllegalArgumentException will be thrown + * during {@link #queue} if access units are not laid out contiguously. + * + * @param block The linear block object + * @param infos Represents {@link MediaCodec.BufferInfo} objects to mark + * individual access-unit boundaries and the timestamps associated with it. + * @return this object + * @throws IllegalStateException if a buffer is already set + */ + @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) + public @NonNull QueueRequest setMultiFrameLinearBlock( + @NonNull LinearBlock block, + @NonNull ArrayDeque<BufferInfo> infos) { + if (!isAccessible()) { + throw new IllegalStateException("The request is stale"); + } + if (mLinearBlock != null || mHardwareBuffer != null) { + throw new IllegalStateException("Cannot set block twice"); + } + mLinearBlock = block; + mBufferInfos.clear(); + mBufferInfos.addAll(infos); + mCryptoInfos.clear(); + return this; + } + + /** * Set an encrypted linear block to this queue request. Exactly one buffer must be * set for a queue request before calling {@link #queue}. It is possible * to use the same {@link LinearBlock} object for multiple queue @@ -3691,26 +3721,6 @@ final public class MediaCodec { } /** - * Sets MediaCodec.BufferInfo objects describing the access units - * contained in this queue request. Access units must be laid out - * contiguously without gaps and in order. - * - * @param infos Represents {@link MediaCodec.BufferInfo} objects to mark - * individual access-unit boundaries and the timestamps associated with it. - * The buffer is expected to contain the data in a continuous manner. - * @return this object - */ - @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) - public @NonNull QueueRequest setBufferInfos(@NonNull ArrayDeque<BufferInfo> infos) { - if (!isAccessible()) { - throw new IllegalStateException("The request is stale"); - } - mBufferInfos.clear(); - mBufferInfos.addAll(infos); - return this; - } - - /** * Add an integer parameter. * See {@link MediaFormat} for an exhaustive list of supported keys with * values of type int, that can also be set with {@link MediaFormat#setInteger}. diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 3174c377b91c..1e7bc4764dd7 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -20,10 +20,12 @@ import static android.media.Utils.intersectSortedDistinctRanges; import static android.media.Utils.sortDistinctRanges; import static android.media.codec.Flags.FLAG_DYNAMIC_COLOR_ASPECTS; import static android.media.codec.Flags.FLAG_HLG_EDITING; +import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC; import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE; import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -40,6 +42,8 @@ import android.util.Range; import android.util.Rational; import android.util.Size; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1808,6 +1812,55 @@ public final class MediaCodecInfo { } } + /** @hide */ + @IntDef(prefix = {"SECURITY_MODEL_"}, value = { + SECURITY_MODEL_SANDBOXED, + SECURITY_MODEL_MEMORY_SAFE, + SECURITY_MODEL_TRUSTED_CONTENT_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SecurityModel {} + + /** + * In this model the codec is running in a sandboxed process. Even if a + * malicious content was fed to the codecs in this model, the impact will + * be contained in the sandboxed process. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int SECURITY_MODEL_SANDBOXED = 0; + /** + * In this model the codec is not running in a sandboxed process, but + * written in a memory-safe way. It typically means that the software + * implementation of the codec is written in a memory-safe language such + * as Rust. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int SECURITY_MODEL_MEMORY_SAFE = 1; + /** + * In this model the codec is suitable only for trusted content where + * the input can be verified to be well-formed and no malicious actor + * can alter it. For example, codecs in this model are not suitable + * for arbitrary media downloaded from the internet or present in a user + * directory. On the other hand, they could be suitable for media encoded + * in the backend that the app developer wholly controls. + * <p> + * Codecs with this security model is not included in + * {@link MediaCodecList#REGULAR_CODECS}, but included in + * {@link MediaCodecList#ALL_CODECS}. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 2; + + /** + * Query the security model of the codec. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + @SecurityModel + public int getSecurityModel() { + // TODO b/297922713 --- detect security model of out-of-sandbox codecs + return SECURITY_MODEL_SANDBOXED; + } + /** * A class that supports querying the video capabilities of a codec. */ diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 5e40eee26886..7b83842a9fb2 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC; + import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE; import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME; @@ -1715,6 +1717,58 @@ public final class MediaFormat { @FlaggedApi(FLAG_CODEC_IMPORTANCE) public static final String KEY_IMPORTANCE = "importance"; + /** @hide */ + @IntDef(flag = true, prefix = {"FLAG_SECURITY_MODEL_"}, value = { + FLAG_SECURITY_MODEL_SANDBOXED, + FLAG_SECURITY_MODEL_MEMORY_SAFE, + FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SecurityModelFlag {} + + /** + * Flag for {@link MediaCodecInfo#SECURITY_MODEL_SANDBOXED}. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int FLAG_SECURITY_MODEL_SANDBOXED = + (1 << MediaCodecInfo.SECURITY_MODEL_SANDBOXED); + /** + * Flag for {@link MediaCodecInfo#SECURITY_MODEL_MEMORY_SAFE}. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int FLAG_SECURITY_MODEL_MEMORY_SAFE = + (1 << MediaCodecInfo.SECURITY_MODEL_MEMORY_SAFE); + /** + * Flag for {@link MediaCodecInfo#SECURITY_MODEL_TRUSTED_CONTENT_ONLY}. + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final int FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY = + (1 << MediaCodecInfo.SECURITY_MODEL_TRUSTED_CONTENT_ONLY); + + /** + * A key describing the requested security model as flags. + * <p> + * The associated value is a flag of the following values: + * {@link FLAG_SECURITY_MODEL_SANDBOXED}, + * {@link FLAG_SECURITY_MODEL_MEMORY_SAFE}, + * {@link FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY}. The default value is + * {@link FLAG_SECURITY_MODEL_SANDBOXED}. + * <p> + * When passed to {@link MediaCodecList#findDecoderForFormat} or + * {@link MediaCodecList#findEncoderForFormat}, MediaCodecList filters + * the security model of the codecs according to this flag value. + * <p> + * When passed to {@link MediaCodec#configure}, MediaCodec verifies + * the security model matches the flag value passed, and throws + * {@link java.lang.IllegalArgumentException} if the model does not match. + * <p> + * @see MediaCodecInfo#getSecurityModel + * @see MediaCodecList#findDecoderForFormat + * @see MediaCodecList#findEncoderForFormat + */ + @FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC) + public static final String KEY_SECURITY_MODEL = "security-model"; + /* package private */ MediaFormat(@NonNull Map<String, Object> map) { mMap = map; } diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig index c4b38c725ea4..b16580927fa6 100644 --- a/media/java/android/media/flags/projection.aconfig +++ b/media/java/android/media/flags/projection.aconfig @@ -1,4 +1,4 @@ -package: "com.android.media.flags" +package: "com.android.media.projection.flags" # Project link: https://gantry.corp.google.com/projects/android_platform_window_surfaces/changes diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index fa9afa872091..a8e94234e063 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -232,52 +232,8 @@ public abstract class MediaBrowserService extends Service { + " package=" + pkg); } - service.mHandler.post(new Runnable() { - @Override - public void run() { - final IBinder b = callbacks.asBinder(); - // Clear out the old subscriptions. We are getting new ones. - service.mServiceState.mConnections.remove(b); - - // Temporarily sets a placeholder ConnectionRecord to make - // getCurrentBrowserInfo() work in onGetRoot(). - service.mServiceState.mCurConnection = - new ConnectionRecord( - service, pkg, pid, uid, rootHints, callbacks, null); - BrowserRoot root = service.onGetRoot(pkg, uid, rootHints); - service.mServiceState.mCurConnection = null; - - // If they didn't return something, don't allow this client. - if (root == null) { - Log.i(TAG, "No root for client " + pkg + " from service " - + getClass().getName()); - try { - callbacks.onConnectFailed(); - } catch (RemoteException ex) { - Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. " - + "pkg=" + pkg); - } - } else { - try { - ConnectionRecord connection = - new ConnectionRecord( - service, pkg, pid, uid, rootHints, callbacks, root); - service.mServiceState.mConnections.put(b, connection); - b.linkToDeath(connection, 0); - if (service.mServiceState.mSession != null) { - callbacks.onConnect( - connection.root.getRootId(), - service.mServiceState.mSession, - connection.root.getExtras()); - } - } catch (RemoteException ex) { - Log.w(TAG, "Calling onConnect() failed. Dropping client. " - + "pkg=" + pkg); - service.mServiceState.mConnections.remove(b); - } - } - } - }); + service.mHandler.post( + () -> service.connectOnHandler(pkg, pid, uid, rootHints, callbacks)); } @Override @@ -315,22 +271,8 @@ public abstract class MediaBrowserService extends Service { return; } - service.mHandler.post(new Runnable() { - @Override - public void run() { - final IBinder b = callbacks.asBinder(); - - // Get the record for the connection - ConnectionRecord connection = service.mServiceState.mConnections.get(b); - if (connection == null) { - Log.w(TAG, "addSubscription for callback that isn't registered id=" - + id); - return; - } - - service.addSubscription(id, connection, token, options); - } - }); + service.mHandler.post( + () -> service.addSubscriptionOnHandler(id, callbacks, token, options)); } @Override @@ -347,23 +289,12 @@ public abstract class MediaBrowserService extends Service { return; } - service.mHandler.post(new Runnable() { - @Override - public void run() { - final IBinder b = callbacks.asBinder(); - - ConnectionRecord connection = service.mServiceState.mConnections.get(b); - if (connection == null) { - Log.w(TAG, "removeSubscription for callback that isn't registered id=" - + id); - return; - } - if (!service.removeSubscription(id, connection, token)) { - Log.w(TAG, "removeSubscription called for " + id - + " which is not subscribed"); - } - } - }); + service.mHandler.post( + () -> { + if (!service.removeSubscriptionOnHandler(id, callbacks, token)) { + Log.w(TAG, "removeSubscription for id with no subscription: " + id); + } + }); } @Override @@ -374,18 +305,8 @@ public abstract class MediaBrowserService extends Service { return; } - service.mHandler.post(new Runnable() { - @Override - public void run() { - final IBinder b = callbacks.asBinder(); - ConnectionRecord connection = service.mServiceState.mConnections.get(b); - if (connection == null) { - Log.w(TAG, "getMediaItem for callback that isn't registered id=" + mediaId); - return; - } - service.performLoadItem(mediaId, connection, receiver); - } - }); + service.mHandler.post( + () -> service.performLoadItemOnHandler(mediaId, callbacks, receiver)); } } @@ -527,22 +448,7 @@ public abstract class MediaBrowserService extends Service { throw new IllegalStateException("The session token has already been set."); } mServiceState.mSession = token; - mHandler.post(new Runnable() { - @Override - public void run() { - Iterator<ConnectionRecord> iter = mServiceState.mConnections.values().iterator(); - while (iter.hasNext()) { - ConnectionRecord connection = iter.next(); - try { - connection.callbacks.onConnect(connection.root.getRootId(), token, - connection.root.getExtras()); - } catch (RemoteException e) { - Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid."); - iter.remove(); - } - } - } - }); + mHandler.post(() -> notifySessionTokenInitializedOnHandler(token)); } /** @@ -599,7 +505,7 @@ public abstract class MediaBrowserService extends Service { * children changed. */ public void notifyChildrenChanged(@NonNull String parentId) { - notifyChildrenChangedInternal(parentId, null); + notifyChildrenChanged(parentId, Bundle.EMPTY); } /** @@ -617,30 +523,10 @@ public abstract class MediaBrowserService extends Service { if (options == null) { throw new IllegalArgumentException("options cannot be null in notifyChildrenChanged"); } - notifyChildrenChangedInternal(parentId, options); - } - - private void notifyChildrenChangedInternal(final String parentId, final Bundle options) { if (parentId == null) { throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged"); } - mHandler.post(new Runnable() { - @Override - public void run() { - for (IBinder binder : mServiceState.mConnections.keySet()) { - ConnectionRecord connection = mServiceState.mConnections.get(binder); - List<Pair<IBinder, Bundle>> callbackList = - connection.subscriptions.get(parentId); - if (callbackList != null) { - for (Pair<IBinder, Bundle> callback : callbackList) { - if (MediaBrowserUtils.hasDuplicatedItems(options, callback.second)) { - performLoadChildren(parentId, connection, callback.second); - } - } - } - } - } - }); + mHandler.post(() -> notifyChildrenChangeOnHandler(parentId, options)); } /** @@ -661,11 +547,45 @@ public abstract class MediaBrowserService extends Service { return false; } - /** - * Save the subscription and if it is a new subscription send the results. - */ - private void addSubscription(String id, ConnectionRecord connection, IBinder token, - Bundle options) { + private void notifySessionTokenInitializedOnHandler(MediaSession.Token token) { + Iterator<ConnectionRecord> iter = mServiceState.mConnections.values().iterator(); + while (iter.hasNext()) { + ConnectionRecord connection = iter.next(); + try { + connection.callbacks.onConnect( + connection.root.getRootId(), token, connection.root.getExtras()); + } catch (RemoteException e) { + Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid."); + iter.remove(); + } + } + } + + private void notifyChildrenChangeOnHandler(final String parentId, final Bundle options) { + for (IBinder binder : mServiceState.mConnections.keySet()) { + ConnectionRecord connection = mServiceState.mConnections.get(binder); + List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(parentId); + if (callbackList != null) { + for (Pair<IBinder, Bundle> callback : callbackList) { + if (MediaBrowserUtils.hasDuplicatedItems(options, callback.second)) { + performLoadChildrenOnHandler(parentId, connection, callback.second); + } + } + } + } + } + + /** Save the subscription and if it is a new subscription send the results. */ + private void addSubscriptionOnHandler( + String id, IMediaBrowserServiceCallbacks callbacks, IBinder token, Bundle options) { + IBinder b = callbacks.asBinder(); + // Get the record for the connection + ConnectionRecord connection = mServiceState.mConnections.get(b); + if (connection == null) { + Log.w(TAG, "addSubscription for callback that isn't registered id=" + id); + return; + } + // Save the subscription List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id); if (callbackList == null) { @@ -680,13 +600,66 @@ public abstract class MediaBrowserService extends Service { callbackList.add(new Pair<>(token, options)); connection.subscriptions.put(id, callbackList); // send the results - performLoadChildren(id, connection, options); + performLoadChildrenOnHandler(id, connection, options); } - /** - * Remove the subscription. - */ - private boolean removeSubscription(String id, ConnectionRecord connection, IBinder token) { + private void connectOnHandler( + String pkg, + int pid, + int uid, + Bundle rootHints, + IMediaBrowserServiceCallbacks callbacks) { + IBinder b = callbacks.asBinder(); + // Clear out the old subscriptions. We are getting new ones. + mServiceState.mConnections.remove(b); + + // Temporarily sets a placeholder ConnectionRecord to make getCurrentBrowserInfo() work in + // onGetRoot(). + mServiceState.mCurConnection = + new ConnectionRecord( + /* service= */ this, pkg, pid, uid, rootHints, callbacks, /* root= */ null); + BrowserRoot root = onGetRoot(pkg, uid, rootHints); + mServiceState.mCurConnection = null; + + // If they didn't return something, don't allow this client. + if (root == null) { + Log.i(TAG, "No root for client " + pkg + " from service " + getClass().getName()); + try { + callbacks.onConnectFailed(); + } catch (RemoteException ex) { + Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. pkg=" + pkg); + } + } else { + try { + ConnectionRecord connection = + new ConnectionRecord( + /* service= */ this, pkg, pid, uid, rootHints, callbacks, root); + mServiceState.mConnections.put(b, connection); + b.linkToDeath(connection, /* flags= */ 0); + if (mServiceState.mSession != null) { + callbacks.onConnect( + connection.root.getRootId(), + mServiceState.mSession, + connection.root.getExtras()); + } + } catch (RemoteException ex) { + Log.w(TAG, "Calling onConnect() failed. Dropping client. pkg=" + pkg); + mServiceState.mConnections.remove(b); + } + } + } + + /** Remove the subscription. */ + private boolean removeSubscriptionOnHandler( + String id, IMediaBrowserServiceCallbacks callbacks, IBinder token) { + final IBinder b = callbacks.asBinder(); + + ConnectionRecord connection = mServiceState.mConnections.get(b); + if (connection == null) { + Log.w(TAG, "removeSubscription for callback that isn't registered id=" + id); + return true; + } + if (token == null) { return connection.subscriptions.remove(id) != null; } @@ -700,7 +673,7 @@ public abstract class MediaBrowserService extends Service { iter.remove(); } } - if (callbackList.size() == 0) { + if (callbackList.isEmpty()) { connection.subscriptions.remove(id); } } @@ -709,44 +682,53 @@ public abstract class MediaBrowserService extends Service { /** * Call onLoadChildren and then send the results back to the connection. - * <p> - * Callers must make sure that this connection is still connected. + * + * <p>Callers must make sure that this connection is still connected. */ - private void performLoadChildren(final String parentId, final ConnectionRecord connection, - final Bundle options) { + private void performLoadChildrenOnHandler( + final String parentId, final ConnectionRecord connection, final Bundle options) { final Result<List<MediaBrowser.MediaItem>> result = - new Result<List<MediaBrowser.MediaItem>>(parentId) { - @Override - void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) { - if (mServiceState.mConnections.get(connection.callbacks.asBinder()) != connection) { - if (DBG) { - Log.d(TAG, "Not sending onLoadChildren result for connection that has" - + " been disconnected. pkg=" + connection.pkg + " id=" + parentId); - } - return; - } + new Result<>(parentId) { + @Override + void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) { + if (mServiceState.mConnections.get(connection.callbacks.asBinder()) + != connection) { + if (DBG) { + Log.d( + TAG, + "Not sending onLoadChildren result for connection that has" + + " been disconnected. pkg=" + + connection.pkg + + " id=" + + parentId); + } + return; + } - List<MediaBrowser.MediaItem> filteredList = - (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0 - ? MediaBrowserUtils.applyPagingOptions(list, options) : list; - final ParceledListSlice<MediaBrowser.MediaItem> pls; - if (filteredList == null) { - pls = null; - } else { - pls = new ParceledListSlice<>(filteredList); - // Limit the size of initial Parcel to prevent binder buffer overflow - // as onLoadChildren is an async binder call. - pls.setInlineCountLimit(1); - } - try { - connection.callbacks.onLoadChildren(parentId, pls, options); - } catch (RemoteException ex) { - // The other side is in the process of crashing. - Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId - + " package=" + connection.pkg); - } - } - }; + List<MediaBrowser.MediaItem> filteredList = + (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0 + ? MediaBrowserUtils.applyPagingOptions(list, options) + : list; + ParceledListSlice<MediaBrowser.MediaItem> pls = null; + if (filteredList != null) { + pls = new ParceledListSlice<>(filteredList); + // Limit the size of initial Parcel to prevent binder buffer overflow + // as onLoadChildren is an async binder call. + pls.setInlineCountLimit(1); + } + try { + connection.callbacks.onLoadChildren(parentId, pls, options); + } catch (RemoteException ex) { + // The other side is in the process of crashing. + Log.w( + TAG, + "Calling onLoadChildren() failed for id=" + + parentId + + " package=" + + connection.pkg); + } + } + }; mServiceState.mCurConnection = connection; if (options == null) { @@ -762,28 +744,41 @@ public abstract class MediaBrowserService extends Service { } } - private void performLoadItem(String itemId, final ConnectionRecord connection, - final ResultReceiver receiver) { + private void performLoadItemOnHandler( + String itemId, IMediaBrowserServiceCallbacks callbacks, final ResultReceiver receiver) { + final IBinder b = callbacks.asBinder(); + ConnectionRecord connection = mServiceState.mConnections.get(b); + if (connection == null) { + Log.w(TAG, "getMediaItem for callback that isn't registered id=" + itemId); + return; + } + final Result<MediaBrowser.MediaItem> result = - new Result<MediaBrowser.MediaItem>(itemId) { - @Override - void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) { - if (mServiceState.mConnections.get(connection.callbacks.asBinder()) != connection) { - if (DBG) { - Log.d(TAG, "Not sending onLoadItem result for connection that has" - + " been disconnected. pkg=" + connection.pkg + " id=" + itemId); + new Result<>(itemId) { + @Override + void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) { + if (mServiceState.mConnections.get(connection.callbacks.asBinder()) + != connection) { + if (DBG) { + Log.d( + TAG, + "Not sending onLoadItem result for connection that has" + + " been disconnected. pkg=" + + connection.pkg + + " id=" + + itemId); + } + return; + } + if ((flag & RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED) != 0) { + receiver.send(RESULT_ERROR, null); + return; + } + Bundle bundle = new Bundle(); + bundle.putParcelable(KEY_MEDIA_ITEM, item); + receiver.send(RESULT_OK, bundle); } - return; - } - if ((flag & RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED) != 0) { - receiver.send(RESULT_ERROR, null); - return; - } - Bundle bundle = new Bundle(); - bundle.putParcelable(KEY_MEDIA_ITEM, item); - receiver.send(RESULT_OK, bundle); - } - }; + }; mServiceState.mCurConnection = connection; onLoadItem(itemId, result); diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java index 3254a394c25a..f264b16347f9 100644 --- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -44,6 +44,8 @@ import android.util.Log; import android.util.Xml; import android.util.proto.ProtoOutputStream; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -138,6 +140,11 @@ public final class ApduServiceInfo implements Parcelable { private boolean mCategoryOtherServiceEnabled; /** + * Whether the NFC stack should default to Observe Mode when this preferred service. + */ + private boolean mDefaultToObserveMode; + + /** * @hide */ @UnsupportedAppUsage @@ -257,6 +264,9 @@ public final class ApduServiceInfo implements Parcelable { com.android.internal.R.styleable.HostApduService_settingsActivity); mOffHostName = null; mStaticOffHostName = mOffHostName; + mDefaultToObserveMode = sa.getBoolean( + R.styleable.HostApduService_defaultToObserveMode, + false); sa.recycle(); } else { TypedArray sa = res.obtainAttributes(attrs, @@ -276,6 +286,9 @@ public final class ApduServiceInfo implements Parcelable { com.android.internal.R.styleable.HostApduService_settingsActivity); mOffHostName = sa.getString( com.android.internal.R.styleable.OffHostApduService_secureElementName); + mDefaultToObserveMode = sa.getBoolean( + R.styleable.HostApduService_defaultToObserveMode, + false); if (mOffHostName != null) { if (mOffHostName.equals("eSE")) { mOffHostName = "eSE1"; @@ -611,6 +624,25 @@ public final class ApduServiceInfo implements Parcelable { } /** + * Returns whether the NFC stack should default to observe mode when this servise is preferred. + * @return whether the NFC stack should default to observe mode when this servise is preferred + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean defaultToObserveMode() { + return mDefaultToObserveMode; + } + + /** + * Sets whether the NFC stack should default to observe mode when this servise is preferred. + * @param defaultToObserveMode whether the NFC stack should default to observe mode when this + * servise is preferred + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public void setDefaultToObserveMode(boolean defaultToObserveMode) { + mDefaultToObserveMode = defaultToObserveMode; + } + + /** * Returns description of service. * @return user readable description of service */ diff --git a/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt new file mode 100644 index 000000000000..cda6b8bb36be --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt @@ -0,0 +1,44 @@ +/* + * 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.settingslib.media.session + +import android.media.session.MediaController +import android.media.session.MediaSessionManager +import android.os.UserHandle +import androidx.concurrent.futures.DirectExecutor +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.launch + +/** [Flow] for [MediaSessionManager.OnActiveSessionsChangedListener]. */ +val MediaSessionManager.activeMediaChanges: Flow<Collection<MediaController>?> + get() = + callbackFlow { + val listener = + MediaSessionManager.OnActiveSessionsChangedListener { launch { send(it) } } + addOnActiveSessionsChangedListener( + null, + UserHandle.of(UserHandle.myUserId()), + DirectExecutor.INSTANCE, + listener, + ) + awaitClose { removeOnActiveSessionsChangedListener(listener) } + } + .buffer(capacity = Channel.CONFLATED) diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt new file mode 100644 index 000000000000..1f037c0280e3 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt @@ -0,0 +1,100 @@ +/* + * 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.settingslib.volume.data.repository + +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession +import android.media.session.PlaybackState +import android.os.Bundle +import android.os.Handler +import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.launch + +/** [MediaController.Callback] flow representation. */ +fun MediaController.stateChanges(handler: Handler): Flow<MediaControllerChange> { + return callbackFlow { + val callback = MediaControllerCallbackProducer(this) + registerCallback(callback, handler) + awaitClose { unregisterCallback(callback) } + } +} + +/** Models particular change event received by [MediaController.Callback]. */ +sealed interface MediaControllerChange { + + data object SessionDestroyed : MediaControllerChange + + data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChange + + data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChange + + data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChange + + data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) : + MediaControllerChange + + data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChange + + data class ExtrasChanged(val extras: Bundle?) : MediaControllerChange + + data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : MediaControllerChange +} + +private class MediaControllerCallbackProducer( + private val producingScope: ProducerScope<MediaControllerChange> +) : MediaController.Callback() { + + override fun onSessionDestroyed() { + send(MediaControllerChange.SessionDestroyed) + } + + override fun onSessionEvent(event: String, extras: Bundle?) { + send(MediaControllerChange.SessionEvent(event, extras)) + } + + override fun onPlaybackStateChanged(state: PlaybackState?) { + send(MediaControllerChange.PlaybackStateChanged(state)) + } + + override fun onMetadataChanged(metadata: MediaMetadata?) { + send(MediaControllerChange.MetadataChanged(metadata)) + } + + override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) { + send(MediaControllerChange.QueueChanged(queue)) + } + + override fun onQueueTitleChanged(title: CharSequence?) { + send(MediaControllerChange.QueueTitleChanged(title)) + } + + override fun onExtrasChanged(extras: Bundle?) { + send(MediaControllerChange.ExtrasChanged(extras)) + } + + override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) { + send(MediaControllerChange.AudioInfoChanged(info)) + } + + private fun send(change: MediaControllerChange) { + producingScope.launch { producingScope.send(change) } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt index ab8c6b820177..6925c71fc68f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt @@ -16,21 +16,23 @@ package com.android.settingslib.volume.data.repository +import android.content.Intent import android.media.AudioManager import android.media.session.MediaController import android.media.session.MediaSessionManager import android.media.session.PlaybackState import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.headsetAudioModeChanges +import com.android.settingslib.media.session.activeMediaChanges import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -38,7 +40,7 @@ import kotlinx.coroutines.flow.stateIn interface MediaControllerRepository { /** Current [MediaController]. Null is emitted when there is no active [MediaController]. */ - val activeMediaController: StateFlow<MediaController?> + val activeLocalMediaController: StateFlow<MediaController?> } class MediaControllerRepositoryImpl( @@ -53,26 +55,28 @@ class MediaControllerRepositoryImpl( audioManagerIntentsReceiver.intents.filter { AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action } - override val activeMediaController: StateFlow<MediaController?> = - buildList { - localBluetoothManager?.headsetAudioModeChanges?.let { add(it) } - add(devicesChanges) + + override val activeLocalMediaController: StateFlow<MediaController?> = + combine( + mediaSessionManager.activeMediaChanges.onStart { + emit(mediaSessionManager.getActiveSessions(null)) + }, + localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) } + ?: flowOf(null), + devicesChanges.onStart { emit(Intent()) }, + ) { controllers, _, _ -> + controllers?.let(::findLocalMediaController) } - .merge() - .onStart { emit(Unit) } - .map { getActiveLocalMediaController() } .flowOn(backgroundContext) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null) - private fun getActiveLocalMediaController(): MediaController? { + private fun findLocalMediaController( + controllers: Collection<MediaController>, + ): MediaController? { var localController: MediaController? = null val remoteMediaSessionLists: MutableList<String> = ArrayList() - for (controller in mediaSessionManager.getActiveSessions(null)) { + for (controller in controllers) { val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue - val playbackState = controller.playbackState ?: continue - if (inactivePlaybackStates.contains(playbackState.state)) { - continue - } when (playbackInfo.playbackType) { MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> { if (localController?.packageName.equals(controller.packageName)) { diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt index 430d733e4a88..7bd43d2cf8ab 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt @@ -116,7 +116,7 @@ class MediaControllerRepositoryImplTest { ) ) var mediaController: MediaController? = null - underTest.activeMediaController + underTest.activeLocalMediaController .onEach { mediaController = it } .launchIn(backgroundScope) runCurrent() @@ -141,7 +141,7 @@ class MediaControllerRepositoryImplTest { ) ) var mediaController: MediaController? = null - underTest.activeMediaController + underTest.activeLocalMediaController .onEach { mediaController = it } .launchIn(backgroundScope) runCurrent() diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java index 5669276a0424..8edda1a1f3a2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java @@ -90,7 +90,7 @@ public class DeviceIconUtilTest { public void getIconResIdFromMediaRouteType_hdmi() { assertThat(new DeviceIconUtil(/* isTv */ false) .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI)) - .isEqualTo(R.drawable.ic_headphone); + .isEqualTo(R.drawable.ic_external_display); } @Test @@ -101,10 +101,10 @@ public class DeviceIconUtilTest { } @Test - public void getIconResIdFromMediaRouteType_hdmiArc_isHeadphone() { + public void getIconResIdFromMediaRouteType_hdmiArc_isExternalDisplay() { assertThat(new DeviceIconUtil(/* isTv */ false) .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI_ARC)) - .isEqualTo(R.drawable.ic_headphone); + .isEqualTo(R.drawable.ic_external_display); } @Test @@ -115,10 +115,10 @@ public class DeviceIconUtilTest { } @Test - public void getIconResIdFromMediaRouteType_hdmiEarc_isHeadphone() { + public void getIconResIdFromMediaRouteType_hdmiEarc_isExternalDisplay() { assertThat(new DeviceIconUtil(/* isTv */ false) .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI_EARC)) - .isEqualTo(R.drawable.ic_headphone); + .isEqualTo(R.drawable.ic_external_display); } @Test @@ -229,10 +229,10 @@ public class DeviceIconUtilTest { } @Test - public void getIconResIdFromAudioDeviceType_hdmi_isHeadphone() { + public void getIconResIdFromAudioDeviceType_hdmi_isExternalDisplay() { assertThat(new DeviceIconUtil(/* isTv */ false) .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI)) - .isEqualTo(R.drawable.ic_headphone); + .isEqualTo(R.drawable.ic_external_display); } @Test @@ -243,10 +243,10 @@ public class DeviceIconUtilTest { } @Test - public void getIconResIdFromAudioDeviceType_hdmiArc_isHeadphone() { + public void getIconResIdFromAudioDeviceType_hdmiArc_isExternalDisplay() { assertThat(new DeviceIconUtil(/* isTv */ false) .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI_ARC)) - .isEqualTo(R.drawable.ic_headphone); + .isEqualTo(R.drawable.ic_external_display); } @Test @@ -257,10 +257,10 @@ public class DeviceIconUtilTest { } @Test - public void getIconResIdFromAudioDeviceType_hdmiEarc_isHeadphone() { + public void getIconResIdFromAudioDeviceType_hdmiEarc_isExternalDisplay() { assertThat(new DeviceIconUtil(/* isTv */ false) .getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI_EARC)) - .isEqualTo(R.drawable.ic_headphone); + .isEqualTo(R.drawable.ic_external_display); } @Test diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 4305d912e806..53f2caf0b793 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -771,6 +771,9 @@ <!-- Permission required for CTS test - CtsDevicePolicyManagerTestCases --> <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" /> + <!-- Permission required for CTS test - CtsDevicePolicyTestCases --> + <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" /> + <!-- Permission required for CTS test - CtsKeystoreTestCases --> <uses-permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION" /> diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt new file mode 100644 index 000000000000..aad593eb6a05 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectView.kt @@ -0,0 +1,51 @@ +/* + * 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.systemui.surfaceeffects.loadingeffect + +import android.content.Context +import android.graphics.BlendMode +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View + +/** Custom View for drawing the [LoadingEffect] with [Canvas.drawPaint]. */ +open class LoadingEffectView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { + + private var paint: Paint? = null + private var blendMode: BlendMode = BlendMode.SRC_OVER + + override fun onDraw(canvas: Canvas) { + if (!canvas.isHardwareAccelerated) { + return + } + paint?.let { canvas.drawPaint(it) } + } + + /** Designed to be called on [LoadingEffect.PaintDrawCallback.onDraw]. */ + fun draw(paint: Paint) { + this.paint = paint + this.paint!!.blendMode = blendMode + + invalidate() + } + + /** Sets the blend mode of the [Paint]. */ + fun setBlendMode(blendMode: BlendMode) { + this.blendMode = blendMode + } +} diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 374a97d769c8..4398b2541f65 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -69,7 +69,7 @@ object ComposeFacade : BaseComposeFacade { override fun setVolumePanelActivityContent( activity: ComponentActivity, viewModel: VolumePanelViewModel, - onDismissAnimationFinished: () -> Unit, + onDismiss: () -> Unit, ) { throwComposeUnavailableError() } diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt new file mode 100644 index 000000000000..8ad0a080a9dd --- /dev/null +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt @@ -0,0 +1,21 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput + +import dagger.Module + +@Module interface MediaOutputModule diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index a1bbc7d7bfb8..aa567364d130 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -102,12 +102,12 @@ object ComposeFacade : BaseComposeFacade { override fun setVolumePanelActivityContent( activity: ComponentActivity, viewModel: VolumePanelViewModel, - onDismissAnimationFinished: () -> Unit, + onDismiss: () -> Unit, ) { activity.setContent { VolumePanelRoot( viewModel = viewModel, - onDismissAnimationFinished = onDismissAnimationFinished, + onDismiss = onDismiss, ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index f387021daeac..ed2cbb85ba1a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -21,10 +21,10 @@ import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.notifications.ui.composable.NotificationStack import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.statusbar.notification.stack.AmbientState @@ -59,37 +59,38 @@ constructor( ) { init { - if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) { - // This scene container section moves the NSSL to the SharedNotificationContainer. - // This also requires that SharedNotificationContainer gets moved to the - // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container, - // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this - // container by the NotificationStackScrollLayoutSection. - // Ensure stackScrollLayout is a child of sharedNotificationContainer. + if (!migrateClocksToBlueprint()) { + throw IllegalStateException("this requires migrateClocksToBlueprint()") + } + // This scene container section moves the NSSL to the SharedNotificationContainer. + // This also requires that SharedNotificationContainer gets moved to the + // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container, + // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this + // container by the NotificationStackScrollLayoutSection. + // Ensure stackScrollLayout is a child of sharedNotificationContainer. - if (stackScrollLayout.parent != sharedNotificationContainer) { - (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout) - sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout) - } + if (stackScrollLayout.parent != sharedNotificationContainer) { + (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout) + sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout) + } - SharedNotificationContainerBinder.bind( + SharedNotificationContainerBinder.bind( + sharedNotificationContainer, + sharedNotificationContainerViewModel, + sceneContainerFlags, + controller, + notificationStackSizeCalculator, + mainDispatcher, + ) + + if (sceneContainerFlags.flexiNotifsEnabled()) { + NotificationStackAppearanceViewBinder.bind( + context, sharedNotificationContainer, - sharedNotificationContainerViewModel, - sceneContainerFlags, + notificationStackAppearanceViewModel, + ambientState, controller, - notificationStackSizeCalculator, - mainDispatcher, ) - - if (sceneContainerFlags.flexiNotifsEnabled()) { - NotificationStackAppearanceViewBinder.bind( - context, - sharedNotificationContainer, - notificationStackAppearanceViewModel, - ambientState, - controller, - ) - } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt index 43d545368536..236aee217f16 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt @@ -32,7 +32,7 @@ interface BottomBarModule { @Binds @IntoMap @StringKey(VolumePanelComponents.BOTTOM_BAR) - fun bindMediaVolumeSliderComponent(component: BottomBarComponent): VolumePanelUiComponent + fun bindVolumePanelUiComponent(component: BottomBarComponent): VolumePanelUiComponent @Binds @IntoMap diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt index 03c07f714541..0cf43672c716 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt @@ -20,6 +20,8 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -49,7 +51,13 @@ constructor( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - PlatformOutlinedButton(onClick = viewModel::onSettingsClicked) { + PlatformOutlinedButton( + onClick = viewModel::onSettingsClicked, + colors = + ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.onSurface, + ), + ) { Text(text = stringResource(R.string.volume_panel_dialog_settings_button)) } PlatformButton(onClick = viewModel::onDoneClicked) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt new file mode 100644 index 000000000000..c73656eb1ec5 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt @@ -0,0 +1,44 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput + +import com.android.systemui.volume.panel.component.mediaoutput.domain.MediaOutputAvailabilityCriteria +import com.android.systemui.volume.panel.component.mediaoutput.ui.composable.MediaOutputComponent +import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents +import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria +import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +/** Dagger module, that provides media output Volume Panel UI functionality. */ +@Module +interface MediaOutputModule { + + @Binds + @IntoMap + @StringKey(VolumePanelComponents.MEDIA_OUTPUT) + fun bindVolumePanelUiComponent(component: MediaOutputComponent): VolumePanelUiComponent + + @Binds + @IntoMap + @StringKey(VolumePanelComponents.MEDIA_OUTPUT) + fun bindComponentAvailabilityCriteria( + criteria: MediaOutputAvailabilityCriteria + ): ComponentAvailabilityCriteria +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt new file mode 100644 index 000000000000..8ad6fdf829d7 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt @@ -0,0 +1,185 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.ui.composable + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.snap +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.android.compose.animation.Expandable +import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.common.ui.compose.toColor +import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.ConnectedDeviceViewModel +import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.DeviceIconViewModel +import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.MediaOutputViewModel +import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent +import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope +import javax.inject.Inject + +@VolumePanelScope +class MediaOutputComponent +@Inject +constructor( + private val viewModel: MediaOutputViewModel, +) : ComposeVolumePanelUiComponent { + + @Composable + override fun VolumePanelComposeScope.Content(modifier: Modifier) { + val connectedDeviceViewModel: ConnectedDeviceViewModel? by + viewModel.connectedDeviceViewModel.collectAsState() + val deviceIconViewModel: DeviceIconViewModel? by + viewModel.deviceIconViewModel.collectAsState() + + Expandable( + modifier = Modifier.fillMaxWidth().height(80.dp), + color = MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(28.dp), + onClick = { viewModel.onBarClick(it) }, + ) { + Row { + connectedDeviceViewModel?.let { ConnectedDeviceText(it) } + + deviceIconViewModel?.let { ConnectedDeviceIcon(it) } + } + } + } + + @Composable + private fun RowScope.ConnectedDeviceText(connectedDeviceViewModel: ConnectedDeviceViewModel) { + Column( + modifier = + Modifier.weight(1f) + .padding(start = 24.dp, top = 20.dp, bottom = 20.dp) + .fillMaxHeight(), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + connectedDeviceViewModel.label.toString(), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + connectedDeviceViewModel.deviceName?.let { + Text( + it.toString(), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } + + @Composable + private fun ConnectedDeviceIcon(deviceIconViewModel: DeviceIconViewModel) { + val transition = updateTransition(deviceIconViewModel, label = "MediaOutputIconTransition") + Box( + modifier = Modifier.padding(16.dp).fillMaxHeight().aspectRatio(1f), + contentAlignment = Alignment.Center + ) { + transition.AnimatedContent( + contentKey = { it.backgroundColor }, + transitionSpec = { + if (targetState is DeviceIconViewModel.IsPlaying) { + scaleIn( + initialScale = 0.9f, + animationSpec = isPlayingInIconBackgroundSpec(), + ) + fadeIn(animationSpec = isPlayingInIconBackgroundSpec()) togetherWith + fadeOut(animationSpec = snap()) + } else { + fadeIn(animationSpec = snap(delayMillis = 900)) togetherWith + scaleOut( + targetScale = 0.9f, + animationSpec = isPlayingOutSpec(), + ) + fadeOut(animationSpec = isPlayingOutSpec()) + } + } + ) { targetViewModel -> + Expandable( + modifier = Modifier.fillMaxSize(), + color = targetViewModel.backgroundColor.toColor(), + shape = RoundedCornerShape(12.dp), + onClick = { viewModel.onDeviceClick(it) }, + ) {} + } + transition.AnimatedContent( + contentKey = { it.icon }, + transitionSpec = { + if (targetState is DeviceIconViewModel.IsPlaying) { + fadeIn(animationSpec = snap(delayMillis = 700)) togetherWith + slideOutVertically( + targetOffsetY = { it }, + animationSpec = isPlayingInIconSpec(), + ) + fadeOut(animationSpec = isNotPlayingOutIconSpec()) + } else { + slideInVertically( + initialOffsetY = { it }, + animationSpec = isNotPlayingInIconSpec(), + ) + fadeIn(animationSpec = isNotPlayingInIconSpec()) togetherWith + fadeOut(animationSpec = isPlayingOutSpec()) + } + } + ) { + Icon( + icon = it.icon, + modifier = Modifier.padding(12.dp).fillMaxSize(), + ) + } + } + } +} + +private fun <T> isPlayingOutSpec() = tween<T>(durationMillis = 400, delayMillis = 500) + +private fun <T> isPlayingInIconSpec() = tween<T>(durationMillis = 400, delayMillis = 300) + +private fun <T> isPlayingInIconBackgroundSpec() = tween<T>(durationMillis = 400, delayMillis = 700) + +private fun <T> isNotPlayingOutIconSpec() = tween<T>(durationMillis = 400, delayMillis = 300) + +private fun <T> isNotPlayingInIconSpec() = tween<T>(durationMillis = 400, delayMillis = 900) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt new file mode 100644 index 000000000000..98ef0674e8a1 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.ui.composable + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.android.systemui.volume.panel.ui.layout.ComponentsLayout + +@Composable +fun VolumePanelComposeScope.HorizontalVolumePanelContent( + layout: ComponentsLayout, + modifier: Modifier = Modifier, +) { + val spacing = 20.dp + Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(space = spacing)) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(spacing) + ) { + for (component in layout.contentComponents) { + AnimatedVisibility(component.isVisible) { + with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } + } + } + } + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(space = spacing, alignment = Alignment.Top) + ) { + for (component in layout.headerComponents) { + AnimatedVisibility(component.isVisible) { + with(component.component as ComposeVolumePanelUiComponent) { + Content(Modifier.weight(1f)) + } + } + } + Row( + modifier = Modifier.fillMaxWidth().wrapContentHeight(), + horizontalArrangement = Arrangement.spacedBy(spacing), + ) { + for (component in layout.footerComponents) { + AnimatedVisibility(component.isVisible) { + with(component.component as ComposeVolumePanelUiComponent) { + Content(Modifier.weight(1f)) + } + } + } + } + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt index e8d59660cebf..86eb84929c02 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.ui.composable import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -33,9 +34,16 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent( modifier: Modifier = Modifier, ) { Column( - modifier = modifier, + modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.spacedBy(20.dp), ) { + for (component in layout.headerComponents) { + AnimatedVisibility(component.isVisible) { + with(component.component as ComposeVolumePanelUiComponent) { + Content(Modifier.weight(1f)) + } + } + } for (component in layout.contentComponents) { AnimatedVisibility(component.isVisible) { with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) } @@ -44,11 +52,13 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent( if (layout.footerComponents.isNotEmpty()) { Row( modifier = Modifier.fillMaxWidth().wrapContentHeight(), - horizontalArrangement = Arrangement.spacedBy(20.dp) + horizontalArrangement = Arrangement.spacedBy(20.dp), ) { for (component in layout.footerComponents) { - with(component.component as ComposeVolumePanelUiComponent) { - Content(Modifier.weight(1f)) + AnimatedVisibility(component.isVisible) { + with(component.component as ComposeVolumePanelUiComponent) { + Content(Modifier.weight(1f)) + } } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt index c70c6b1ad861..10731c7f2df7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt @@ -16,17 +16,21 @@ package com.android.systemui.volume.panel.ui.composable +import android.content.res.Configuration import android.content.res.Configuration.Orientation import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState class VolumePanelComposeScope(private val state: VolumePanelState) { - /** - * Layout orientation of the panel. It doesn't necessarily aligns with the device orientation, - * because in some cases we want to show bigger version of a portrait orientation when the - * device is in landscape. - */ + /** Layout orientation of the panel. This aligns with the device orientation. */ @Orientation val orientation: Int get() = state.orientation + + /** Is true when Volume Panel is using wide-screen layout and false the otherwise. */ + val isWideScreen: Boolean + get() = state.isWideScreen } + +val VolumePanelComposeScope.isPortrait: Boolean + get() = orientation == Configuration.ORIENTATION_PORTRAIT diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt index 60d03fc6a107..dd6342029885 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt @@ -17,17 +17,13 @@ package com.android.systemui.volume.panel.ui.composable import android.content.res.Configuration -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -37,11 +33,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.dp import com.android.compose.theme.PlatformTheme import com.android.systemui.res.R import com.android.systemui.volume.panel.ui.layout.ComponentsLayout @@ -51,48 +46,45 @@ import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel @Composable fun VolumePanelRoot( viewModel: VolumePanelViewModel, - onDismissAnimationFinished: () -> Unit, modifier: Modifier = Modifier, + onDismiss: () -> Unit ) { + LaunchedEffect(viewModel) { + viewModel.volumePanelState.collect { + if (!it.isVisible) { + onDismiss() + } + } + } + PlatformTheme(isSystemInDarkTheme()) { val state: VolumePanelState by viewModel.volumePanelState.collectAsState() val components by viewModel.componentsLayout.collectAsState(null) - val transitionState = - remember { MutableTransitionState(false) }.apply { targetState = state.isVisible } - - LaunchedEffect(transitionState.targetState, transitionState.isIdle) { - if (!transitionState.targetState && transitionState.isIdle) { - onDismissAnimationFinished() + with(VolumePanelComposeScope(state)) { + var boxModifier = modifier.fillMaxSize().clickable(onClick = onDismiss) + if (!isPortrait) { + boxModifier = boxModifier.padding(horizontal = 48.dp) } - } - - Box( - modifier = modifier.fillMaxSize(), - contentAlignment = Alignment.BottomCenter, - ) { - Spacer( - modifier = - Modifier.fillMaxSize() - .alpha(0.32f) - .background(MaterialTheme.colorScheme.scrim) - .clickable(onClick = { viewModel.dismissPanel() }) - ) - AnimatedVisibility( - visibleState = transitionState, - enter = slideInVertically { it }, - exit = slideOutVertically { it }, + Box( + modifier = boxModifier, + contentAlignment = Alignment.BottomCenter, ) { val radius = dimensionResource(R.dimen.volume_panel_corner_radius) Surface( + modifier = + Modifier.clickable( + interactionSource = null, + indication = null, + onClick = { + // prevent windowCloseOnTouchOutside from dismissing when tapped on + // the panel itself. + }, + ), shape = RoundedCornerShape(topStart = radius, topEnd = radius), color = MaterialTheme.colorScheme.surfaceContainer, ) { - Column { - components?.let { componentsState -> - with(VolumePanelComposeScope(state)) { Components(componentsState) } - } - } + Column { components?.let { componentsState -> Components(componentsState) } } } } } @@ -100,27 +92,38 @@ fun VolumePanelRoot( } @Composable -private fun VolumePanelComposeScope.Components(state: ComponentsLayout) { +private fun VolumePanelComposeScope.Components(components: ComponentsLayout) { if (orientation == Configuration.ORIENTATION_PORTRAIT) { VerticalVolumePanelContent( - state, - modifier = Modifier.padding(dimensionResource(R.dimen.volume_panel_content_padding)), + components, + modifier = Modifier.padding(24.dp), ) } else { - TODO("Add landscape layout") + HorizontalVolumePanelContent( + components, + modifier = + Modifier.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 20.dp) + .heightIn(max = 236.dp), + ) } - val horizontalPadding = dimensionResource(R.dimen.volume_panel_bottom_bar_horizontal_padding) - if (state.bottomBarComponent.isVisible) { - with(state.bottomBarComponent.component as ComposeVolumePanelUiComponent) { - Content( - Modifier.navigationBarsPadding() + if (components.bottomBarComponent.isVisible) { + val horizontalPadding = + dimensionResource(R.dimen.volume_panel_bottom_bar_horizontal_padding) + Box( + modifier = + Modifier.fillMaxWidth() + .navigationBarsPadding() .padding( start = horizontalPadding, end = horizontalPadding, bottom = dimensionResource(R.dimen.volume_panel_bottom_bar_bottom_padding), - ) - ) + ), + contentAlignment = Alignment.Center, + ) { + with(components.bottomBarComponent.component as ComposeVolumePanelUiComponent) { + Content(Modifier) + } } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 3dfe65a4f736..51c008ab686a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -36,7 +36,7 @@ import com.android.systemui.plugins.clocks.ClockMetadata import com.android.systemui.plugins.clocks.ClockProvider import com.android.systemui.plugins.clocks.ClockProviderPlugin import com.android.systemui.plugins.clocks.ClockSettings -import com.android.systemui.util.Assert +import com.android.systemui.util.ThreadAssert import java.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean @@ -89,6 +89,7 @@ open class ClockRegistry( val keepAllLoaded: Boolean, subTag: String, var isTransitClockEnabled: Boolean = false, + val assert: ThreadAssert = ThreadAssert(), ) { private val TAG = "${ClockRegistry::class.simpleName} ($subTag)" private val logger: Logger = @@ -286,7 +287,7 @@ open class ClockRegistry( @OpenForTesting open fun querySettings() { - assertNotMainThread() + assert.isNotMainThread() val result = try { val json = @@ -313,7 +314,7 @@ open class ClockRegistry( @OpenForTesting open fun applySettings(value: ClockSettings?) { - assertNotMainThread() + assert.isNotMainThread() try { value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis()) @@ -339,16 +340,6 @@ open class ClockRegistry( settings = value } - @OpenForTesting - protected open fun assertMainThread() { - Assert.isMainThread() - } - - @OpenForTesting - protected open fun assertNotMainThread() { - Assert.isNotMainThread() - } - private var isClockChanged = AtomicBoolean(false) private fun triggerOnCurrentClockChanged() { val shouldSchedule = isClockChanged.compareAndSet(false, true) @@ -357,7 +348,7 @@ open class ClockRegistry( } scope.launch(mainDispatcher) { - assertMainThread() + assert.isMainThread() isClockChanged.set(false) clockChangeListeners.forEach { it.onCurrentClockChanged() } } @@ -371,7 +362,7 @@ open class ClockRegistry( } scope.launch(mainDispatcher) { - assertMainThread() + assert.isMainThread() isClockListChanged.set(false) clockChangeListeners.forEach { it.onAvailableClocksChanged() } } @@ -585,7 +576,7 @@ open class ClockRegistry( * Calling from main thread to make sure the access is thread safe. */ fun registerClockChangeListener(listener: ClockChangeListener) { - assertMainThread() + assert.isMainThread() clockChangeListeners.add(listener) } @@ -595,7 +586,7 @@ open class ClockRegistry( * Calling from main thread to make sure the access is thread safe. */ fun unregisterClockChangeListener(listener: ClockChangeListener) { - assertMainThread() + assert.isMainThread() clockChangeListeners.remove(listener) } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioModeInteractorTest.kt index 4dbf865475a4..fe34361540e1 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioModeInteractorTest.kt @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.android.settingslib.volume.domain.interactor +package com.android.systemui.volume.domain.interactor import android.media.AudioManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.BaseTest -import com.android.settingslib.volume.data.repository.FakeAudioRepository +import com.android.settingslib.volume.domain.interactor.AudioModeInteractor +import com.android.systemui.SysuiTestCase +import com.android.systemui.volume.data.repository.FakeAudioRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -34,7 +35,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) @SmallTest -class AudioModeInteractorTest : BaseTest() { +class AudioModeInteractorTest : SysuiTestCase() { private val testScope = TestScope() private val fakeAudioRepository = FakeAudioRepository() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt new file mode 100644 index 000000000000..ec37925af0f3 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt @@ -0,0 +1,111 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.domain + +import android.media.AudioManager +import android.media.session.MediaSession +import android.media.session.PlaybackState +import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.volume.audioModeInteractor +import com.android.systemui.volume.audioRepository +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.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@RunWithLooper(setAsMainLooper = true) +class MediaOutputAvailabilityCriteriaTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private lateinit var underTest: MediaOutputAvailabilityCriteria + + @Before + fun setup() { + with(kosmos) { + 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 = MediaOutputAvailabilityCriteria(mediaOutputInteractor, audioModeInteractor) + } + } + + @Test + fun notInCallAndHasDevices_isAvailable_true() { + with(kosmos) { + testScope.runTest { + audioRepository.setMode(AudioManager.MODE_NORMAL) + localMediaRepository.updateMediaDevices(listOf(mock {})) + + val isAvailable by collectLastValue(underTest.isAvailable()) + runCurrent() + + assertThat(isAvailable).isTrue() + } + } + } + @Test + fun inCallAndHasDevices_isAvailable_false() { + with(kosmos) { + testScope.runTest { + audioRepository.setMode(AudioManager.MODE_IN_CALL) + localMediaRepository.updateMediaDevices(listOf(mock {})) + + val isAvailable by collectLastValue(underTest.isAvailable()) + runCurrent() + + assertThat(isAvailable).isFalse() + } + } + } + + @Test + fun notInCallAndHasDevices_isAvailable_false() { + with(kosmos) { + testScope.runTest { + audioRepository.setMode(AudioManager.MODE_NORMAL) + localMediaRepository.updateMediaDevices(emptyList()) + + val isAvailable by collectLastValue(underTest.isAvailable()) + runCurrent() + + assertThat(isAvailable).isFalse() + } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt new file mode 100644 index 000000000000..243aab24b07d --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt @@ -0,0 +1,121 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.ui.viewmodel + +import android.content.applicationContext +import android.media.session.MediaSession +import android.media.session.PlaybackState +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.res.R +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +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.mediaOutputActionsInteractor +import com.android.systemui.volume.panel.volumePanelViewModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class MediaOutputViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val playbackStateBuilder = PlaybackState.Builder() + + private lateinit var underTest: MediaOutputViewModel + + @Before + fun setup() { + with(kosmos) { + underTest = + MediaOutputViewModel( + applicationContext, + testScope.backgroundScope, + volumePanelViewModel, + mediaOutputActionsInteractor, + mediaOutputInteractor, + ) + + with(context.orCreateTestableResources) { + addOverride(R.string.media_output_label_title, "media_output_label_title") + addOverride( + R.string.media_output_title_without_playing, + "media_output_title_without_playing" + ) + } + + whenever(mediaController.packageName).thenReturn("test.pkg") + whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {})) + whenever(mediaController.playbackState).then { playbackStateBuilder.build() } + + mediaControllerRepository.setActiveLocalMediaController(mediaController) + } + } + + @Test + fun playingSession_connectedDeviceViewMode_hasTheDevice() { + with(kosmos) { + testScope.runTest { + playbackStateBuilder.setState(PlaybackState.STATE_PLAYING, 0, 0f) + localMediaRepository.updateCurrentConnectedDevice( + mock { whenever(name).thenReturn("test_device") } + ) + + val connectedDeviceViewModel by collectLastValue(underTest.connectedDeviceViewModel) + runCurrent() + + assertThat(connectedDeviceViewModel!!.label).isEqualTo("media_output_label_title") + assertThat(connectedDeviceViewModel!!.deviceName).isEqualTo("test_device") + } + } + } + + @Test + fun notPlaying_connectedDeviceViewMode_hasTheDevice() { + with(kosmos) { + testScope.runTest { + playbackStateBuilder.setState(PlaybackState.STATE_STOPPED, 0, 0f) + localMediaRepository.updateCurrentConnectedDevice( + mock { whenever(name).thenReturn("test_device") } + ) + + val connectedDeviceViewModel by collectLastValue(underTest.connectedDeviceViewModel) + runCurrent() + + assertThat(connectedDeviceViewModel!!.label) + .isEqualTo("media_output_title_without_playing") + assertThat(connectedDeviceViewModel!!.deviceName).isEqualTo("test_device") + } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt index 7c993603b810..71866b3957b6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt @@ -50,7 +50,7 @@ class DefaultComponentsLayoutManagerTest : SysuiTestCase() { val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false) val layout = underTest.layout( - VolumePanelState(0, false), + VolumePanelState(0, false, false), setOf(bottomBarComponentState, component1, component2, component3, component4) ) @@ -71,7 +71,7 @@ class DefaultComponentsLayoutManagerTest : SysuiTestCase() { val component1State = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false) val component2State = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false) underTest.layout( - VolumePanelState(0, false), + VolumePanelState(0, false, false), setOf( component1State, component2State, diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 5db9eee6a908..109e63c6167a 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -67,6 +67,18 @@ android:background="@drawable/qs_media_outline_layout_bg" /> + <com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView + android:id="@+id/loading_effect_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_expanded" + app:layout_constraintStart_toStartOf="@id/album_art" + app:layout_constraintEnd_toEndOf="@id/album_art" + app:layout_constraintTop_toTopOf="@id/album_art" + app:layout_constraintBottom_toBottomOf="@id/album_art" + android:clipToOutline="true" + android:background="@drawable/qs_media_outline_layout_bg" + /> + <!-- Guideline for output switcher --> <androidx.constraintlayout.widget.Guideline android:id="@+id/center_vertical_guideline" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7537a003275d..a681da3adc4e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -608,7 +608,6 @@ <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen> <dimen name="volume_panel_corner_radius">52dp</dimen> - <dimen name="volume_panel_content_padding">24dp</dimen> <dimen name="volume_panel_bottom_bar_horizontal_padding">24dp</dimen> <dimen name="volume_panel_bottom_bar_bottom_padding">20dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e401c71b3716..a7d93e70fda3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1416,6 +1416,9 @@ <!-- Indication on the keyguard that appears when a trust agents unlocks the device. [CHAR LIMIT=40] --> <string name="keyguard_indication_trust_unlocked">Kept unlocked by TrustAgent</string> + <!-- Message asking the user to authenticate with primary authentication methods (PIN/pattern/password) or biometrics after the device is locked by adaptive auth. [CHAR LIMIT=60] --> + <string name="kg_prompt_after_adaptive_auth_lock">Theft protection\nDevice locked, too many unlock attempts</string> + <!-- Accessibility string for current zen mode and selected exit condition. A template that simply concatenates existing mode string and the current condition description. [CHAR LIMIT=20] --> <string name="zen_mode_and_condition"><xliff:g id="zen_mode" example="Priority interruptions only">%1$s</xliff:g>. <xliff:g id="exit_condition" example="For one hour">%2$s</xliff:g></string> @@ -1532,6 +1535,12 @@ <string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string> + <!-- Title with application label for media output settings. [CHAR LIMIT=20] --> + <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string> + + <!-- Title for media output settings without media is playing. [CHAR LIMIT=20] --> + <string name="media_output_title_without_playing">Audio will play on</string> + <!-- Name of special SystemUI debug settings --> <string name="system_ui_tuner">System UI Tuner</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index ab3cacb27191..f1d4d71d1cc4 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -953,13 +953,13 @@ <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item> </style> - <style name="Theme.VolumePanelActivity" parent="@style/Theme.SystemUI"> + <style name="Theme.VolumePanelActivity" + parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> - <item name="android:windowActionBar">false</item> - <item name="android:windowNoTitle">true</item> - <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen. --> - <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item> + <item name="android:backgroundDimEnabled">true</item> + <item name="android:windowCloseOnTouchOutside">true</item> + <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> </style> <style name="Theme.UserSwitcherFullscreenDialog" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"> diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml index c053b33b4c63..2f2b10f8dc0c 100644 --- a/packages/SystemUI/res/xml/media_session_collapsed.xml +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -55,6 +55,15 @@ app:layout_constraintBottom_toBottomOf="@+id/album_art" /> <Constraint + android:id="@+id/loading_effect_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_collapsed" + app:layout_constraintStart_toStartOf="@+id/album_art" + app:layout_constraintEnd_toEndOf="@+id/album_art" + app:layout_constraintTop_toTopOf="@+id/album_art" + app:layout_constraintBottom_toBottomOf="@+id/album_art" /> + + <Constraint android:id="@+id/header_title" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml index 8bf7560d6ddb..0140d52bd175 100644 --- a/packages/SystemUI/res/xml/media_session_expanded.xml +++ b/packages/SystemUI/res/xml/media_session_expanded.xml @@ -48,6 +48,15 @@ app:layout_constraintBottom_toBottomOf="@+id/album_art" /> <Constraint + android:id="@+id/loading_effect_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_expanded" + app:layout_constraintStart_toStartOf="@+id/album_art" + app:layout_constraintEnd_toEndOf="@+id/album_art" + app:layout_constraintTop_toTopOf="@+id/album_art" + app:layout_constraintBottom_toBottomOf="@+id/album_art" /> + + <Constraint android:id="@+id/header_title" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index ee260e16dc56..8b2a0ec27011 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -43,7 +43,6 @@ import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.core.Logger @@ -316,7 +315,7 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { if (!isKeyguardVisible) { clock?.run { smallClock.animations.doze(if (isDozing) 1f else 0f) @@ -410,7 +409,7 @@ constructor( parent.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { listenForDozing(this) - if (KeyguardShadeMigrationNssl.isEnabled) { + if (migrateClocksToBlueprint()) { listenForDozeAmountTransition(this) listenForAnyStateToAodTransition(this) } else { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 2db3795cbad7..e621ffe4cbc4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -47,7 +47,6 @@ import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; @@ -349,7 +348,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } int getNotificationIconAreaHeight() { - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { return 0; } else if (NotificationIconContainerRefactor.isEnabled()) { return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0; @@ -597,7 +596,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void updateAodIcons() { - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { NotificationIconContainer nic = (NotificationIconContainer) mView.findViewById( com.android.systemui.res.R.id.left_aligned_notification_icon_container); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 3585feb3442d..84c8ea708031 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.WindowInsets.Type.ime; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_ADAPTIVE_AUTH_REQUEST; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT; @@ -126,6 +127,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_TRUSTAGENT_EXPIRED: return R.string.kg_prompt_reason_timeout_password; + case PROMPT_REASON_ADAPTIVE_AUTH_REQUEST: + return R.string.kg_prompt_after_adaptive_auth_lock; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index db7ff888356c..bf8900da887a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -331,6 +331,9 @@ public class KeyguardPatternViewController case PROMPT_REASON_TRUSTAGENT_EXPIRED: resId = R.string.kg_prompt_reason_timeout_pattern; break; + case PROMPT_REASON_ADAPTIVE_AUTH_REQUEST: + resId = R.string.kg_prompt_after_adaptive_auth_lock; + break; case PROMPT_REASON_NONE: break; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index fcff0dbc0878..bcab6f054dd6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_ADAPTIVE_AUTH_REQUEST; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT; @@ -138,6 +139,8 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_TRUSTAGENT_EXPIRED: return R.string.kg_prompt_reason_timeout_pin; + case PROMPT_REASON_ADAPTIVE_AUTH_REQUEST: + return R.string.kg_prompt_after_adaptive_auth_lock; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index 83b1a2cbbf52..3e87c1b60581 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -67,6 +67,11 @@ public interface KeyguardSecurityView { int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8; /** + * Some auth is required because adaptive auth has determined risk + */ + int PROMPT_REASON_ADAPTIVE_AUTH_REQUEST = 9; + + /** * Strong auth is required because the device has just booted because of an automatic * mainline update. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 1758831203d6..9421f150a38a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -54,7 +54,6 @@ import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.clocks.ClockController; @@ -231,7 +230,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mDumpManager.registerDumpable(getInstanceName(), this); - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { startCoroutines(EmptyCoroutineContext.INSTANCE); } } @@ -511,7 +510,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(layout); int guideline; - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { guideline = R.id.split_shade_guideline; } else { guideline = R.id.qs_edge_guideline; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 536f3afdd575..38c2829e27f6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -35,6 +35,7 @@ import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; @@ -382,6 +383,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private List<SubscriptionInfo> mSubscriptionInfo; @VisibleForTesting protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; + private boolean mFingerprintDetectRunning; private boolean mIsDreaming; private boolean mLogoutEnabled; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -1003,6 +1005,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean wasCancellingRestarting = mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING; mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; + mFingerprintDetectRunning = false; if (wasCancellingRestarting) { KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } else { @@ -1111,6 +1114,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING; boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING; mFingerprintRunningState = fingerprintRunningState; + if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) { + mFingerprintDetectRunning = false; + } mLogger.logFingerprintRunningState(mFingerprintRunningState); // Clients of KeyguardUpdateMonitor don't care about the internal state about the // asynchronousness of the cancel cycle. So only notify them if the actually running state @@ -1570,6 +1576,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return isEncrypted || isLockDown; } + /** + * Whether the device is locked by adaptive auth + */ + public boolean isDeviceLockedByAdaptiveAuth(int userId) { + return containsFlag(mStrongAuthTracker.getStrongAuthForUser(userId), + SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST); + } + private boolean containsFlag(int haystack, int needle) { return (haystack & needle) != 0; } @@ -1835,8 +1849,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) { - handleBiometricDetected(userId, FINGERPRINT, isStrongBiometric); + // Fingerprint lifecycle ends + if (mHandler.hasCallbacks(mFpCancelNotReceived)) { + mLogger.d("onFingerprintDetected()" + + " triggered while waiting for cancellation, removing watchdog"); + mHandler.removeCallbacks(mFpCancelNotReceived); + } + // Don't send cancel if detect succeeds + mFingerprintCancelSignal = null; setFingerprintRunningState(BIOMETRIC_STATE_STOPPED); + handleBiometricDetected(userId, FINGERPRINT, isStrongBiometric); } }; @@ -2099,6 +2121,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting void resetBiometricListeningState() { mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; + mFingerprintDetectRunning = false; } @VisibleForTesting @@ -2537,8 +2560,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported()); - final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING + final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING; + final boolean runningOrRestarting = running || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING; + final boolean runDetect = !isUnlockingWithFingerprintAllowed(); + if (runningOrRestarting && !shouldListenForFingerprint) { if (action == BIOMETRIC_ACTION_START) { mLogger.v("Ignoring stopListeningForFingerprint()"); @@ -2550,7 +2576,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.v("Ignoring startListeningForFingerprint()"); return; } - startListeningForFingerprint(); + startListeningForFingerprint(runDetect); + } else if (running && (runDetect != mFingerprintDetectRunning)) { + if (action == BIOMETRIC_ACTION_STOP) { + if (runDetect) { + mLogger.v("Allowing startListeningForFingerprint(detect) despite" + + " BIOMETRIC_ACTION_STOP since auth was running before."); + } else { + mLogger.v("Ignoring startListeningForFingerprint() switch detect -> auth"); + return; + } + } + startListeningForFingerprint(runDetect); } } @@ -2809,7 +2846,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && biometricEnabledForUser && !isUserInLockdown(user); final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed(); - final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled(); final boolean shouldListenBouncerState = !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing; @@ -2872,7 +2908,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private void startListeningForFingerprint() { + private void startListeningForFingerprint(boolean runDetect) { final int userId = mSelectedUserInteractor.getSelectedUserId(); final boolean unlockPossible = isUnlockWithFingerprintPossible(userId); if (mFingerprintCancelSignal != null) { @@ -2902,18 +2938,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintInteractiveToAuthProvider.getVendorExtension(userId)); } - if (!isUnlockingWithFingerprintAllowed()) { + if (runDetect) { mLogger.v("startListeningForFingerprint - detect"); mFpm.detectFingerprint( mFingerprintCancelSignal, mFingerprintDetectionCallback, fingerprintAuthenticateOptions); + mFingerprintDetectRunning = true; } else { mLogger.v("startListeningForFingerprint"); mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal, mFingerprintAuthenticationCallback, null /* handler */, fingerprintAuthenticateOptions); + mFingerprintDetectRunning = false; } setFingerprintRunningState(BIOMETRIC_STATE_RUNNING); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 9ebae9023d44..2000028dff41 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static com.android.systemui.Flags.migrateClocksToBlueprint; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -23,7 +24,6 @@ import android.util.Property; import android.view.View; import com.android.app.animation.Interpolators; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.statusbar.StatusBarState; @@ -109,7 +109,7 @@ public class KeyguardVisibilityHelper { animProps.setDelay(0).setDuration(160); log("goingToFullShade && !keyguardFadingAway"); } - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { log("Using LockscreenToGoneTransition 1"); } else { PropertyAnimator.setProperty( @@ -167,7 +167,7 @@ public class KeyguardVisibilityHelper { animProps, true /* animate */); } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { log("Using GoneToAodTransition"); mKeyguardViewVisibilityAnimating = false; } else { @@ -183,7 +183,7 @@ public class KeyguardVisibilityHelper { mView.setVisibility(View.VISIBLE); } } else { - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { log("Using LockscreenToGoneTransition 2"); } else { log("Direct set Visibility to GONE"); diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 878a5d88f164..a0f15efe7025 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -33,6 +33,7 @@ import com.android.systemui.plugins.clocks.ClockMessageBuffers; import com.android.systemui.res.R; import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.shared.clocks.DefaultClockProvider; +import com.android.systemui.util.ThreadAssert; import dagger.Module; import dagger.Provides; @@ -74,7 +75,8 @@ public abstract class ClockRegistryModule { clockBuffers, /* keepAllLoaded = */ false, /* subTag = */ "System", - /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK)); + /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK), + new ThreadAssert()); registry.registerListeners(); return registry; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 3397906aa6ea..0bd44f0f3901 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.hardware.biometrics.Flags.customBiometricPrompt; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION; @@ -401,37 +402,8 @@ public class AuthContainerView extends LinearLayout @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper ) { - if (Utils.isBiometricAllowed(config.mPromptInfo)) { - mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication( - config.mPromptInfo, - config.mUserId, - config.mOperationId, - new BiometricModalities(fpProps, faceProps), - config.mOpPackageName); - - if (constraintBp()) { - mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null, - // TODO(b/201510778): This uses the wrong timeout in some cases - getJankListener(mLayout, TRANSIT, - BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), - mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, - vibratorHelper); - } else { - final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( - R.layout.biometric_prompt_layout, null, false); - mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController, - // TODO(b/201510778): This uses the wrong timeout in some cases - getJankListener(view, TRANSIT, - BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), - mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, - vibratorHelper); - - // TODO(b/251476085): migrate these dependencies - if (fpProps != null && fpProps.isAnyUdfpsType()) { - view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps), - config.mScaleProvider); - } - } + if (Utils.isBiometricAllowed(config.mPromptInfo) || customBiometricPrompt()) { + addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper); } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { addCredentialView(true, false); } else { @@ -439,6 +411,44 @@ public class AuthContainerView extends LinearLayout } } + + private void addBiometricView(@NonNull Config config, @NonNull LayoutInflater layoutInflater, + @NonNull PromptViewModel viewModel, + @Nullable FingerprintSensorPropertiesInternal fpProps, + @Nullable FaceSensorPropertiesInternal faceProps, + @NonNull VibratorHelper vibratorHelper) { + mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication( + config.mPromptInfo, + config.mUserId, + config.mOperationId, + new BiometricModalities(fpProps, faceProps), + config.mOpPackageName); + + if (constraintBp()) { + mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null, + // TODO(b/201510778): This uses the wrong timeout in some cases + getJankListener(mLayout, TRANSIT, + BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), + mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, + vibratorHelper); + } else { + final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( + R.layout.biometric_prompt_layout, null, false); + mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController, + // TODO(b/201510778): This uses the wrong timeout in some cases + getJankListener(view, TRANSIT, + BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), + mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, + vibratorHelper); + + // TODO(b/251476085): migrate these dependencies + if (fpProps != null && fpProps.isAnyUdfpsType()) { + view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps), + config.mScaleProvider); + } + } + } + private void onBackInvoked() { sendEarlyUserCanceled(); animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); @@ -524,7 +534,7 @@ public class AuthContainerView extends LinearLayout () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)); if (constraintBp()) { // Do nothing on attachment with constraintLayout - } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { + } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo) || customBiometricPrompt()) { mBiometricScrollView.addView(mBiometricView.asView()); } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { addCredentialView(true /* animatePanel */, false /* animateContents */); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 31aadf51c4f2..b0cc3bd807dd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -22,6 +22,7 @@ import android.content.Context import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricPrompt +import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.face.FaceManager import android.text.method.ScrollingMovementMethod import android.util.Log @@ -123,13 +124,6 @@ object BiometricViewBinder { (view as BiometricPromptLayout).updatedFingerprintAffordanceSize } - PromptIconViewBinder.bind( - iconView, - iconOverlayView, - iconSizeOverride, - viewModel, - ) - val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator) // Negative-side (left) buttons @@ -156,6 +150,18 @@ object BiometricViewBinder { view.repeatWhenAttached { // these do not change and need to be set before any size transitions val modalities = viewModel.modalities.first() + + // If there is no biometrics available, biometric prompt is showing just for displaying + // content, no authentication needed. + if (!(customBiometricPrompt() && modalities.isEmpty)) { + PromptIconViewBinder.bind( + iconView, + iconOverlayView, + iconSizeOverride, + viewModel, + ) + } + if (modalities.hasFingerprint) { /** * Load the given [rawResources] immediately so they are cached for use in the 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 2417fe9cd333..a37d9168dfd3 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 @@ -21,6 +21,7 @@ import android.animation.AnimatorSet import android.animation.ValueAnimator import android.graphics.Outline import android.graphics.Rect +import android.hardware.biometrics.Flags import android.transition.AutoTransition import android.transition.TransitionManager import android.view.Surface @@ -59,6 +60,7 @@ 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. */ @@ -219,6 +221,18 @@ object BiometricViewSizeBinder { view.repeatWhenAttached { var currentSize: PromptSize? = null + val modalities = viewModel.modalities.first() + // TODO(b/288175072): Move all visibility settings together. + // If there is no biometrics available, biometric prompt is showing just for + // displaying content, no authentication needed. + if (Flags.customBiometricPrompt() && modalities.isEmpty) { + smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) + smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + smallConstraintSet.setVisibility(R.id.indicator, View.GONE) + mediumConstraintSet.setVisibility(iconHolderView.id, View.GONE) + mediumConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + mediumConstraintSet.setVisibility(R.id.indicator, View.GONE) + } lifecycleScope.launch { combine(viewModel.position, viewModel.size, ::Pair).collect { (position, size) -> @@ -299,6 +313,7 @@ object BiometricViewSizeBinder { // TODO(b/251476085): migrate the legacy panel controller and simplify this view.repeatWhenAttached { var currentSize: PromptSize? = null + val modalities = viewModel.modalities.first() lifecycleScope.launch { /** * View is only set visible in BiometricViewSizeBinder once PromptSize is @@ -318,6 +333,9 @@ object BiometricViewSizeBinder { for (v in viewsToHideWhenSmall) { v.showContentOrHide(forceHide = size.isSmall) } + if (Flags.customBiometricPrompt() && modalities.isEmpty) { + iconHolderView.visibility = View.GONE + } if (currentSize == null && size.isSmall) { iconHolderView.alpha = 0f } @@ -328,8 +346,7 @@ object BiometricViewSizeBinder { // TODO(b/302735104): Fix wrong height due to the delay of // PromptContentView. addOnLayoutChangeListener() will cause crash when // showing credential view, since |PromptIconViewModel| won't release - // the - // flow. + // the flow. // propagate size changes to legacy panel controller and animate // transitions view.doOnLayout { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index 7b4be0220ff2..9c28f1c16546 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -115,17 +115,11 @@ constructor( } private var overlayView: View? = null - private var lottie: LottieAnimationView? = null /** Show the side fingerprint sensor indicator */ private fun show() { - overlayView?.let { - if (it.isAttachedToWindow) { - lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation) - lottie?.pauseAnimation() - lottie?.removeAllLottieOnCompositionLoadedListener() - windowManager.get().removeView(it) - } + if (overlayView?.isAttachedToWindow == true) { + return } overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt index 8197145f9646..c25e748f8668 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt @@ -50,6 +50,7 @@ import com.android.systemui.res.R.string.kg_fp_not_recognized import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin +import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock import com.android.systemui.res.R.string.kg_prompt_after_update_password import com.android.systemui.res.R.string.kg_prompt_after_update_pattern @@ -208,6 +209,11 @@ constructor( } else { faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) } + } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) { + authRequiredAfterAdaptiveAuthRequest( + currentSecurityMode, + isFingerprintAuthCurrentlyAllowed.value + ) } else if ( trustOrBiometricsAvailable && flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout @@ -464,6 +470,34 @@ private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerM }.toMessage() } +private fun authRequiredAfterAdaptiveAuthRequest( + securityMode: SecurityMode, + fpAuthIsAllowed: Boolean +): BouncerMessageModel { + return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode) + else + return when (securityMode) { + SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock) + SecurityMode.Password -> + Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock) + SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock) + else -> Pair(0, 0) + }.toMessage() +} + +private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed( + securityMode: SecurityMode +): BouncerMessageModel { + return when (securityMode) { + SecurityMode.Pattern -> + Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock) + SecurityMode.Password -> + Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock) + SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock) + else -> Pair(0, 0) + }.toMessage() +} + private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel { return when (securityMode) { SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern) diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 8178adef49b2..a0aaa906802a 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -79,7 +79,7 @@ interface BaseComposeFacade { fun setVolumePanelActivityContent( activity: ComponentActivity, viewModel: VolumePanelViewModel, - onDismissAnimationFinished: () -> Unit, + onDismiss: () -> Unit, ) /** Create a [View] to represent [viewModel] on screen. */ diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 41ce3fd11e8a..7a24d7693035 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -29,7 +29,6 @@ import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW import com.android.systemui.keyguard.shared.ComposeLockscreen -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor @@ -52,15 +51,11 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token - // Internal keyguard dependencies - KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor - // SceneContainer dependencies SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta } SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW // ComposeLockscreen dependencies - ComposeLockscreen.token dependsOn KeyguardShadeMigrationNssl.token ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor ComposeLockscreen.token dependsOn migrateClocksToBlueprint } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index e23ec894f8f3..00ec1a14bb93 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -404,6 +404,7 @@ public class KeyguardIndicationRotateTextViewController extends public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11; public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP = 12; public static final int INDICATION_IS_DISMISSIBLE = 13; + public static final int INDICATION_TYPE_ADAPTIVE_AUTH = 14; @IntDef({ INDICATION_TYPE_NONE, @@ -419,7 +420,8 @@ public class KeyguardIndicationRotateTextViewController extends INDICATION_TYPE_REVERSE_CHARGING, INDICATION_TYPE_BIOMETRIC_MESSAGE, INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, - INDICATION_IS_DISMISSIBLE + INDICATION_IS_DISMISSIBLE, + INDICATION_TYPE_ADAPTIVE_AUTH }) @Retention(RetentionPolicy.SOURCE) public @interface IndicationType{} @@ -455,6 +457,8 @@ public class KeyguardIndicationRotateTextViewController extends return "biometric_message"; case INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP: return "biometric_message_followup"; + case INDICATION_TYPE_ADAPTIVE_AUTH: + return "adaptive_auth"; default: return "unknown[" + type + "]"; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 86b99ecac66c..e35c5a636bde 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -53,6 +53,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel import com.android.systemui.plugins.FalsingManager @@ -98,6 +99,7 @@ constructor( private val falsingManager: FalsingManager, private val aodAlphaViewModel: AodAlphaViewModel, private val keyguardClockViewModel: KeyguardClockViewModel, + private val smartspaceViewModel: KeyguardSmartspaceViewModel, private val lockscreenContentViewModel: LockscreenContentViewModel, private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>, private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder, @@ -148,6 +150,7 @@ constructor( keyguardRootView, keyguardBlueprintViewModel, keyguardClockViewModel, + smartspaceViewModel, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b5ca79e71c33..641b9677c2e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -30,6 +30,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BA import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; @@ -690,18 +691,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } else { resetStateLocked(); } - } else { - if (lastSimStateWasLocked && mShowing) { - if (DEBUG_SIM_STATES) { - Log.d(TAG, "SIM moved to " - + "NOT_READY/ABSENT/UNKNOWN when the previous state " - + "was locked. Reset the state."); - } + } + if (simState == TelephonyManager.SIM_STATE_ABSENT) { + // MVNO SIMs can become transiently NOT_READY when switching networks, + // so we should only lock when they are ABSENT. + if (lastSimStateWasLocked) { + if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the " + + "previous state was locked. Reset the state."); resetStateLocked(); } + mSimWasLocked.append(slotId, false); } - - mSimWasLocked.append(slotId, false); } break; case TelephonyManager.SIM_STATE_PIN_REQUIRED: @@ -921,15 +921,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; } else if ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) { return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; + } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 + || mUpdateMonitor.isFingerprintLockedOut())) { + return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; + } else if ((strongAuth & SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST) != 0) { + return KeyguardSecurityView.PROMPT_REASON_ADAPTIVE_AUTH_REQUEST; } else if (trustAgentsEnabled && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; } else if (trustAgentsEnabled && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) { return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; - } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 - || mUpdateMonitor.isFingerprintLockedOut())) { - return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) { return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; } else if (any && (strongAuth 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 405d1d46456c..78749ead7ef9 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 @@ -241,6 +241,9 @@ constructor( /** * When the lockscreen can be dismissed, emit an alpha value as the user swipes up. This is * useful just before the code commits to moving to GONE. + * + * This uses legacyShadeExpansion to process swipe up events. In the future, the touch input + * signal should be sent directly to transitions. */ val dismissAlpha: Flow<Float?> = combine( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt deleted file mode 100644 index 23642a741fb8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.keyguard.shared - -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -/** Helper for reading or using the keyguard shade migration nssl flag state. */ -@Suppress("NOTHING_TO_INLINE") -object KeyguardShadeMigrationNssl { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the refactor enabled */ - @JvmStatic - inline val isEnabled - get() = Flags.keyguardShadeMigrationNssl() - - /** - * Called to ensure code is only run when the flag is enabled. This protects users from the - * unintended behaviors caused by accidentally running new logic, while also crashing on an eng - * build to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) - - /** - * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is enabled to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt index cf5b88fde3dc..08904b6ffa86 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt @@ -60,6 +60,12 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) { LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT ) + + val isSomeAuthRequiredAfterAdaptiveAuthRequest = + containsFlag( + flag, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST + ) } private fun containsFlag(haystack: Int, needle: Int): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 6e70368476ed..66fc99567d42 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.Intra import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.lifecycle.repeatWhenAttached import javax.inject.Inject import kotlin.math.max @@ -84,6 +85,7 @@ constructor( constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel, clockViewModel: KeyguardClockViewModel, + smartspaceViewModel: KeyguardSmartspaceViewModel, ) { constraintLayout.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { @@ -108,10 +110,18 @@ constructor( ) { BaseBlueprintTransition(clockViewModel) .addTransition( - IntraBlueprintTransition(Config.DEFAULT, clockViewModel) + IntraBlueprintTransition( + Config.DEFAULT, + clockViewModel, + smartspaceViewModel + ) ) } else { - IntraBlueprintTransition(Config.DEFAULT, clockViewModel) + IntraBlueprintTransition( + Config.DEFAULT, + clockViewModel, + smartspaceViewModel + ) } runTransition(constraintLayout, transition, Config.DEFAULT) { @@ -136,7 +146,11 @@ constructor( runTransition( constraintLayout, - IntraBlueprintTransition(transition, clockViewModel), + IntraBlueprintTransition( + transition, + clockViewModel, + smartspaceViewModel + ), transition, ) { cs.applyTo(constraintLayout) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 5604ef23a142..c58a03c05a09 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -43,7 +43,6 @@ import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel @@ -146,7 +145,7 @@ object KeyguardRootViewBinder { } } - if (KeyguardShadeMigrationNssl.isEnabled) { + if (migrateClocksToBlueprint()) { launch { viewModel.burnInLayerVisibility.collect { visibility -> childViews[burnInLayerId]?.visibility = visibility @@ -316,7 +315,7 @@ object KeyguardRootViewBinder { } } - if (KeyguardShadeMigrationNssl.isEnabled) { + if (migrateClocksToBlueprint()) { burnInParams.update { current -> current.copy(translationY = { childViews[burnInLayerId]?.translationY }) } @@ -415,7 +414,9 @@ object KeyguardRootViewBinder { configuration: ConfigurationState, screenOffAnimationController: ScreenOffAnimationController, ) { - KeyguardShadeMigrationNssl.assertInLegacyMode() + if (migrateClocksToBlueprint()) { + throw IllegalStateException("should only be called in legacy code paths") + } if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return coroutineScope { val iconAppearTranslationPx = @@ -444,7 +445,7 @@ object KeyguardRootViewBinder { } when { !isVisible.isAnimating -> { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { translationY = 0f } visibility = @@ -494,7 +495,7 @@ object KeyguardRootViewBinder { animatorListener: Animator.AnimatorListener, ) { if (animate) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { translationY = -iconAppearTranslation.toFloat() } alpha = 0f @@ -502,19 +503,19 @@ object KeyguardRootViewBinder { .alpha(1f) .setInterpolator(Interpolators.LINEAR) .setDuration(AOD_ICONS_APPEAR_DURATION) - .apply { if (KeyguardShadeMigrationNssl.isEnabled) animateInIconTranslation() } + .apply { if (migrateClocksToBlueprint()) animateInIconTranslation() } .setListener(animatorListener) .start() } else { alpha = 1.0f - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { translationY = 0f } } } private fun View.animateInIconTranslation() { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index bc9671e65f24..77f7ac8571dd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -33,6 +33,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSec import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSliceViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import java.util.Optional import javax.inject.Inject @@ -65,6 +66,7 @@ constructor( communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, clockSection: ClockSection, smartspaceSection: SmartspaceSection, + keyguardSliceViewSection: KeyguardSliceViewSection, udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection, ) : KeyguardBlueprint { override val id: String = DEFAULT @@ -83,6 +85,7 @@ constructor( aodBurnInSection, communalTutorialIndicatorSection, clockSection, + keyguardSliceViewSection, defaultDeviceEntrySection, udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index d118d4d11948..55b2381c79e4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -33,6 +33,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSec import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSliceViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.util.kotlin.getOrNull import java.util.Optional @@ -60,6 +61,7 @@ constructor( communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, clockSection: ClockSection, smartspaceSection: SmartspaceSection, + keyguardSliceViewSection: KeyguardSliceViewSection, udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection, ) : KeyguardBlueprint { override val id: String = SHORTCUTS_BESIDE_UDFPS @@ -78,6 +80,7 @@ constructor( aodBurnInSection, communalTutorialIndicatorSection, clockSection, + keyguardSliceViewSection, defaultDeviceEntrySection, udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt index a7075d97459e..3adeb2aeb283 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt @@ -20,10 +20,12 @@ import android.transition.TransitionSet import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition import com.android.systemui.keyguard.ui.view.layout.sections.transitions.DefaultClockSteppingTransition import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel class IntraBlueprintTransition( config: IntraBlueprintTransition.Config, clockViewModel: KeyguardClockViewModel, + smartspaceViewModel: KeyguardSmartspaceViewModel, ) : TransitionSet() { enum class Type( @@ -56,7 +58,7 @@ class IntraBlueprintTransition( Type.NoTransition -> {} Type.DefaultClockStepping -> addTransition(clockViewModel.clock?.let { DefaultClockSteppingTransition(it) }) - else -> addTransition(ClockSizeTransition(config, clockViewModel)) + else -> addTransition(ClockSizeTransition(config, clockViewModel, smartspaceViewModel)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 9a1fcc1a6a51..282c4952d557 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -22,7 +22,6 @@ import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.Flags.migrateClocksToBlueprint -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.res.R @@ -37,7 +36,7 @@ constructor( ) : KeyguardSection() { private lateinit var burnInLayer: AodBurnInLayer override fun addViews(constraintLayout: ConstraintLayout) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } @@ -58,16 +57,14 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } - if (migrateClocksToBlueprint()) { - clockViewModel.burnInLayer = burnInLayer - } + clockViewModel.burnInLayer = burnInLayer } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index ad589dfcff9e..3d9c04e39679 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -28,7 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore @@ -59,7 +58,7 @@ constructor( private lateinit var nic: NotificationIconContainer override fun addViews(constraintLayout: ConstraintLayout) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } nic = @@ -78,7 +77,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } @@ -99,7 +98,7 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } val bottomMargin = @@ -114,12 +113,9 @@ constructor( BOTTOM } constraintSet.apply { - if (migrateClocksToBlueprint()) { - connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin) - setGoneMargin(nicId, BOTTOM, bottomMargin) - } else { - connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin) - } + connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin) + setGoneMargin(nicId, BOTTOM, bottomMargin) + connect( nicId, START, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index 75132a59eb88..218af2994f4a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -27,7 +27,6 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.centralizedStatusBarDimensRefactor import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.LargeScreenHeaderHelper @@ -71,7 +70,7 @@ constructor( mainDispatcher, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt index 851a45f31705..390b39f1e202 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt @@ -31,8 +31,8 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.keyguard.KeyguardStatusView import com.android.keyguard.dagger.KeyguardStatusViewComponent +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.keyguard.KeyguardViewConfigurator -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.media.controls.ui.KeyguardMediaController import com.android.systemui.res.R @@ -58,7 +58,7 @@ constructor( private val statusViewId = R.id.keyguard_status_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available. @@ -83,7 +83,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (KeyguardShadeMigrationNssl.isEnabled) { + if (migrateClocksToBlueprint()) { constraintLayout.findViewById<KeyguardStatusView?>(R.id.keyguard_status_view)?.let { val statusViewComponent = keyguardStatusViewComponentFactory.build(it, context.display) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt new file mode 100644 index 000000000000..d572c51d1146 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt @@ -0,0 +1,89 @@ +/* + * 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.systemui.keyguard.ui.view.layout.sections + +import android.view.View +import android.view.ViewGroup +import androidx.constraintlayout.widget.Barrier +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.Flags.migrateClocksToBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.res.R +import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController +import javax.inject.Inject + +class KeyguardSliceViewSection +@Inject +constructor( + val smartspaceController: LockscreenSmartspaceController, +) : KeyguardSection() { + override fun addViews(constraintLayout: ConstraintLayout) { + if (!migrateClocksToBlueprint()) return + if (smartspaceController.isEnabled()) return + + constraintLayout.findViewById<View?>(R.id.keyguard_slice_view)?.let { + (it.parent as ViewGroup).removeView(it) + constraintLayout.addView(it) + } + } + + override fun bindData(constraintLayout: ConstraintLayout) {} + + override fun applyConstraints(constraintSet: ConstraintSet) { + if (!migrateClocksToBlueprint()) return + if (smartspaceController.isEnabled()) return + + constraintSet.apply { + connect( + R.id.keyguard_slice_view, + ConstraintSet.START, + ConstraintSet.PARENT_ID, + ConstraintSet.START + ) + connect( + R.id.keyguard_slice_view, + ConstraintSet.END, + ConstraintSet.PARENT_ID, + ConstraintSet.END + ) + constrainHeight(R.id.keyguard_slice_view, ConstraintSet.WRAP_CONTENT) + + connect( + R.id.keyguard_slice_view, + ConstraintSet.TOP, + R.id.lockscreen_clock_view, + ConstraintSet.BOTTOM + ) + + createBarrier( + R.id.smart_space_barrier_bottom, + Barrier.BOTTOM, + 0, + *intArrayOf(R.id.keyguard_slice_view) + ) + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + if (!migrateClocksToBlueprint()) return + if (smartspaceController.isEnabled()) return + + constraintLayout.removeView(R.id.keyguard_slice_view) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index 52d94a087110..d0f57c7f9ded 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -25,8 +25,8 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlags @@ -83,7 +83,7 @@ constructor( } override fun addViews(constraintLayout: ConstraintLayout) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } // This moves the existing NSSL view to a different parent, as the controller is a @@ -99,7 +99,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 8255bcc87400..b0f7a258a4e6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -57,6 +57,7 @@ constructor( override fun addViews(constraintLayout: ConstraintLayout) { if (!migrateClocksToBlueprint()) return + if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return smartspaceView = smartspaceController.buildAndConnectView(constraintLayout) weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout) dateView = smartspaceController.buildAndConnectDateView(constraintLayout) @@ -83,6 +84,7 @@ constructor( override fun bindData(constraintLayout: ConstraintLayout) { if (!migrateClocksToBlueprint()) return + if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return KeyguardSmartspaceViewBinder.bind( constraintLayout, keyguardClockViewModel, @@ -93,6 +95,7 @@ constructor( override fun applyConstraints(constraintSet: ConstraintSet) { if (!migrateClocksToBlueprint()) return + if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return val horizontalPaddingStart = context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) + context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal) @@ -189,6 +192,7 @@ constructor( override fun removeViews(constraintLayout: ConstraintLayout) { if (!migrateClocksToBlueprint()) return + if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return listOf(smartspaceView, dateView, weatherView).forEach { it?.let { if (it.parent == constraintLayout) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index 3e35ae4b2dc3..2545302ccaa1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -23,8 +23,8 @@ import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlags @@ -67,7 +67,7 @@ constructor( mainDispatcher, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { return } constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 64cbb3229a57..ab0d4892ae0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -31,6 +31,7 @@ import com.android.app.animation.Interpolators import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import kotlin.math.abs @@ -41,12 +42,13 @@ internal fun View.setRect(rect: Rect) = class ClockSizeTransition( config: IntraBlueprintTransition.Config, clockViewModel: KeyguardClockViewModel, + smartspaceViewModel: KeyguardSmartspaceViewModel, ) : TransitionSet() { init { ordering = ORDERING_TOGETHER if (config.type != Type.SmartspaceVisibility) { - addTransition(ClockFaceOutTransition(config, clockViewModel)) - addTransition(ClockFaceInTransition(config, clockViewModel)) + addTransition(ClockFaceOutTransition(config, clockViewModel, smartspaceViewModel)) + addTransition(ClockFaceInTransition(config, clockViewModel, smartspaceViewModel)) } addTransition(SmartspaceMoveTransition(config, clockViewModel)) } @@ -197,12 +199,13 @@ class ClockSizeTransition( class ClockFaceInTransition( config: IntraBlueprintTransition.Config, val viewModel: KeyguardClockViewModel, + val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : VisibilityBoundsTransition() { init { duration = CLOCK_IN_MILLIS startDelay = CLOCK_IN_START_DELAY_MILLIS interpolator = CLOCK_IN_INTERPOLATOR - captureSmartspace = !viewModel.useLargeClock + captureSmartspace = !viewModel.useLargeClock && smartspaceViewModel.isSmartspaceEnabled if (viewModel.useLargeClock) { viewModel.clock?.let { it.largeClock.layout.views.forEach { addTarget(it) } } @@ -252,11 +255,12 @@ class ClockSizeTransition( class ClockFaceOutTransition( config: IntraBlueprintTransition.Config, val viewModel: KeyguardClockViewModel, + val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : VisibilityBoundsTransition() { init { duration = CLOCK_OUT_MILLIS interpolator = CLOCK_OUT_INTERPOLATOR - captureSmartspace = viewModel.useLargeClock + captureSmartspace = viewModel.useLargeClock && smartspaceViewModel.isSmartspaceEnabled if (viewModel.useLargeClock) { addTarget(R.id.lockscreen_clock_view) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt index a3888c3341db..9fa14236ee77 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -102,7 +102,6 @@ constructor( override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.sharedFlow( duration = 500.milliseconds, - onStart = { 1f }, onStep = { 1f }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt index 85885b065264..9fc759b8eca1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -53,7 +53,6 @@ constructor( return transitionAnimation.sharedFlowWithState( startTime = 600.milliseconds, duration = 500.milliseconds, - onStart = { translatePx }, onStep = { translatePx + it * -translatePx }, onFinish = { 0f }, onCancel = { 0f }, @@ -66,7 +65,6 @@ constructor( transitionAnimation.sharedFlow( startTime = 700.milliseconds, duration = 400.milliseconds, - onStart = { 0f }, onStep = { it }, onFinish = { 1f }, onCancel = { 1f }, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt index c61b1f5cc3be..d7ba46b6e708 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt @@ -54,7 +54,6 @@ constructor( startTime = 233.milliseconds, duration = 250.milliseconds, onStep = { it }, - onStart = { 0f }, ) override val deviceEntryParentViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index 19c11a947fe2..3a19780c7017 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -78,7 +78,6 @@ constructor( startTime = 233.milliseconds, duration = 250.milliseconds, onStep = { it }, - onStart = { 0f }, name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha", ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt index 1b14f75d54ef..898eacff6246 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt @@ -25,8 +25,9 @@ import android.widget.SeekBar import android.widget.TextView import androidx.constraintlayout.widget.Barrier import com.android.internal.widget.CachingIconView -import com.android.systemui.res.R import com.android.systemui.media.controls.models.GutsViewHolder +import com.android.systemui.res.R +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView import com.android.systemui.surfaceeffects.ripple.MultiRippleView import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView import com.android.systemui.util.animation.TransitionLayout @@ -42,6 +43,7 @@ class MediaViewHolder constructor(itemView: View) { val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view) val turbulenceNoiseView = itemView.requireViewById<TurbulenceNoiseView>(R.id.turbulence_noise_view) + val loadingEffectView = itemView.requireViewById<LoadingEffectView>(R.id.loading_effect_view) val appIcon = itemView.requireViewById<ImageView>(R.id.icon) val titleText = itemView.requireViewById<TextView>(R.id.header_title) val artistText = itemView.requireViewById<TextView>(R.id.header_artist) @@ -171,6 +173,7 @@ class MediaViewHolder constructor(itemView: View) { setOf( R.id.album_art, R.id.turbulence_noise_view, + R.id.loading_effect_view, R.id.touch_ripple_view, ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt index c87fd14a943d..952f9b8711f0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt @@ -29,6 +29,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.media.controls.models.player.MediaViewHolder import com.android.systemui.monet.ColorScheme +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect import com.android.systemui.surfaceeffects.ripple.MultiRippleController import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController @@ -118,6 +119,7 @@ internal constructor( turbulenceNoiseController, ::AnimatingColorTransition ) + var loadingEffect: LoadingEffect? = null val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_neutral20) val surfaceColor = @@ -128,7 +130,6 @@ internal constructor( mediaViewHolder.albumView.backgroundTintList = colorList mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor) } - val accentPrimary = animatingColorTransitionFactory( loadDefaultColor(R.attr.textColorPrimary), @@ -139,6 +140,7 @@ internal constructor( mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) multiRippleController.updateColor(accentPrimary) turbulenceNoiseController.updateNoiseColor(accentPrimary) + loadingEffect?.updateColor(accentPrimary) } val accentSecondary = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index aa92814a584d..e97c9d3d8c0b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -41,6 +41,7 @@ import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; +import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.BitmapDrawable; @@ -81,6 +82,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.widget.CachingIconView; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.ActivityIntentHelper; +import com.android.systemui.Flags; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.GhostedViewTransitionAnimatorController; import com.android.systemui.bluetooth.BroadcastDialogController; @@ -111,6 +113,9 @@ import com.android.systemui.res.R; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect; +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState; +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView; import com.android.systemui.surfaceeffects.ripple.MultiRippleController; import com.android.systemui.surfaceeffects.ripple.MultiRippleView; import com.android.systemui.surfaceeffects.ripple.RippleAnimation; @@ -248,13 +253,34 @@ public class MediaControlPanel { private String mCurrentBroadcastApp; private MultiRippleController mMultiRippleController; private TurbulenceNoiseController mTurbulenceNoiseController; + private LoadingEffect mLoadingEffect; private final GlobalSettings mGlobalSettings; - + private final Random mRandom = new Random(); private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig; private boolean mWasPlaying = false; private boolean mButtonClicked = false; - private final Random mRandom = new Random(); + private final LoadingEffect.Companion.PaintDrawCallback mNoiseDrawCallback = + new LoadingEffect.Companion.PaintDrawCallback() { + @Override + public void onDraw(@NonNull Paint loadingPaint) { + mMediaViewHolder.getLoadingEffectView().draw(loadingPaint); + } + }; + private final LoadingEffect.Companion.AnimationStateChangedCallback mStateChangedCallback = + new LoadingEffect.Companion.AnimationStateChangedCallback() { + @Override + public void onStateChanged(@NonNull AnimationState oldState, + @NonNull AnimationState newState) { + LoadingEffectView loadingEffectView = + mMediaViewHolder.getLoadingEffectView(); + if (newState == AnimationState.NOT_PLAYING) { + loadingEffectView.setVisibility(View.INVISIBLE); + } else { + loadingEffectView.setVisibility(View.VISIBLE); + } + } + }; /** * Initialize a new control panel @@ -456,6 +482,10 @@ public class MediaControlPanel { TurbulenceNoiseView turbulenceNoiseView = vh.getTurbulenceNoiseView(); turbulenceNoiseView.setBlendMode(BlendMode.SCREEN); + LoadingEffectView loadingEffectView = vh.getLoadingEffectView(); + loadingEffectView.setBlendMode(BlendMode.SCREEN); + loadingEffectView.setVisibility(View.INVISIBLE); + mTurbulenceNoiseController = new TurbulenceNoiseController(turbulenceNoiseView); mColorSchemeTransition = new ColorSchemeTransition( @@ -587,22 +617,41 @@ public class MediaControlPanel { } } - // Turbulence noise if (shouldPlayTurbulenceNoise()) { + // Need to create the config here to get the correct view size and color. if (mTurbulenceNoiseAnimationConfig == null) { mTurbulenceNoiseAnimationConfig = - createTurbulenceNoiseAnimation(); + createTurbulenceNoiseConfig(); + } + + if (Flags.shaderlibLoadingEffectRefactor()) { + if (mLoadingEffect == null) { + mLoadingEffect = new LoadingEffect( + Type.SIMPLEX_NOISE, + mTurbulenceNoiseAnimationConfig, + mNoiseDrawCallback, + mStateChangedCallback + ); + mColorSchemeTransition.setLoadingEffect(mLoadingEffect); + } + + mLoadingEffect.play(); + mMainExecutor.executeDelayed( + mLoadingEffect::finish, + TURBULENCE_NOISE_PLAY_DURATION + ); + } else { + mTurbulenceNoiseController.play( + Type.SIMPLEX_NOISE, + mTurbulenceNoiseAnimationConfig + ); + mMainExecutor.executeDelayed( + mTurbulenceNoiseController::finish, + TURBULENCE_NOISE_PLAY_DURATION + ); } - // Color will be correctly updated in ColorSchemeTransition. - mTurbulenceNoiseController.play( - Type.SIMPLEX_NOISE, - mTurbulenceNoiseAnimationConfig - ); - mMainExecutor.executeDelayed( - mTurbulenceNoiseController::finish, - TURBULENCE_NOISE_PLAY_DURATION - ); } + mButtonClicked = false; mWasPlaying = isPlaying(); @@ -1232,7 +1281,13 @@ public class MediaControlPanel { return mButtonClicked && !mWasPlaying && isPlaying(); } - private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() { + private TurbulenceNoiseAnimationConfig createTurbulenceNoiseConfig() { + View targetView = Flags.shaderlibLoadingEffectRefactor() + ? mMediaViewHolder.getLoadingEffectView() : + mMediaViewHolder.getTurbulenceNoiseView(); + int width = targetView.getWidth(); + int height = targetView.getHeight(); + return new TurbulenceNoiseAnimationConfig( /* gridCount= */ 2.14f, TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER, @@ -1242,10 +1297,11 @@ public class MediaControlPanel { /* noiseMoveSpeedX= */ 0.42f, /* noiseMoveSpeedY= */ 0f, TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z, + // Color will be correctly updated in ColorSchemeTransition. /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(), /* backgroundColor= */ Color.BLACK, - /* width= */ mMediaViewHolder.getTurbulenceNoiseView().getWidth(), - /* height= */ mMediaViewHolder.getTurbulenceNoiseView().getHeight(), + width, + height, TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS, /* easeInDuration= */ 1350f, /* easeOutDuration= */ 1350f, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt index 25d89fac1af5..02be0c1a6c2d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -35,10 +35,10 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import javax.inject.Inject -/** - * Factory to create [MediaOutputDialog] objects. - */ -open class MediaOutputDialogFactory @Inject constructor( +/** Factory to create [MediaOutputDialog] objects. */ +open class MediaOutputDialogFactory +@Inject +constructor( private val context: Context, private val mediaSessionManager: MediaSessionManager, private val lbm: LocalBluetoothManager?, @@ -55,46 +55,93 @@ open class MediaOutputDialogFactory @Inject constructor( private val userTracker: UserTracker ) { companion object { - private const val INTERACTION_JANK_TAG = "media_output" + const val INTERACTION_JANK_TAG = "media_output" var mediaOutputDialog: MediaOutputDialog? = null } /** Creates a [MediaOutputDialog] for the given package. */ open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) { - create(packageName, aboveStatusBar, view, includePlaybackAndAppMetadata = true) + createWithController( + packageName, + aboveStatusBar, + controller = + view?.let { + DialogTransitionAnimator.Controller.fromView( + it, + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG + ) + ) + }, + ) } - open fun createDialogForSystemRouting() { - create(packageName = null, aboveStatusBar = false, includePlaybackAndAppMetadata = false) + /** Creates a [MediaOutputDialog] for the given package. */ + open fun createWithController( + packageName: String, + aboveStatusBar: Boolean, + controller: DialogTransitionAnimator.Controller?, + ) { + create( + packageName, + aboveStatusBar, + dialogTransitionAnimatorController = controller, + includePlaybackAndAppMetadata = true + ) + } + + open fun createDialogForSystemRouting(controller: DialogTransitionAnimator.Controller? = null) { + create( + packageName = null, + aboveStatusBar = false, + dialogTransitionAnimatorController = null, + includePlaybackAndAppMetadata = false + ) } private fun create( - packageName: String?, - aboveStatusBar: Boolean, - view: View? = null, - includePlaybackAndAppMetadata: Boolean = true + packageName: String?, + aboveStatusBar: Boolean, + dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?, + includePlaybackAndAppMetadata: Boolean = true ) { // Dismiss the previous dialog, if any. mediaOutputDialog?.dismiss() - val controller = MediaOutputController( - context, packageName, - mediaSessionManager, lbm, starter, notifCollection, - dialogTransitionAnimator, nearbyMediaDevicesManager, audioManager, - powerExemptionManager, keyGuardManager, featureFlags, userTracker) + val controller = + MediaOutputController( + context, + packageName, + mediaSessionManager, + lbm, + starter, + notifCollection, + dialogTransitionAnimator, + nearbyMediaDevicesManager, + audioManager, + powerExemptionManager, + keyGuardManager, + featureFlags, + userTracker + ) val dialog = - MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, - dialogTransitionAnimator, uiEventLogger, includePlaybackAndAppMetadata) + MediaOutputDialog( + context, + aboveStatusBar, + broadcastSender, + controller, + dialogTransitionAnimator, + uiEventLogger, + includePlaybackAndAppMetadata + ) mediaOutputDialog = dialog // Show the dialog. - if (view != null) { - dialogTransitionAnimator.showFromView( - dialog, view, - cuj = DialogCuj( - InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG - ) + if (dialogTransitionAnimatorController != null) { + dialogTransitionAnimator.show( + dialog, + dialogTransitionAnimatorController, ) } else { dialog.show() diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 092f1ed7d498..152f193be3f9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -44,6 +44,7 @@ import android.view.WindowManagerGlobal; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.RegisterStatusBarResult; import com.android.settingslib.applications.InterestingConfigChanges; @@ -89,7 +90,16 @@ public class NavigationBarControllerImpl implements private final TaskbarDelegate mTaskbarDelegate; private final NavBarHelper mNavBarHelper; private int mNavMode; + /** + * Indicates whether the active display is a large screen, e.g. tablets, foldable devices in + * the unfolded state. + */ @VisibleForTesting boolean mIsLargeScreen; + /** + * Indicates whether the device is a phone, rather than everything else (e.g. foldables, + * tablets) is considered not a handheld device. + */ + @VisibleForTesting boolean mIsPhone; /** A displayId - nav bar maps. */ @VisibleForTesting @@ -139,6 +149,8 @@ public class NavigationBarControllerImpl implements dumpManager, autoHideController, lightBarController, pipOptional, backAnimation.orElse(null), taskStackChangeListeners); mIsLargeScreen = isLargeScreen(mContext); + mIsPhone = + mContext.getResources().getIntArray(R.array.config_foldedDeviceStates).length == 0; dumpManager.registerDumpable(this); } @@ -253,9 +265,8 @@ public class NavigationBarControllerImpl implements /** @return {@code true} if taskbar is enabled, false otherwise */ private boolean initializeTaskbarIfNecessary() { - // Enable for large screens or (phone AND flag is set); assuming phone = !mIsLargeScreen - boolean taskbarEnabled = (mIsLargeScreen || enableTaskbarNavbarUnification()) - && shouldCreateNavBarAndTaskBar(mContext.getDisplayId()); + boolean taskbarEnabled = supportsTaskbar() && shouldCreateNavBarAndTaskBar( + mContext.getDisplayId()); if (taskbarEnabled) { Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary"); @@ -274,6 +285,12 @@ public class NavigationBarControllerImpl implements return taskbarEnabled; } + @VisibleForTesting + boolean supportsTaskbar() { + // Enable for tablets, unfolded state on a foldable device or (non handheld AND flag is set) + return mIsLargeScreen || (!mIsPhone && enableTaskbarNavbarUnification()); + } + private final CommandQueue.Callbacks mCommandQueueCallbacks = new CommandQueue.Callbacks() { @Override public void onDisplayRemoved(int displayId) { diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java index 3671dd439239..b4cc196b89ed 100644 --- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java @@ -39,7 +39,10 @@ public class ProcessWrapper { /** * Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}. * - * Please strongly consider using {@link com.android.systemui.settings.UserTracker} instead. + * This should not be used to get the "current" user. This information only applies to the + * current process, not the current state of SystemUI. Please use + * {@link com.android.systemui.settings.UserTracker} if you want to learn about the currently + * active user in SystemUI. */ public UserHandle myUserHandle() { return Process.myUserHandle(); diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index a755805d1872..4ccb18fced56 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -19,15 +19,16 @@ package com.android.systemui.scene.shared.flag import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.keyguardBottomAreaRefactor +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.Flags.sceneContainer import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FlagToken import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED import com.android.systemui.flags.RefactorFlagUtils -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.media.controls.util.MediaInSceneContainerFlag import dagger.Module import dagger.Provides @@ -43,7 +44,7 @@ object SceneContainerFlag { SCENE_CONTAINER_ENABLED && // mainStaticFlag sceneContainer() && // mainAconfigFlag keyguardBottomAreaRefactor() && - KeyguardShadeMigrationNssl.isEnabled && + migrateClocksToBlueprint() && MediaInSceneContainerFlag.isEnabled && // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer ComposeFacade.isComposeAvailable() @@ -63,7 +64,7 @@ object SceneContainerFlag { inline fun getSecondaryFlags(): Sequence<FlagToken> = sequenceOf( FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()), - KeyguardShadeMigrationNssl.token, + FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint()), MediaInSceneContainerFlag.token, // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer ) diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index 2f0fc5127009..ee602e5f1c04 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -23,6 +23,7 @@ import android.content.DialogInterface.BUTTON_NEGATIVE import android.content.DialogInterface.BUTTON_POSITIVE import android.content.Intent import android.content.Intent.EXTRA_PACKAGE_NAME +import android.content.pm.PackageManager import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS import android.hardware.SensorPrivacyManager.EXTRA_SENSOR @@ -31,6 +32,7 @@ import android.os.Bundle import android.os.Handler import android.window.OnBackInvokedDispatcher import androidx.annotation.OpenForTesting +import com.android.internal.camera.flags.Flags import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE @@ -90,14 +92,14 @@ open class SensorUseStartedActivity @Inject constructor( sensor = ALL_SENSORS val callback = IndividualSensorPrivacyController.Callback { _, _ -> if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && - !sensorPrivacyController.isSensorBlocked(CAMERA)) { + !isCameraBlocked(sensorUsePackageName)) { finish() } } sensorPrivacyListener = callback sensorPrivacyController.addCallback(callback) if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && - !sensorPrivacyController.isSensorBlocked(CAMERA)) { + !isCameraBlocked(sensorUsePackageName)) { finish() return } @@ -110,14 +112,22 @@ open class SensorUseStartedActivity @Inject constructor( } val callback = IndividualSensorPrivacyController.Callback { whichSensor: Int, isBlocked: Boolean -> - if (whichSensor == sensor && !isBlocked) { + if (whichSensor != sensor) { + // Ignore a callback; we're not interested in. + } else if ((whichSensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) { + finish() + } else if ((whichSensor == MICROPHONE) && !isBlocked) { finish() } } sensorPrivacyListener = callback sensorPrivacyController.addCallback(callback) - if (!sensorPrivacyController.isSensorBlocked(sensor)) { + if ((sensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) { + finish() + return + } else if ((sensor == MICROPHONE) && + !sensorPrivacyController.isSensorBlocked(MICROPHONE)) { finish() return } @@ -204,6 +214,22 @@ open class SensorUseStartedActivity @Inject constructor( recreate() } + private fun isAutomotive(): Boolean { + return getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + } + + private fun isCameraBlocked(packageName: String): Boolean { + if (Flags.cameraPrivacyAllowlist()) { + if (isAutomotive()) { + return sensorPrivacyController.isCameraPrivacyEnabled(packageName) + } else { + return sensorPrivacyController.isSensorBlocked(CAMERA) + } + } else { + return sensorPrivacyController.isSensorBlocked(CAMERA) + } + } + private fun disableSensorPrivacy() { if (sensor == ALL_SENSORS) { sensorPrivacyController.setSensorBlocked(DIALOG, MICROPHONE, false) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 0e359b7f0eec..9a03393be979 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -71,7 +71,6 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; import android.view.HapticFeedbackConstants; -import android.view.InputDevice; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -137,7 +136,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver; import com.android.systemui.keyguard.shared.ComposeLockscreen; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; @@ -1022,7 +1020,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump instantCollapse(); } else { mView.animate().cancel(); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mView.animate() .alpha(0f) .setStartDelay(0) @@ -1158,7 +1156,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Occluded->Lockscreen collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(), mOccludedToLockscreenTransition, mMainDispatcher); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); collectFlow(mView, @@ -1169,7 +1167,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Lockscreen->Dreaming collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(), mLockscreenToDreamingTransition, mMainDispatcher); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); @@ -1181,7 +1179,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Gone->Dreaming collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(), mGoneToDreamingTransition, mMainDispatcher); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(), setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); } @@ -1192,7 +1190,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Lockscreen->Occluded collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(), mLockscreenToOccludedTransition, mMainDispatcher); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY(), @@ -1200,7 +1198,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } // Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth) - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(), setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); } @@ -1278,7 +1276,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.onDestroy(); } - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { // Need a shared controller until mKeyguardStatusViewController can be removed from // here, due to important state being set in that controller. Rebind in order to pick // up config changes @@ -1334,7 +1332,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mNotificationStackScrollLayoutController.setOverExpansion(0); mNotificationStackScrollLayoutController.setOverScrollAmount(0); } @@ -1355,7 +1353,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } updateClockAppearance(); mQsController.updateQsState(); - if (!KeyguardShadeMigrationNssl.isEnabled() && !FooterViewRefactor.isEnabled()) { + if (!migrateClocksToBlueprint() && !FooterViewRefactor.isEnabled()) { mNotificationStackScrollLayoutController.updateFooter(); } } @@ -1387,7 +1385,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump void reInflateViews() { debugLog("reInflateViews"); // Re-inflate the status view group. - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { KeyguardStatusView keyguardStatusView = mNotificationContainerParent.findViewById(R.id.keyguard_status_view); int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView); @@ -1507,7 +1505,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateMaxDisplayedNotifications(boolean recompute) { - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { return; } @@ -1664,7 +1662,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard), mKeyguardStatusViewController.isClockTopAligned()); mClockPositionAlgorithm.run(mClockPositionResult); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mKeyguardStatusViewController.setLockscreenClockY( mClockPositionAlgorithm.getExpandedPreferredClockY()); } @@ -1678,7 +1676,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockY, mClockPositionResult.clockScale, animateClock); @@ -1754,7 +1752,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updateKeyguardStatusViewAlignment(boolean animate) { boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); ConstraintLayout layout; - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { layout = mKeyguardViewConfigurator.getKeyguardRootView(); } else { layout = mNotificationContainerParent; @@ -1930,7 +1928,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha; mKeyguardStatusViewController.setAlpha(alpha); - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { // TODO (b/296373478) This is for split shade media movement. } else { mKeyguardStatusViewController @@ -2523,7 +2521,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump void requestScrollerTopPaddingUpdate(boolean animate) { float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing, getKeyguardNotificationStaticPadding(), mExpandedFraction); - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { mSharedNotificationContainerInteractor.setTopPosition(padding); } else { mNotificationStackScrollLayoutController.updateTopPadding(padding, animate); @@ -2705,7 +2703,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return; } - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { float alpha = 1f; if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) { @@ -2740,7 +2738,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateKeyguardBottomAreaAlpha() { - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { return; } if (mIsOcclusionTransitionRunning) { @@ -2981,7 +2979,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onScreenTurningOn() { - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mKeyguardStatusViewController.dozeTimeTick(); } } @@ -3233,7 +3231,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump public void dozeTimeTick() { mLockIconViewController.dozeTimeTick(); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mKeyguardStatusViewController.dozeTimeTick(); } if (mInterpolatedDarkAmount > 0) { @@ -4449,7 +4447,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump && statusBarState == KEYGUARD) { // This means we're doing the screen off animation - position the keyguard status // view where it'll be on AOD, so we can animate it in. - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockYFullyDozing, @@ -4569,7 +4567,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setDozing(true /* dozing */, false /* animate */); mStatusBarStateController.setUpcomingState(KEYGUARD); - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { mStatusBarStateController.setState(KEYGUARD); } else { mStatusBarStateListener.onStateChanged(KEYGUARD); @@ -4630,7 +4628,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); // Update Clock Pivot (used by anti-burnin transformations) - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); } @@ -4740,7 +4738,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private Consumer<Float> setTransitionY( NotificationStackScrollLayoutController stackScroller) { return (Float translationY) -> { - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false); stackScroller.setTranslationY(translationY); @@ -4782,7 +4780,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) { + if (migrateClocksToBlueprint() && !mUseExternalTouch) { return false; } @@ -4853,7 +4851,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mCentralSurfaces.userActivity(); } mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation; @@ -4954,7 +4952,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ @Override public boolean onTouchEvent(MotionEvent event) { - if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) { + if (migrateClocksToBlueprint() && !mUseExternalTouch) { return false; } @@ -5066,19 +5064,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return false; } - final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll( - mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe( - mTrackpadGestureFeaturesEnabled, event); - - // On expanding, single mouse click expands the panel instead of dragging. - if (isFullyCollapsed() && (event.isFromSource(InputDevice.SOURCE_MOUSE) - && !isTrackpadTwoOrThreeFingerSwipe)) { - if (event.getAction() == MotionEvent.ACTION_UP) { - expand(true /* animate */); - } - return true; - } - /* * We capture touch events here and update the expand height here in case according to * the users fingers. This also handles multi-touch. @@ -5099,6 +5084,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mIgnoreXTouchSlop = true; } + final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll( + mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe( + mTrackpadGestureFeaturesEnabled, event); + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index aa2d606c5126..99e91c1d332f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,6 +16,7 @@ package com.android.systemui.shade; +import static com.android.systemui.Flags.migrateClocksToBlueprint; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; @@ -48,7 +49,6 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder; @@ -320,7 +320,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mTouchActive = true; mTouchCancelled = false; mDownEvent = ev; - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { mService.userActivity(); } } else if (ev.getActionMasked() == MotionEvent.ACTION_UP @@ -475,7 +475,7 @@ public class NotificationShadeWindowViewController implements Dumpable { && !bouncerShowing && !mStatusBarStateController.isDozing()) { if (mDragDownHelper.isDragDownEnabled()) { - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { // When on lockscreen, if the touch originates at the top of the screen // go directly to QS and not the shade if (mStatusBarStateController.getState() == KEYGUARD @@ -488,7 +488,7 @@ public class NotificationShadeWindowViewController implements Dumpable { // This handles drag down over lockscreen boolean result = mDragDownHelper.onInterceptTouchEvent(ev); - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { if (result) { mLastInterceptWasDragDownHelper = true; if (ev.getAction() == MotionEvent.ACTION_DOWN) { @@ -520,7 +520,7 @@ public class NotificationShadeWindowViewController implements Dumpable { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); mStackScrollLayout.onInterceptTouchEvent(cancellation); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mNotificationPanelViewController.handleExternalInterceptTouch(cancellation); } cancellation.recycle(); @@ -535,7 +535,7 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarKeyguardViewManager.onTouch(ev)) { return true; } - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { // we still want to finish our drag down gesture when locking the screen handled |= mDragDownHelper.onTouchEvent(ev) || handled; @@ -625,7 +625,7 @@ public class NotificationShadeWindowViewController implements Dumpable { } private boolean didNotificationPanelInterceptEvent(MotionEvent ev) { - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need // to also ask NotificationPanelViewController directly, in order to process swipe up // events originating from notifications diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index c0afa32571e7..457b3d7e6217 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -28,10 +28,10 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.lifecycle.lifecycleScope import com.android.systemui.Flags.centralizedStatusBarDimensRefactor +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.fragments.FragmentService -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.plugins.qs.QS @@ -284,7 +284,7 @@ class NotificationsQSContainerController @Inject constructor( } private fun setNotificationsConstraints(constraintSet: ConstraintSet) { - if (KeyguardShadeMigrationNssl.isEnabled) { + if (migrateClocksToBlueprint()) { return } val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java index 25e558ee42dd..e82f2d3cbd30 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java @@ -18,6 +18,8 @@ package com.android.systemui.shade; import static androidx.constraintlayout.core.widgets.Optimizer.OPTIMIZATION_GRAPH; +import static com.android.systemui.Flags.migrateClocksToBlueprint; + import android.app.Fragment; import android.content.Context; import android.content.res.Configuration; @@ -33,7 +35,6 @@ import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.plugins.qs.QS; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.AboveShelfObserver; @@ -189,7 +190,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - if (KeyguardShadeMigrationNssl.isEnabled()) { + if (migrateClocksToBlueprint()) { return super.drawChild(canvas, child, drawingTime); } int layoutIndex = mLayoutDrawingOrder.indexOf(child); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index f3e9c7503626..f86c71b9508c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -21,6 +21,7 @@ import static android.view.WindowInsets.Type.ime; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor; +import static com.android.systemui.Flags.migrateClocksToBlueprint; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS; import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE; @@ -70,7 +71,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -1782,7 +1782,7 @@ public class QuickSettingsController implements Dumpable { // Dragging down on the lockscreen statusbar should prohibit other interactions // immediately, otherwise we'll wait on the touchslop. This is to allow // dragging down to expanded quick settings directly on the lockscreen. - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mPanelView.getParent().requestDisallowInterceptTouchEvent(true); } } @@ -1827,7 +1827,7 @@ public class QuickSettingsController implements Dumpable { && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept( mInitialTouchX, mInitialTouchY, h)) { - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mPanelView.getParent().requestDisallowInterceptTouchEvent(true); } mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index 7cb3be7f159d..6a2a6a417f5a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -241,7 +241,7 @@ constructor( } override fun onStatusBarTouch(event: MotionEvent) { - // The only call to this doesn't happen with KeyguardShadeMigrationNssl enabled + // The only call to this doesn't happen with migrateClocksToBlueprint() enabled throw UnsupportedOperationException() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 14230ba43f59..19fe60a60bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import static android.adaptiveauth.Flags.enableAdaptiveAuth; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; @@ -32,6 +33,7 @@ import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_IS_DISMISSIBLE; +import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ADAPTIVE_AUTH; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE; @@ -454,6 +456,9 @@ public class KeyguardIndicationController { updateLockScreenAlignmentMsg(); updateLockScreenLogoutView(); updateLockScreenPersistentUnlockMsg(); + if (enableAdaptiveAuth()) { + updateLockScreenAdaptiveAuthMsg(userId); + } } private void updateOrganizedOwnedDevice() { @@ -740,6 +745,22 @@ public class KeyguardIndicationController { } } + private void updateLockScreenAdaptiveAuthMsg(int userId) { + final boolean deviceLocked = mKeyguardUpdateMonitor.isDeviceLockedByAdaptiveAuth(userId); + if (deviceLocked) { + mRotateTextViewController.updateIndication( + INDICATION_TYPE_ADAPTIVE_AUTH, + new KeyguardIndication.Builder() + .setMessage(mContext + .getString(R.string.kg_prompt_after_adaptive_auth_lock)) + .setTextColor(mInitialTextColorState) + .build(), + true); + } else { + mRotateTextViewController.hideIndication(INDICATION_TYPE_ADAPTIVE_AUTH); + } + } + private boolean isOrganizationOwnedDevice() { return mDevicePolicyManager.isDeviceManaged() || mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index ef5026538216..2e7110381b91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -15,6 +15,7 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.ExpandHelper import com.android.systemui.Flags.nsslFalsingFix +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.Gefingerpoken import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy import com.android.systemui.classifier.Classifier @@ -23,7 +24,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll import com.android.systemui.plugins.ActivityStarter @@ -890,7 +890,7 @@ class DragDownHelper( isDraggingDown = false isTrackpadReverseScroll = false shadeRepository.setLegacyLockscreenShadeTracking(false) - if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled) { + if (nsslFalsingFix() || migrateClocksToBlueprint()) { return true } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 2a4753def463..9916ef6ff9ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -749,7 +749,7 @@ public class NotificationLockscreenUserManagerImpl implements || isNotifUserRedacted; boolean notificationRequestsRedaction = - ent.getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE; + ent.isNotificationVisibilityPrivate(); boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey()); if (keyguardPrivateNotifications()) { @@ -767,9 +767,7 @@ public class NotificationLockscreenUserManagerImpl implements } NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key); if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { - return entry != null && entry.getRanking().getChannel() != null - && entry.getRanking().getChannel().getLockscreenVisibility() - == Notification.VISIBILITY_PRIVATE; + return entry != null && entry.isChannelVisibilityPrivate(); } else { return entry != null && entry.getRanking().getLockscreenVisibilityOverride() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 8678f0aad181..e111525285e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -998,6 +998,23 @@ public final class NotificationEntry extends ListEntry { return style == null ? "nostyle" : style.getSimpleName(); } + /** + * Return {@code true} if notification's visibility is {@link Notification.VISIBILITY_PRIVATE} + */ + public boolean isNotificationVisibilityPrivate() { + return getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE; + } + + /** + * Return {@code true} if notification's channel lockscreen visibility is + * {@link Notification.VISIBILITY_PRIVATE} + */ + public boolean isChannelVisibilityPrivate() { + return getRanking().getChannel() != null + && getRanking().getChannel().getLockscreenVisibility() + == Notification.VISIBILITY_PRIVATE; + } + /** Information about a suggestion that is being edited. */ public static class EditedSuggestionInfo { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 54b6ad71e734..fb67f7cc0b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -104,7 +104,7 @@ class HeadsUpCoordinator @Inject constructor( /** * Once the pipeline starts running, we can look through posted entries and quickly process - * any that don't have groups, and thus will never gave a group alert edge case. + * any that don't have groups, and thus will never gave a group heads up edge case. */ fun onBeforeTransformGroups(list: List<ListEntry>) { mNow = mSystemClock.currentTimeMillis() @@ -125,7 +125,7 @@ class HeadsUpCoordinator @Inject constructor( /** * Once we have a nearly final shade list (not including what's pruned for inflation reasons), * we know that stability and [NotifPromoter]s have been applied, so we can use the location of - * notifications in this list to determine what kind of group alert behavior should happen. + * notifications in this list to determine what kind of group heads up behavior should happen. */ fun onBeforeFinalizeFilter(list: List<ListEntry>) = mHeadsUpManager.modifyHuns { hunMutator -> // Nothing to do if there are no other adds/updates @@ -140,7 +140,7 @@ class HeadsUpCoordinator @Inject constructor( .groupBy { it.sbn.groupKey } val groupLocationsByKey: Map<String, GroupLocation> by lazy { getGroupLocationsByKey(list) } mLogger.logEvaluatingGroups(postedEntriesByGroup.size) - // For each group, determine which notification(s) for a group should alert. + // For each group, determine which notification(s) for a group should heads up. postedEntriesByGroup.forEach { (groupKey, postedEntries) -> // get and classify the logical members val logicalMembers = logicalMembersByGroup[groupKey] ?: emptyList() @@ -149,7 +149,7 @@ class HeadsUpCoordinator @Inject constructor( // Report the start of this group's evaluation mLogger.logEvaluatingGroup(groupKey, postedEntries.size, logicalMembers.size) - // If there is no logical summary, then there is no alert to transfer + // If there is no logical summary, then there is no heads up to transfer if (logicalSummary == null) { postedEntries.forEach { handlePostedEntry(it, hunMutator, scenario = "logical-summary-missing") @@ -157,43 +157,43 @@ class HeadsUpCoordinator @Inject constructor( return@forEach } - // If summary isn't wanted to be heads up, then there is no alert to transfer + // If summary isn't wanted to be heads up, then there is no heads up to transfer if (!isGoingToShowHunStrict(logicalSummary)) { postedEntries.forEach { - handlePostedEntry(it, hunMutator, scenario = "logical-summary-not-alerting") + handlePostedEntry(it, hunMutator, scenario = "logical-summary-not-heads-up") } return@forEach } - // The group is alerting! Overall goals: - // - Maybe transfer its alert to a child - // - Also let any/all newly alerting children still alert - var childToReceiveParentAlert: NotificationEntry? + // The group is heads up! Overall goals: + // - Maybe transfer its heads up to a child + // - Also let any/all newly heads up children still heads up + var childToReceiveParentHeadsUp: NotificationEntry? var targetType = "undefined" - // If the parent is alerting, always look at the posted notification with the newest + // If the parent is heads up, always look at the posted notification with the newest // 'when', and if it is isolated with GROUP_ALERT_SUMMARY, then it should receive the - // parent's alert. - childToReceiveParentAlert = - findAlertOverride(postedEntries, groupLocationsByKey::getLocation) - if (childToReceiveParentAlert != null) { - targetType = "alertOverride" + // parent's heads up. + childToReceiveParentHeadsUp = + findHeadsUpOverride(postedEntries, groupLocationsByKey::getLocation) + if (childToReceiveParentHeadsUp != null) { + targetType = "headsUpOverride" } - // If the summary is Detached and we have not picked a receiver of the alert, then we - // need to look for the best child to alert in place of the summary. + // If the summary is Detached and we have not picked a receiver of the heads up, then we + // need to look for the best child to heads up in place of the summary. val isSummaryAttached = groupLocationsByKey.contains(logicalSummary.key) - if (!isSummaryAttached && childToReceiveParentAlert == null) { - childToReceiveParentAlert = + if (!isSummaryAttached && childToReceiveParentHeadsUp == null) { + childToReceiveParentHeadsUp = findBestTransferChild(logicalMembers, groupLocationsByKey::getLocation) - if (childToReceiveParentAlert != null) { + if (childToReceiveParentHeadsUp != null) { targetType = "bestChild" } } - // If there is no child to receive the parent alert, then just handle the posted entries - // and return. - if (childToReceiveParentAlert == null) { + // If there is no child to receive the parent heads up, then just handle the posted + // entries and return. + if (childToReceiveParentHeadsUp == null) { postedEntries.forEach { handlePostedEntry(it, hunMutator, scenario = "no-transfer-target") } @@ -203,14 +203,14 @@ class HeadsUpCoordinator @Inject constructor( // At this point we just need to initiate the transfer val summaryUpdate = mPostedEntries[logicalSummary.key] - // Because we now know for certain that some child is going to alert for this summary - // (as we have found a child to transfer the alert to), mark the group as having + // Because we now know for certain that some child is going to heads up for this summary + // (as we have found a child to transfer the heads up to), mark the group as having // interrupted. This will allow us to know in the future that the "should heads up" // state of this group has already been handled, just not via the summary entry itself. logicalSummary.setInterruption() - mLogger.logSummaryMarkedInterrupted(logicalSummary.key, childToReceiveParentAlert.key) + mLogger.logSummaryMarkedInterrupted(logicalSummary.key, childToReceiveParentHeadsUp.key) - // If the summary was not attached, then remove the alert from the detached summary. + // If the summary was not attached, then remove the heads up from the detached summary. // Otherwise we can simply ignore its posted update. if (!isSummaryAttached) { val summaryUpdateForRemoval = summaryUpdate?.also { @@ -221,60 +221,63 @@ class HeadsUpCoordinator @Inject constructor( wasUpdated = false, shouldHeadsUpEver = false, shouldHeadsUpAgain = false, - isAlerting = mHeadsUpManager.isHeadsUpEntry(logicalSummary.key), + isHeadsUpEntry = mHeadsUpManager.isHeadsUpEntry(logicalSummary.key), isBinding = isEntryBinding(logicalSummary), ) - // If we transfer the alert and the summary isn't even attached, that means we - // should ensure the summary is no longer alerting, so we remove it here. + // If we transfer the heads up notification and the summary isn't even attached, + // that means we should ensure the summary is no longer a heads up notification, + // so we remove it here. handlePostedEntry( summaryUpdateForRemoval, hunMutator, - scenario = "detached-summary-remove-alert") + scenario = "detached-summary-remove-heads-up") } else if (summaryUpdate != null) { mLogger.logPostedEntryWillNotEvaluate( summaryUpdate, reason = "attached-summary-transferred") } - // Handle all posted entries -- if the child receiving the parent's alert is in the - // list, then set its flags to ensure it alerts. - var didAlertChildToReceiveParentAlert = false + // Handle all posted entries -- if the child receiving the parent's heads up is in the + // list, then set its flags to ensure it heads up. + var didHeadsUpChildToReceiveParentHeadsUp = false postedEntries.asSequence() .filter { it.key != logicalSummary.key } .forEach { postedEntry -> - if (childToReceiveParentAlert.key == postedEntry.key) { + if (childToReceiveParentHeadsUp.key == postedEntry.key) { // Update the child's posted update so that it postedEntry.shouldHeadsUpEver = true postedEntry.shouldHeadsUpAgain = true handlePostedEntry( postedEntry, hunMutator, - scenario = "child-alert-transfer-target-$targetType") - didAlertChildToReceiveParentAlert = true + scenario = "child-heads-up-transfer-target-$targetType") + didHeadsUpChildToReceiveParentHeadsUp = true } else { handlePostedEntry( postedEntry, hunMutator, - scenario = "child-alert-non-target") + scenario = "child-heads-up-non-target") } } - // If the child receiving the alert was not updated on this tick (which can happen in a - // standard alert transfer scenario), then construct an update so that we can apply it. - if (!didAlertChildToReceiveParentAlert) { + // If the child receiving the heads up notification was not updated on this tick + // (which can happen in a standard heads up transfer scenario), then construct an update + // so that we can apply it. + if (!didHeadsUpChildToReceiveParentHeadsUp) { val posted = PostedEntry( - childToReceiveParentAlert, + childToReceiveParentHeadsUp, wasAdded = false, wasUpdated = false, shouldHeadsUpEver = true, shouldHeadsUpAgain = true, - isAlerting = mHeadsUpManager.isHeadsUpEntry(childToReceiveParentAlert.key), - isBinding = isEntryBinding(childToReceiveParentAlert), + isHeadsUpEntry = + mHeadsUpManager.isHeadsUpEntry(childToReceiveParentHeadsUp.key), + isBinding = isEntryBinding(childToReceiveParentHeadsUp), ) handlePostedEntry( posted, hunMutator, - scenario = "non-posted-child-alert-transfer-target-$targetType") + scenario = "non-posted-child-heads-up-transfer-target-$targetType") } } // After this method runs, all posted entries should have been handled (or skipped). @@ -286,9 +289,9 @@ class HeadsUpCoordinator @Inject constructor( /** * Find the posted child with the newest when, and return it if it is isolated and has - * GROUP_ALERT_SUMMARY so that it can be alerted. + * GROUP_ALERT_SUMMARY so that it can be heads uped. */ - private fun findAlertOverride( + private fun findHeadsUpOverride( postedEntries: List<PostedEntry>, locationLookupByKey: (String) -> GroupLocation, ): NotificationEntry? = postedEntries.asSequence() @@ -344,16 +347,17 @@ class HeadsUpCoordinator @Inject constructor( } } else { if (posted.isHeadsUpAlready) { - // NOTE: This might be because we're alerting (i.e. tracked by HeadsUpManager) OR - // it could be because we're binding, and that will affect the next step. + // NOTE: This might be because we're showing heads up (i.e. tracked by + // HeadsUpManager) OR it could be because we're binding, and that will affect the + // next step. if (posted.shouldHeadsUpEver) { - // If alerting, we need to post an update. Otherwise we're still binding, - // and we can just let that finish. - if (posted.isAlerting) { + // If showing heads up, we need to post an update. Otherwise we're still + // binding, and we can just let that finish. + if (posted.isHeadsUpEntry) { hunMutator.updateNotification(posted.key, posted.shouldHeadsUpAgain) } } else { - if (posted.isAlerting) { + if (posted.isHeadsUpEntry) { // We don't want this to be interrupting anymore, let's remove it hunMutator.removeNotification(posted.key, false /*removeImmediately*/) } else { @@ -408,7 +412,7 @@ class HeadsUpCoordinator @Inject constructor( wasUpdated = false, shouldHeadsUpEver = shouldHeadsUpEver, shouldHeadsUpAgain = true, - isAlerting = false, + isHeadsUpEntry = false, isBinding = false, ) @@ -418,21 +422,21 @@ class HeadsUpCoordinator @Inject constructor( /** * Notification could've updated to be heads up or not heads up. Even if it did update to - * heads up, if the notification specified that it only wants to alert once, don't heads + * heads up, if the notification specified that it only wants to heads up once, don't heads * up again. */ override fun onEntryUpdated(entry: NotificationEntry) { val shouldHeadsUpEver = mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt val shouldHeadsUpAgain = shouldHunAgain(entry) - val isAlerting = mHeadsUpManager.isHeadsUpEntry(entry.key) + val isHeadsUpEntry = mHeadsUpManager.isHeadsUpEntry(entry.key) val isBinding = isEntryBinding(entry) val posted = mPostedEntries.compute(entry.key) { _, value -> value?.also { update -> update.wasUpdated = true update.shouldHeadsUpEver = shouldHeadsUpEver update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain - update.isAlerting = isAlerting + update.isHeadsUpEntry = isHeadsUpEntry update.isBinding = isBinding } ?: PostedEntry( entry, @@ -440,15 +444,15 @@ class HeadsUpCoordinator @Inject constructor( wasUpdated = true, shouldHeadsUpEver = shouldHeadsUpEver, shouldHeadsUpAgain = shouldHeadsUpAgain, - isAlerting = isAlerting, + isHeadsUpEntry = isHeadsUpEntry, isBinding = isBinding, ) } - // Handle cancelling alerts here, rather than in the OnBeforeFinalizeFilter, so that + // Handle cancelling heads up here, rather than in the OnBeforeFinalizeFilter, so that // work can be done before the ShadeListBuilder is run. This prevents re-entrant // behavior between this Coordinator, HeadsUpManager, and VisualStabilityManager. if (posted?.shouldHeadsUpEver == false) { - if (posted.isAlerting) { + if (posted.isHeadsUpEntry) { // We don't want this to be interrupting anymore, let's remove it mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/) } else if (posted.isBinding) { @@ -462,7 +466,7 @@ class HeadsUpCoordinator @Inject constructor( } /** - * Stop alerting HUNs that are removed from the notification collection + * Stop showing as heads up once removed from the notification collection */ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { mPostedEntries.remove(entry.key) @@ -484,7 +488,7 @@ class HeadsUpCoordinator @Inject constructor( /** * Identify notifications whose heads-up state changes when the notification rankings are - * updated, and have those changed notifications alert if necessary. + * updated, and have those changed notifications heads up if necessary. * * This method will occur after any operations in onEntryAdded or onEntryUpdated, so any * handling of ranking changes needs to take into account that we may have just made a @@ -492,7 +496,7 @@ class HeadsUpCoordinator @Inject constructor( */ override fun onRankingApplied() { // Because a ranking update may cause some notifications that are no longer (or were - // never) in mPostedEntries to need to alert, we need to check every notification + // never) in mPostedEntries to need to heads up, we need to check every notification // known to the pipeline. for (entry in mNotifPipeline.allNotifs) { // Only consider entries that are recent enough, since we want to apply a fairly @@ -500,9 +504,9 @@ class HeadsUpCoordinator @Inject constructor( // app-provided notification update. if (!isNewEnoughForRankingUpdate(entry)) continue - // The only entries we consider alerting for here are entries that have never - // interrupted and that now say they should heads up or FSI; if they've alerted in - // the past, we don't want to incorrectly alert a second time if there wasn't an + // The only entries we consider heads up for here are entries that have never + // interrupted and that now say they should heads up or FSI; if they've heads uped in + // the past, we don't want to incorrectly heads up a second time if there wasn't an // explicit notification update. if (entry.hasInterrupted()) continue @@ -561,7 +565,7 @@ class HeadsUpCoordinator @Inject constructor( } /** - * Checks whether an update for a notification warrants an alert for the user. + * Checks whether an update for a notification warrants an heads up for the user. */ private fun shouldHunAgain(entry: NotificationEntry): Boolean { return (!entry.hasInterrupted() || @@ -716,25 +720,25 @@ class HeadsUpCoordinator @Inject constructor( } /** - * Whether the notification is already alerting or binding so that it can imminently alert + * Whether the notification is already heads up or binding so that it can imminently heads up */ private fun isAttemptingToShowHun(entry: ListEntry) = mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry) /** - * Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it - * has been updated so that it should alert this update. This method is permissive because it - * returns `true` even if the update would (in isolation of its group) cause the alert to be - * retracted. This is important for not retracting transferred group alerts. + * Whether the notification is already heads up/binding per [isAttemptingToShowHun] OR if it + * has been updated so that it should heads up this update. This method is permissive because + * it returns `true` even if the update would (in isolation of its group) cause the heads up to + * be retracted. This is important for not retracting transferred group heads ups. */ private fun isGoingToShowHunNoRetract(entry: ListEntry) = mPostedEntries[entry.key]?.calculateShouldBeHeadsUpNoRetract ?: isAttemptingToShowHun(entry) /** * If the notification has been updated, then whether it should HUN in isolation, otherwise - * defers to the already alerting/binding state of [isAttemptingToShowHun]. This method is - * strict because any update which would revoke the alert supersedes the current - * alerting/binding state. + * defers to the already heads up/binding state of [isAttemptingToShowHun]. This method is + * strict because any update which would revoke the heads up supersedes the current + * heads up/binding state. */ private fun isGoingToShowHunStrict(entry: ListEntry) = mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry) @@ -760,12 +764,12 @@ class HeadsUpCoordinator @Inject constructor( var wasUpdated: Boolean, var shouldHeadsUpEver: Boolean, var shouldHeadsUpAgain: Boolean, - var isAlerting: Boolean, + var isHeadsUpEntry: Boolean, var isBinding: Boolean, ) { val key = entry.key val isHeadsUpAlready: Boolean - get() = isAlerting || isBinding + get() = isHeadsUpEntry || isBinding val calculateShouldBeHeadsUpStrict: Boolean get() = shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain || isHeadsUpAlready) val calculateShouldBeHeadsUpNoRetract: Boolean @@ -781,7 +785,7 @@ private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation = /** * Invokes the given block with a [HunMutator] that defers all HUN removals. This ensures that the * HeadsUpManager is notified of additions before removals, which prevents a glitch where the - * HeadsUpManager temporarily believes that nothing is alerting, causing bad re-entrant behavior. + * HeadsUpManager temporarily believes that nothing is heads up, causing bad re-entrant behavior. */ private fun <R> HeadsUpManager.modifyHuns(block: (HunMutator) -> R): R { val mutator = HunMutatorImpl(this) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 20fae88b6f33..c90aceef6934 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -263,8 +263,8 @@ public class AmbientState implements Dumpable { return mStackHeight; } - /** Tracks the state from AlertingNotificationManager#hasNotifications() */ - private boolean mHasAlertEntries; + /** Tracks the state from HeadsUpManager#hasNotifications() */ + private boolean mHasHeadsUpEntries; @Inject public AmbientState( @@ -563,7 +563,7 @@ public class AmbientState implements Dumpable { } public boolean hasPulsingNotifications() { - return mPulsing && mHasAlertEntries; + return mPulsing && mHasHeadsUpEntries; } public void setPulsing(boolean hasPulsing) { @@ -716,8 +716,8 @@ public class AmbientState implements Dumpable { return mAppearFraction; } - public void setHasAlertEntries(boolean hasAlertEntries) { - mHasAlertEntries = hasAlertEntries; + public void setHasHeadsUpEntries(boolean hasHeadsUpEntries) { + mHasHeadsUpEntries = hasHeadsUpEntries; } public void setStackTopMargin(int stackTopMargin) { 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 aa9d3b23e47b..933a78009e29 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 @@ -5721,7 +5721,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable void setNumHeadsUp(long numHeadsUp) { mNumHeadsUp = numHeadsUp; - mAmbientState.setHasAlertEntries(numHeadsUp > 0); + mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0); } public boolean getIsExpanded() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 830b8c1ae63e..78e6a795604a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -23,6 +23,7 @@ import static com.android.app.animation.Interpolators.STANDARD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.server.notification.Flags.screenshareNotificationHiding; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; +import static com.android.systemui.Flags.migrateClocksToBlueprint; import static com.android.systemui.Flags.nsslFalsingFix; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener; @@ -71,7 +72,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.media.controls.ui.KeyguardMediaController; @@ -2078,7 +2078,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } boolean horizontalSwipeWantsIt = false; boolean scrollerWantsIt = false; - if (nsslFalsingFix() || KeyguardShadeMigrationNssl.isEnabled()) { + if (nsslFalsingFix() || migrateClocksToBlueprint()) { // Reverse the order relative to the else statement. onScrollTouch will reset on an // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes. if (mLongPressedView == null && !mView.isBeingDragged() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index a135802f095e..b0fefdd36012 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -27,6 +27,7 @@ import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import static com.android.systemui.Flags.lightRevealMigration; +import static com.android.systemui.Flags.migrateClocksToBlueprint; import static com.android.systemui.Flags.newAodTransition; import static com.android.systemui.Flags.predictiveBackSysui; import static com.android.systemui.Flags.truncatedStatusBarIconsFix; @@ -143,7 +144,6 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; @@ -1468,7 +1468,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return (v, event) -> { mAutoHideController.checkUserAutoHide(event); mRemoteInputManager.checkRemoteInputOutside(event); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mShadeController.onStatusBarTouch(event); } return getNotificationShadeWindowView().onTouchEvent(event); @@ -2505,7 +2505,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { mDeviceInteractive = true; - boolean isFlaggedOff = newAodTransition() && KeyguardShadeMigrationNssl.isEnabled(); + boolean isFlaggedOff = newAodTransition() && migrateClocksToBlueprint(); if (!isFlaggedOff && shouldAnimateDozeWakeup()) { // If this is false, the power button must be physically pressed in order to // trigger fingerprint authentication. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java index e79f3ff19031..94f62e075a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.Flags.newAodTransition; +import static com.android.systemui.Flags.migrateClocksToBlueprint; import android.content.Context; import android.content.res.Resources; @@ -40,7 +41,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -545,7 +545,7 @@ public class LegacyNotificationIconAreaControllerImpl implements return; } if (mScreenOffAnimationController.shouldAnimateAodIcons()) { - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mAodIcons.setTranslationY(-mAodIconAppearTranslation); } mAodIcons.setAlpha(0); @@ -557,14 +557,14 @@ public class LegacyNotificationIconAreaControllerImpl implements .start(); } else { mAodIcons.setAlpha(1.0f); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mAodIcons.setTranslationY(0); } } } private void animateInAodIconTranslation() { - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mAodIcons.animate() .setInterpolator(Interpolators.DECELERATE_QUINT) .translationY(0) @@ -667,7 +667,7 @@ public class LegacyNotificationIconAreaControllerImpl implements } } else { mAodIcons.setAlpha(1.0f); - if (!KeyguardShadeMigrationNssl.isEnabled()) { + if (!migrateClocksToBlueprint()) { mAodIcons.setTranslationY(0); } mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 8ac3b4a75141..d10ca3d31de2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -38,9 +38,9 @@ import android.widget.LinearLayout; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.Dependency; import com.android.systemui.Gefingerpoken; -import com.android.systemui.res.R; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; @@ -67,6 +67,8 @@ public class PhoneStatusBarView extends FrameLayout { private int mStatusBarHeight; @Nullable private Gefingerpoken mTouchEventHandler; + private int mDensity; + private float mFontScale; /** * Draw this many pixels into the left/right side of the cutout to optimally use the space @@ -167,13 +169,23 @@ public class PhoneStatusBarView extends FrameLayout { mDisplayCutout = getRootWindowInsets().getDisplayCutout(); } - final Rect newSize = mContext.getResources().getConfiguration().windowConfiguration - .getMaxBounds(); + Configuration newConfiguration = mContext.getResources().getConfiguration(); + final Rect newSize = newConfiguration.windowConfiguration.getMaxBounds(); if (!Objects.equals(newSize, mDisplaySize)) { changed = true; mDisplaySize = newSize; } + int density = newConfiguration.densityDpi; + if (density != mDensity) { + changed = true; + mDensity = density; + } + float fontScale = newConfiguration.fontScale; + if (fontScale != mFontScale) { + changed = true; + mFontScale = fontScale; + } return changed; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index f9702dd12535..a39bfe00be28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -81,6 +81,7 @@ private constructor( statusContainer.setOnHoverListener( statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer) ) + statusContainer.setOnClickListener { shadeViewController.expand(/* animate= */true) } progressProvider?.setReadyToHandleTransition(true) configurationController.addCallback(configurationListener) 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 665a5714e277..223eaf74e2e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -17,10 +17,10 @@ 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.migrateClocksToBlueprint import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealScrim @@ -286,7 +286,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // up, with unpredictable consequences. if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) && shouldAnimateInKeyguard) { - if (!KeyguardShadeMigrationNssl.isEnabled) { + if (!migrateClocksToBlueprint()) { // Tracking this state should no longer be relevant, as the isInteractive // check covers it aodUiAnimationPlaying = true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt index a7352be8d80a..420701f026d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt @@ -61,16 +61,16 @@ interface HeadsUpManager : Dumpable { fun getTouchableRegion(): Region? /** - * Whether or not there are any active alerting notifications. + * Whether or not there are any entries managed by HeadsUpManager. * - * @return true if there is an alert, false otherwise + * @return true if there is a heads up entry, false otherwise */ fun hasNotifications(): Boolean = false /** Returns whether there are any pinned Heads Up Notifications or not. */ fun hasPinnedHeadsUp(): Boolean - /** Returns whether or not the given notification is alerting and managed by this manager. */ + /** Returns whether or not the given notification is managed by this manager. */ fun isHeadsUpEntry(key: String): Boolean fun isHeadsUpGoingAway(): Boolean diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java index eb08f37503c6..eba3162febe5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.policy; +import android.annotation.FlaggedApi; import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.hardware.SensorPrivacyManager.Sources.Source; +import com.android.internal.camera.flags.Flags; + public interface IndividualSensorPrivacyController extends CallbackController<IndividualSensorPrivacyController.Callback> { void init(); @@ -42,6 +45,12 @@ public interface IndividualSensorPrivacyController extends */ boolean requiresAuthentication(); + /** + * @return whether camera privacy is enabled for the package. + */ + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + boolean isCameraPrivacyEnabled(String packageName); + interface Callback { void onSensorBlockedChanged(@Sensor int sensor, boolean blocked); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 87dfc9962675..58b82f166623 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -19,6 +19,9 @@ package com.android.systemui.statusbar.policy; import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; +import android.Manifest; +import android.annotation.FlaggedApi; +import android.annotation.RequiresPermission; import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.hardware.SensorPrivacyManager.Sources.Source; @@ -28,6 +31,8 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; +import com.android.internal.camera.flags.Flags; + import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { @@ -102,6 +107,13 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr } @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public boolean isCameraPrivacyEnabled(String packageName) { + return mSensorPrivacyManager.isCameraPrivacyEnabled(packageName); + } + + @Override public void addCallback(@NonNull Callback listener) { mCallbacks.add(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java index 2b0a92c6ecd7..6956a7d8a8e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java @@ -221,10 +221,15 @@ public class SensitiveNotificationProtectionControllerImpl // Exempt foreground service notifications from protection in effort to keep screen share // stop actions easily accessible StatusBarNotification sbn = entry.getSbn(); - if (sbn.getNotification().isFgsOrUij()) { - return !sbn.getPackageName().equals(projection.getPackageName()); + if (sbn.getNotification().isFgsOrUij() + && sbn.getPackageName().equals(projection.getPackageName())) { + return false; } - return true; + // Only protect/redact notifications if the developer has not explicitly set notification + // visibility as public and users has not adjusted default channel visibility to private + boolean notificationRequestsRedaction = entry.isNotificationVisibilityPrivate(); + boolean userForcesRedaction = entry.isChannelVisibilityPrivate(); + return notificationRequestsRedaction || userForcesRedaction; } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt index ab76d450eb0a..9f99e9778ef2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt @@ -24,6 +24,9 @@ import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory +import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactoryImpl +import dagger.Binds import dagger.Module import dagger.Provides import kotlin.coroutines.CoroutineContext @@ -32,6 +35,11 @@ import kotlinx.coroutines.CoroutineScope @Module interface MediaDevicesModule { + @Binds + fun bindLocalMediaRepositoryFactory( + impl: LocalMediaRepositoryFactoryImpl + ): LocalMediaRepositoryFactory + companion object { @Provides diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt index 0a1ee249d6fb..1f52260bb20d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt @@ -26,7 +26,12 @@ import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope -class LocalMediaRepositoryFactory +interface LocalMediaRepositoryFactory { + + fun create(packageName: String?): LocalMediaRepository +} + +class LocalMediaRepositoryFactoryImpl @Inject constructor( private val intentsReceiver: AudioManagerIntentsReceiver, @@ -34,9 +39,9 @@ constructor( private val localMediaManagerFactory: LocalMediaManagerFactory, @Application private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, -) { +) : LocalMediaRepositoryFactory { - fun create(packageName: String?): LocalMediaRepository = + override fun create(packageName: String?): LocalMediaRepository = LocalMediaRepositoryImpl( intentsReceiver, localMediaManagerFactory.create(packageName), diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt new file mode 100644 index 000000000000..020ec64c0491 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt @@ -0,0 +1,43 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.domain + +import com.android.settingslib.volume.domain.interactor.AudioModeInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +/** Determines if the Media Output Volume Panel component is available. */ +@VolumePanelScope +class MediaOutputAvailabilityCriteria +@Inject +constructor( + private val mediaOutputInteractor: MediaOutputInteractor, + private val audioModeInteractor: AudioModeInteractor, +) : ComponentAvailabilityCriteria { + + override fun isAvailable(): Flow<Boolean> { + return combine(mediaOutputInteractor.mediaDevices, audioModeInteractor.isOngoingCall) { + devices, + isOngoingCall -> + !isOngoingCall && devices.isNotEmpty() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt new file mode 100644 index 000000000000..170b32c1d0ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt @@ -0,0 +1,75 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.domain.interactor + +import android.content.Intent +import android.provider.Settings +import com.android.internal.jank.InteractionJankMonitor +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable +import com.android.systemui.media.dialog.MediaOutputDialogFactory +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession +import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import javax.inject.Inject + +/** User actions interactor for Media Output Volume Panel component. */ +@VolumePanelScope +class MediaOutputActionsInteractor +@Inject +constructor( + private val mediaOutputDialogFactory: MediaOutputDialogFactory, + private val activityStarter: ActivityStarter, +) { + + fun onDeviceClick(expandable: Expandable) { + activityStarter.startActivity( + Intent(Settings.ACTION_BLUETOOTH_SETTINGS), + true, + expandable.activityTransitionController(), + ) + } + + fun onBarClick(session: MediaDeviceSession, expandable: Expandable) { + when (session) { + is MediaDeviceSession.Active -> { + mediaOutputDialogFactory.createWithController( + session.packageName, + false, + expandable.dialogController() + ) + } + is MediaDeviceSession.Inactive -> { + mediaOutputDialogFactory.createDialogForSystemRouting(expandable.dialogController()) + } + else -> { + /* do nothing */ + } + } + } + + private fun Expandable.dialogController(): DialogTransitionAnimator.Controller? { + return dialogTransitionController( + cuj = + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + MediaOutputDialogFactory.INTERACTION_JANK_TAG + ) + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index 6c456f963f03..24cc29d8e1f9 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -17,10 +17,14 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor import android.content.pm.PackageManager +import android.media.session.MediaController +import android.os.Handler import android.util.Log import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.repository.LocalMediaRepository +import com.android.settingslib.volume.data.repository.MediaControllerChange import com.android.settingslib.volume.data.repository.MediaControllerRepository +import com.android.settingslib.volume.data.repository.stateChanges import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession @@ -30,14 +34,21 @@ import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext +/** Provides observable models about the current media session state. */ @OptIn(ExperimentalCoroutinesApi::class) @VolumePanelScope class MediaOutputInteractor @@ -47,32 +58,44 @@ constructor( private val packageManager: PackageManager, @VolumePanelScope private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, + @Background private val backgroundHandler: Handler, mediaControllerRepository: MediaControllerRepository ) { - val mediaDeviceSession: Flow<MediaDeviceSession> = - mediaControllerRepository.activeMediaController.mapNotNull { mediaController -> - if (mediaController == null) { - MediaDeviceSession.Inactive - } else { + /** Current [MediaDeviceSession]. Emits when the session playback changes. */ + val mediaDeviceSession: StateFlow<MediaDeviceSession> = + mediaControllerRepository.activeLocalMediaController + .flatMapLatest { it?.mediaDeviceSession() ?: flowOf(MediaDeviceSession.Inactive) } + .flowOn(backgroundCoroutineContext) + .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSession.Inactive) + + private fun MediaController.mediaDeviceSession(): Flow<MediaDeviceSession> { + return stateChanges(backgroundHandler) + .onStart { emit(MediaControllerChange.PlaybackStateChanged(playbackState)) } + .filterIsInstance<MediaControllerChange.PlaybackStateChanged>() + .map { MediaDeviceSession.Active( - appLabel = getApplicationLabel(mediaController.packageName) - ?: return@mapNotNull null, - packageName = mediaController.packageName, - sessionToken = mediaController.sessionToken, + appLabel = getApplicationLabel(packageName) + ?: return@map MediaDeviceSession.Inactive, + packageName = packageName, + sessionToken = sessionToken, + playbackState = playbackState, ) } - } - private val localMediaRepository: Flow<LocalMediaRepository> = + } + + private val localMediaRepository: SharedFlow<LocalMediaRepository> = mediaDeviceSession .map { (it as? MediaDeviceSession.Active)?.packageName } .distinctUntilChanged() .map { localMediaRepositoryFactory.create(it) } - .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 1) + .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1) + /** Currently connected [MediaDevice]. */ val currentConnectedDevice: Flow<MediaDevice?> = localMediaRepository.flatMapLatest { it.currentConnectedDevice } + /** A list of available [MediaDevice]s. */ val mediaDevices: Flow<Collection<MediaDevice>> = localMediaRepository.flatMapLatest { it.mediaDevices } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt index f250308802b2..71df8e53b5e2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.model import android.media.session.MediaSession +import android.media.session.PlaybackState /** Represents media playing on the connected device. */ sealed interface MediaDeviceSession { @@ -26,6 +27,7 @@ sealed interface MediaDeviceSession { val appLabel: CharSequence, val packageName: String, val sessionToken: MediaSession.Token, + val playbackState: PlaybackState?, ) : MediaDeviceSession /** Media is not playing. */ diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt new file mode 100644 index 000000000000..8ba672d2a15e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt @@ -0,0 +1,26 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.ui.viewmodel + +/** + * Models part of the Media Session Volume Panel component that displays connected device + * information. + */ +data class ConnectedDeviceViewModel( + val label: CharSequence, + val deviceName: CharSequence?, +) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt new file mode 100644 index 000000000000..e0718ace2c30 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt @@ -0,0 +1,37 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.ui.viewmodel + +import com.android.systemui.common.shared.model.Color +import com.android.systemui.common.shared.model.Icon + +/** Models Media Session Volume Panel component connected device icon. */ +sealed interface DeviceIconViewModel { + + val icon: Icon + val backgroundColor: Color + + class IsPlaying( + override val icon: Icon, + override val backgroundColor: Color, + ) : DeviceIconViewModel + + class IsNotPlaying( + override val icon: Icon, + override val backgroundColor: Color, + ) : DeviceIconViewModel +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt new file mode 100644 index 000000000000..d14899294526 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt @@ -0,0 +1,117 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.ui.viewmodel + +import android.content.Context +import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.Color +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.res.R +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession +import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn + +/** Models the UI of the Media Output Volume Panel component. */ +@VolumePanelScope +class MediaOutputViewModel +@Inject +constructor( + private val context: Context, + @VolumePanelScope private val coroutineScope: CoroutineScope, + private val volumePanelViewModel: VolumePanelViewModel, + private val actionsInteractor: MediaOutputActionsInteractor, + interactor: MediaOutputInteractor, +) { + + private val mediaDeviceSession: StateFlow<MediaDeviceSession> = + interactor.mediaDeviceSession.stateIn( + coroutineScope, + SharingStarted.Eagerly, + MediaDeviceSession.Unknown, + ) + + val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> = + combine(mediaDeviceSession, interactor.currentConnectedDevice) { + mediaDeviceSession, + currentConnectedDevice -> + ConnectedDeviceViewModel( + if (mediaDeviceSession.isPlaying()) { + context.getString( + R.string.media_output_label_title, + (mediaDeviceSession as MediaDeviceSession.Active).appLabel + ) + } else { + context.getString(R.string.media_output_title_without_playing) + }, + currentConnectedDevice?.name, + ) + } + .stateIn( + coroutineScope, + SharingStarted.Eagerly, + null, + ) + + val deviceIconViewModel: StateFlow<DeviceIconViewModel?> = + combine(mediaDeviceSession, interactor.currentConnectedDevice) { + mediaDeviceSession, + currentConnectedDevice -> + if (mediaDeviceSession.isPlaying()) { + val icon = + currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) } + ?: Icon.Resource( + com.android.internal.R.drawable.ic_bt_headphones_a2dp, + null + ) + DeviceIconViewModel.IsPlaying( + icon, + Color.Attribute(com.android.internal.R.attr.materialColorSecondary), + ) + } else { + DeviceIconViewModel.IsNotPlaying( + Icon.Resource(R.drawable.ic_media_home_devices, null), + Color.Attribute(com.android.internal.R.attr.materialColorSurface), + ) + } + } + .stateIn( + coroutineScope, + SharingStarted.Eagerly, + null, + ) + + private fun MediaDeviceSession.isPlaying(): Boolean = + this is MediaDeviceSession.Active && playbackState?.isActive == true + + fun onDeviceClick(expandable: Expandable) { + actionsInteractor.onDeviceClick(expandable) + volumePanelViewModel.dismissPanel() + } + + fun onBarClick(expandable: Expandable) { + actionsInteractor.onBarClick(mediaDeviceSession.value, expandable) + volumePanelViewModel.dismissPanel() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt index 842c3234fe26..6c742ba7e5f9 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt @@ -20,6 +20,7 @@ import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey object VolumePanelComponents { + const val MEDIA_OUTPUT: VolumePanelComponentKey = "media_output" const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar" const val CAPTIONING: VolumePanelComponentKey = "captioning" } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt index 841daf85ebcc..afd3f6170d3d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt @@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.dagger import com.android.systemui.volume.panel.component.bottombar.BottomBarModule import com.android.systemui.volume.panel.component.captioning.CaptioningModule +import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import com.android.systemui.volume.panel.domain.DomainModule @@ -46,6 +47,7 @@ import kotlinx.coroutines.CoroutineScope // Components modules BottomBarModule::class, CaptioningModule::class, + MediaOutputModule::class, ] ) interface VolumePanelComponent { diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt index defa92def893..55d8de5aeb95 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt @@ -51,6 +51,7 @@ interface DomainModule { fun provideEnabledComponents(): Collection<VolumePanelComponentKey> { return setOf( VolumePanelComponents.CAPTIONING, + VolumePanelComponents.MEDIA_OUTPUT, VolumePanelComponents.BOTTOM_BAR, ) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt index a3f052d88b7d..867df4a87dd5 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt @@ -37,7 +37,11 @@ interface UiModule { @Provides @VolumePanelScope @HeaderComponents - fun provideHeaderComponents(): Collection<VolumePanelComponentKey> = setOf() + fun provideHeaderComponents(): Collection<VolumePanelComponentKey> { + return setOf( + VolumePanelComponents.MEDIA_OUTPUT, + ) + } @Provides @VolumePanelScope diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt index 1b2265b9891e..53e1b8b5bb70 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt @@ -20,8 +20,8 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels -import androidx.core.view.WindowCompat import com.android.systemui.compose.ComposeFacade +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel import javax.inject.Inject @@ -32,6 +32,7 @@ class VolumePanelActivity constructor( private val volumePanelViewModelFactory: Provider<VolumePanelViewModel.Factory>, private val volumePanelFlag: VolumePanelFlag, + private val configurationController: ConfigurationController, ) : ComponentActivity() { private val viewModel: VolumePanelViewModel by @@ -43,7 +44,11 @@ constructor( volumePanelFlag.assertNewVolumePanel() - WindowCompat.setDecorFitsSystemWindows(window, false) ComposeFacade.setVolumePanelActivityContent(this, viewModel) { finish() } } + + override fun onContentChanged() { + super.onContentChanged() + configurationController.onConfigurationChanged(resources.configuration) + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt index f67db965e28a..7f33a6bb70f9 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelState.kt @@ -22,10 +22,13 @@ import android.content.res.Configuration.Orientation /** * State of the Volume Panel itself. * - * @property orientation is current Volume Panel orientation. + * @property orientation is current Volume Panel orientation + * @property isWideScreen is true when Volume Panel should use wide-screen layout and false the + * otherwise */ data class VolumePanelState( @Orientation val orientation: Int, + val isWideScreen: Boolean, val isVisible: Boolean, ) { init { diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt index d87a79ed90f4..3c5b75cfb349 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt @@ -21,6 +21,7 @@ import android.content.res.Resources import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged import com.android.systemui.volume.panel.dagger.VolumePanelComponent @@ -72,7 +73,11 @@ class VolumePanelViewModel( .distinctUntilChanged(), mutablePanelVisibility, ) { configuration, isVisible -> - VolumePanelState(orientation = configuration.orientation, isVisible = isVisible) + VolumePanelState( + orientation = configuration.orientation, + isVisible = isVisible, + isWideScreen = !resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog), + ) } .stateIn( scope, @@ -80,6 +85,7 @@ class VolumePanelViewModel( VolumePanelState( orientation = resources.configuration.orientation, isVisible = mutablePanelVisibility.value, + isWideScreen = !resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog) ), ) val componentsLayout: Flow<ComponentsLayout> = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index cd19259091ab..30269664a559 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -260,7 +260,7 @@ class ClockEventControllerTest : SysuiTestCase() { @Test fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() verify(keyguardUpdateMonitor).registerCallback(capture(captor)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index e8d86dddcda5..9d81b960336f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -35,6 +35,7 @@ import android.view.View; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.plugins.clocks.ClockFaceConfig; import com.android.systemui.plugins.clocks.ClockTickRate; import com.android.systemui.shared.clocks.ClockRegistry; @@ -50,6 +51,8 @@ import org.mockito.verification.VerificationMode; public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchControllerBaseTest { @Test public void testInit_viewAlreadyAttached() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + mController.init(); verifyAttachment(times(1)); @@ -57,6 +60,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testInit_viewNotYetAttached() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); @@ -73,12 +78,16 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testInitSubControllers() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + mController.init(); verify(mKeyguardSliceViewController).init(); } @Test public void testInit_viewDetached() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); mController.init(); @@ -92,6 +101,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testPluginPassesStatusBarState() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); @@ -105,6 +116,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSmartspaceEnabledRemovesKeyguardStatusArea() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mSmartspaceController.isEnabled()).thenReturn(true); mController.init(); @@ -113,6 +126,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void onLocaleListChangedRebuildsSmartspaceView() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mSmartspaceController.isEnabled()).thenReturn(true); mController.init(); @@ -123,6 +138,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mSmartspaceController.isEnabled()).thenReturn(true); when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true); mController.init(); @@ -136,6 +153,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSmartspaceDisabledShowsKeyguardStatusArea() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mSmartspaceController.isEnabled()).thenReturn(false); mController.init(); @@ -144,6 +163,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testRefresh() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + mController.refresh(); verify(mSmartspaceController).requestSmartspaceUpdate(); @@ -151,6 +172,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testChangeToDoubleLineClockSetsSmallClock() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mSecureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1, UserHandle.USER_CURRENT)) .thenReturn(0); @@ -174,11 +197,15 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClock_ForwardsToClock() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + assertEquals(mClockController, mController.getClock()); } @Test public void testGetLargeClockBottom_returnsExpectedValue() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mLargeClockFrame.getVisibility()).thenReturn(View.VISIBLE); when(mLargeClockFrame.getHeight()).thenReturn(100); when(mSmallClockFrame.getHeight()).thenReturn(50); @@ -191,6 +218,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetSmallLargeClockBottom_returnsExpectedValue() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mLargeClockFrame.getVisibility()).thenReturn(View.GONE); when(mLargeClockFrame.getHeight()).thenReturn(100); when(mSmallClockFrame.getHeight()).thenReturn(50); @@ -203,12 +232,16 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClockBottom_nullClock_returnsZero() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mClockEventController.getClock()).thenReturn(null); assertEquals(0, mController.getClockBottom(10)); } @Test public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mSmartspaceController.isWeatherEnabled()).thenReturn(true); ArgumentCaptor<ContentObserver> observerCaptor = ArgumentCaptor.forClass(ContentObserver.class); @@ -227,6 +260,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); when(mSmartspaceController.isEnabled()).thenReturn(true); @@ -249,11 +284,15 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClock_nullClock_returnsNull() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + when(mClockEventController.getClock()).thenReturn(null); assertNull(mController.getClock()); } private void verifyAttachment(VerificationMode times) { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + verify(mClockRegistry, times).registerClockChangeListener( any(ClockRegistry.ClockChangeListener.class)); verify(mClockEventController, times).registerListeners(mView); @@ -261,6 +300,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSplitShadeEnabledSetToSmartspaceController() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + mController.setSplitShadeEnabled(true); verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true); verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false); @@ -268,6 +309,8 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSplitShadeDisabledSetToSmartspaceController() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + mController.setSplitShadeEnabled(false); verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false); verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 4508aea81176..b4a9d40a6caf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -40,6 +40,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextView; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.clocks.ClockController; import com.android.systemui.plugins.clocks.ClockFaceController; @@ -79,6 +80,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Before public void setUp() { + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); + MockitoAnnotations.initMocks(this); when(mMockKeyguardSliceView.getContext()).thenReturn(mContext); when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index be06cc5d3d1d..538daee52377 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -1500,7 +1500,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mHandler).postDelayed(mKeyguardUpdateMonitor.mFpCancelNotReceived, DEFAULT_CANCEL_SIGNAL_TIMEOUT); - mKeyguardUpdateMonitor.onFingerprintAuthenticated(0, true); mTestableLooper.processAllMessages(); @@ -2016,6 +2015,34 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void authenticateFingerprint_onFaceLockout_detectFingerprint() throws RemoteException { + // GIVEN fingerprintAuthenticate + mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); + mTestableLooper.processAllMessages(); + verifyFingerprintAuthenticateCall(); + verifyFingerprintDetectNeverCalled(); + clearInvocations(mFingerprintManager); + + // WHEN class 3 face is locked out + when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true); + when(mFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()).thenReturn(true); + setupFingerprintAuth(/* isClass3 */ true); + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN face (class 3) is locked out + faceAuthLockOut(); + mTestableLooper.processAllMessages(); + + // THEN unlocking with fingerprint is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)); + + // THEN fingerprint detect gets called + verifyFingerprintDetectCall(); + } + + @Test public void testFingerprintSensorProperties() throws RemoteException { mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered( new ArrayList<>()); 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 43f7c60721ee..2c1a87d86be9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -20,6 +20,8 @@ import android.content.pm.PackageManager import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT +import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.biometrics.PromptInfo import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorPropertiesInternal @@ -38,11 +40,11 @@ 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.res.R +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 import com.android.systemui.biometrics.data.repository.FakePromptRepository -import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor @@ -53,6 +55,7 @@ import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.events.ANIMATING_OUT import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -145,6 +148,8 @@ open class AuthContainerViewTest : SysuiTestCase() { @Before fun setup() { + mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) displayRepository = FakeDisplayRepository() displayStateInteractor = @@ -394,6 +399,19 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test + fun testShowBiometricUIWhenCustomBpEnabledAndNoSensors() { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + val container = initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) + waitForIdleSync() + + assertThat(customBiometricPrompt()).isTrue() + assertThat(container.hasBiometricPrompt()).isTrue() + assertThat(container.hasCredentialView()).isFalse() + } + + @Test fun testCredentialViewUsesEffectiveUserId() { whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200) whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))).thenReturn( 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 2732047b4eba..0957748c9938 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT; import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; @@ -650,6 +651,25 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test + public void testBouncerPrompt_deviceLockedByAdaptiveAuth() { + // GIVEN no trust agents enabled and biometrics aren't enrolled + when(mUpdateMonitor.isTrustUsuallyManaged(anyInt())).thenReturn(false); + when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(false); + + // WHEN the strong auth reason is SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST + KeyguardUpdateMonitor.StrongAuthTracker strongAuthTracker = + mock(KeyguardUpdateMonitor.StrongAuthTracker.class); + when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(strongAuthTracker); + when(strongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true); + when(strongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( + SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST); + + // THEN the bouncer prompt reason should return PROMPT_REASON_ADAPTIVE_AUTH_REQUEST + assertEquals(KeyguardSecurityView.PROMPT_REASON_ADAPTIVE_AUTH_REQUEST, + mViewMediator.mViewMediatorCallback.getBouncerPromptReason()); + } + + @Test public void testBouncerPrompt_deviceRestartedDueToMainlineUpdate() { // GIVEN biometrics enrolled when(mUpdateMonitor.isUnlockingWithBiometricsPossible(anyInt())).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index ad86ee9f07d2..9c7f254a7b85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSec import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSliceViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.mockito.whenever @@ -71,6 +72,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection @Mock private lateinit var clockSection: ClockSection @Mock private lateinit var smartspaceSection: SmartspaceSection + @Mock private lateinit var keyguardSliceViewSection: KeyguardSliceViewSection @Mock private lateinit var udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection @Before @@ -92,6 +94,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { communalTutorialIndicatorSection, clockSection, smartspaceSection, + keyguardSliceViewSection, udfpsAccessibilityOverlaySection, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt index deb3a83fcbee..8eccde7d6cb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt @@ -88,6 +88,8 @@ class SmartspaceSectionTest : SysuiTestCase() { whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay) .thenReturn(hasCustomWeatherDataDisplay) whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered) + whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true) + constraintSet = ConstraintSet() } @@ -103,7 +105,6 @@ class SmartspaceSectionTest : SysuiTestCase() { @Test fun testAddViews_smartspaceEnabled_dateWeatherDecoupled() { - whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true) whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true) underTest.addViews(constraintLayout) assert(smartspaceView.parent == constraintLayout) @@ -113,7 +114,6 @@ class SmartspaceSectionTest : SysuiTestCase() { @Test fun testAddViews_smartspaceEnabled_notDateWeatherDecoupled() { - whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true) whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(false) underTest.addViews(constraintLayout) assert(smartspaceView.parent == constraintLayout) @@ -123,7 +123,6 @@ class SmartspaceSectionTest : SysuiTestCase() { @Test fun testConstraintsWhenNotHasCustomWeatherDataDisplay() { - whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true) whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true) hasCustomWeatherDataDisplay.value = false underTest.addViews(constraintLayout) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index 59965022d7cc..c896486339b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -40,6 +40,7 @@ import android.media.MediaMetadata import android.media.session.MediaSession import android.media.session.PlaybackState import android.os.Bundle +import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.testing.AndroidTestingRunner @@ -61,6 +62,7 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.widget.CachingIconView import com.android.systemui.ActivityIntentHelper +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.bluetooth.BroadcastDialogController import com.android.systemui.broadcast.BroadcastSender @@ -88,6 +90,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView import com.android.systemui.surfaceeffects.ripple.MultiRippleView import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView @@ -190,6 +193,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var dismissText: TextView private lateinit var multiRippleView: MultiRippleView private lateinit var turbulenceNoiseView: TurbulenceNoiseView + private lateinit var loadingEffectView: LoadingEffectView private lateinit var session: MediaSession private lateinit var device: MediaDeviceData @@ -402,6 +406,7 @@ public class MediaControlPanelTest : SysuiTestCase() { multiRippleView = MultiRippleView(context, null) turbulenceNoiseView = TurbulenceNoiseView(context, null) + loadingEffectView = LoadingEffectView(context, null) whenever(viewHolder.player).thenReturn(view) whenever(viewHolder.appIcon).thenReturn(appIcon) @@ -447,6 +452,7 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.multiRippleView).thenReturn(multiRippleView) whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView) + whenever(viewHolder.loadingEffectView).thenReturn(loadingEffectView) } /** Initialize elements for the recommendation view holder */ @@ -2429,6 +2435,7 @@ public class MediaControlPanelTest : SysuiTestCase() { mainExecutor.execute { assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE) + assertThat(loadingEffectView.visibility).isEqualTo(View.INVISIBLE) clock.advanceTime( MediaControlPanel.TURBULENCE_NOISE_PLAY_DURATION + @@ -2436,6 +2443,40 @@ public class MediaControlPanelTest : SysuiTestCase() { ) assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE) + assertThat(loadingEffectView.visibility).isEqualTo(View.INVISIBLE) + } + } + + @Test + @EnableFlags(Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR) + fun playTurbulenceNoise_newLoadingEffect_finishesAfterDuration() { + val semanticActions = + MediaButton( + playOrPause = + MediaAction( + icon = null, + action = {}, + contentDescription = "play", + background = null + ) + ) + val data = mediaData.copy(semanticActions = semanticActions) + player.attachPlayer(viewHolder) + player.bindPlayer(data, KEY) + + viewHolder.actionPlayPause.callOnClick() + + mainExecutor.execute { + assertThat(loadingEffectView.visibility).isEqualTo(View.VISIBLE) + assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE) + + clock.advanceTime( + MediaControlPanel.TURBULENCE_NOISE_PLAY_DURATION + + TurbulenceNoiseAnimationConfig.DEFAULT_EASING_DURATION_IN_MILLIS.toLong() + ) + + assertThat(loadingEffectView.visibility).isEqualTo(View.INVISIBLE) + assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE) } } @@ -2458,6 +2499,30 @@ public class MediaControlPanelTest : SysuiTestCase() { viewHolder.action0.callOnClick() assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE) + assertThat(loadingEffectView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @EnableFlags(Flags.FLAG_SHADERLIB_LOADING_EFFECT_REFACTOR) + fun playTurbulenceNoise_newLoadingEffect_whenPlaybackStateIsNotPlaying_doesNotPlayTurbulence() { + val semanticActions = + MediaButton( + custom0 = + MediaAction( + icon = null, + action = {}, + contentDescription = "custom0", + background = null + ), + ) + val data = mediaData.copy(semanticActions = semanticActions) + player.attachPlayer(viewHolder) + player.bindPlayer(data, KEY) + + viewHolder.action0.callOnClick() + + assertThat(loadingEffectView.visibility).isEqualTo(View.INVISIBLE) + assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java index bfb18c88bf9b..52859cdeb406 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java @@ -22,6 +22,8 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; @@ -289,32 +291,43 @@ public class NavigationBarControllerImplTest extends SysuiTestCase { verify(mCommandQueue, never()).addCallback(any(TaskbarDelegate.class)); } - @Test public void testConfigurationChange_taskbarNotInitialized() { Configuration configuration = mContext.getResources().getConfiguration(); - when(Utilities.isLargeScreen(any())).thenReturn(true); + mNavigationBarController.mIsLargeScreen = true; mNavigationBarController.onConfigChanged(configuration); verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration); } @Test - public void testConfigurationChangeUnfolding_taskbarInitialized() { + public void testConfigurationChange_taskbarInitialized() { Configuration configuration = mContext.getResources().getConfiguration(); - when(Utilities.isLargeScreen(any())).thenReturn(true); + mNavigationBarController.mIsLargeScreen = true; when(mTaskbarDelegate.isInitialized()).thenReturn(true); mNavigationBarController.onConfigChanged(configuration); verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration); } @Test - public void testConfigurationChangeFolding_taskbarInitialized() { + public void testShouldRenderTaskbar_taskbarNotRenderedOnPhone() { + mNavigationBarController.mIsLargeScreen = false; + mNavigationBarController.mIsPhone = true; + assertFalse(mNavigationBarController.supportsTaskbar()); + } + + @Test + public void testShouldRenderTaskbar_taskbarRenderedOnTabletOrUnfolded() { + mNavigationBarController.mIsLargeScreen = true; + mNavigationBarController.mIsPhone = false; + assertTrue(mNavigationBarController.supportsTaskbar()); + } + + @Test + public void testShouldRenderTaskbar_taskbarRenderedInFoldedState() { assumeTrue(enableTaskbarNavbarUnification()); - Configuration configuration = mContext.getResources().getConfiguration(); - when(Utilities.isLargeScreen(any())).thenReturn(false); - when(mTaskbarDelegate.isInitialized()).thenReturn(true); - mNavigationBarController.onConfigChanged(configuration); - verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration); + mNavigationBarController.mIsLargeScreen = false; + mNavigationBarController.mIsPhone = false; + assertTrue(mNavigationBarController.supportsTaskbar()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index cc27cbd9d809..44b897447759 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -103,7 +103,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; @@ -388,7 +387,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false); mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); - mSetFlagsRule.disableFlags(KeyguardShadeMigrationNssl.FLAG_NAME); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 2e8d46a83e1c..059053c58e39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -57,7 +57,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.DejankUtils; import com.android.systemui.flags.Flags; -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; @@ -363,7 +362,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void onInterceptTouchEvent_nsslMigrationOff_userActivity() { - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL); + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */, 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, @@ -374,7 +373,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void onInterceptTouchEvent_nsslMigrationOn_userActivity_not_called() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL); + mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */, 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, @@ -1125,7 +1124,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void nsslFlagEnabled_allowOnlyExternalTouches() { - mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME); + mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); // This sets the dozing state that is read when onMiddleClicked is eventually invoked. mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 248ed249c213..c2267903440a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -44,7 +44,6 @@ import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.res.R @@ -378,7 +377,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() { - mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) underTest.setStatusBarViewController(phoneStatusBarViewController) interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -388,7 +387,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Test fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() { - mSetFlagsRule.enableFlags(Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) underTest.setStatusBarViewController(phoneStatusBarViewController) interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -430,7 +429,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // AND the lock icon wants the touch whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true) - mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) + mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() @@ -449,7 +448,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(false) - mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) + mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -468,7 +467,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(true) - mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) + mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt index c32635020ddc..d7eada82b9a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService -import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.plugins.qs.QS @@ -100,7 +99,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) fakeSystemClock = FakeSystemClock() delayableExecutor = FakeExecutor(fakeSystemClock) - mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) + mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) mContext.ensureTestableResources() whenever(view.context).thenReturn(mContext) whenever(view.resources).thenReturn(mContext.resources) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 64fd80d72d3f..74d017375eb5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq +import com.android.systemui.util.ThreadAssert import java.util.function.BiConsumer import junit.framework.Assert.assertEquals import junit.framework.Assert.fail @@ -69,6 +70,7 @@ class ClockRegistryTest : SysuiTestCase() { @Mock private lateinit var mockDefaultClock: ClockController @Mock private lateinit var mockThumbnail: Drawable @Mock private lateinit var mockContentResolver: ContentResolver + @Mock private lateinit var mockThreadAssert: ThreadAssert private lateinit var fakeDefaultProvider: FakeClockPlugin private lateinit var pluginListener: PluginListener<ClockProviderPlugin> private lateinit var registry: ClockRegistry @@ -163,14 +165,12 @@ class ClockRegistryTest : SysuiTestCase() { defaultClockProvider = fakeDefaultProvider, keepAllLoaded = false, subTag = "Test", + assert = mockThreadAssert, ) { override fun querySettings() { } override fun applySettings(value: ClockSettings?) { settings = value } - // Unit Test does not validate threading - override fun assertMainThread() {} - override fun assertNotMainThread() {} } registry.registerListeners() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index ccc9dc0d9618..8a48fe10d7fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -50,8 +50,8 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.util.time.FakeSystemClock; @@ -280,6 +280,66 @@ public class NotificationEntryTest extends SysuiTestCase { } @Test + public void testIsNotificationVisibilityPrivate_true() { + assertTrue(mEntry.isNotificationVisibilityPrivate()); + } + + @Test + public void testIsNotificationVisibilityPrivate_visibilityPublic_false() { + Notification.Builder notification = new Notification.Builder(mContext, "") + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setChannel(mChannel) + .setId(mId++) + .setNotification(notification.build()) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + + assertFalse(entry.isNotificationVisibilityPrivate()); + } + + @Test + public void testIsChannelVisibilityPrivate_true() { + assertTrue(mEntry.isChannelVisibilityPrivate()); + } + + @Test + public void testIsChannelVisibilityPrivate_visibilityPublic_false() { + NotificationChannel channel = + new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE); + channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); + StatusBarNotification sbn = new SbnBuilder().build(); + Ranking ranking = new RankingBuilder() + .setChannel(channel) + .setKey(sbn.getKey()) + .build(); + NotificationEntry entry = + new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + + assertFalse(entry.isChannelVisibilityPrivate()); + } + + @Test + public void testIsChannelVisibilityPrivate_entryHasNoChannel_false() { + StatusBarNotification sbn = new SbnBuilder().build(); + Ranking ranking = new RankingBuilder() + .setChannel(null) + .setKey(sbn.getKey()) + .build(); + NotificationEntry entry = + new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); + + assertFalse(entry.isChannelVisibilityPrivate()); + } + + @Test public void notificationDataEntry_testIsLastMessageFromReply() { Person.Builder person = new Person.Builder() .setName("name") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index b7560ad79026..1687ccbf5826 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -22,15 +22,16 @@ import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR import android.view.LayoutInflater import android.view.MotionEvent +import android.view.View import android.view.ViewTreeObserver import android.view.ViewTreeObserver.OnPreDrawListener import android.widget.FrameLayout import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeControllerImpl @@ -48,8 +49,6 @@ import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.view.ViewUtil import com.google.common.truth.Truth.assertThat -import java.util.Optional -import javax.inject.Provider import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor @@ -60,6 +59,8 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import java.util.Optional +import javax.inject.Provider @SmallTest class PhoneStatusBarViewControllerTest : SysuiTestCase() { @@ -98,7 +99,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { val parent = FrameLayout(mContext) // add parent to keep layout params view = LayoutInflater.from(mContext).inflate(R.layout.status_bar, parent, false) - as PhoneStatusBarView + as PhoneStatusBarView controller = createAndInitController(view) } } @@ -231,6 +232,27 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { verify(centralSurfacesImpl).setInteracting(any(), any()) } + @Test + fun shadeIsExpandedOnStatusIconClick() { + val view = createViewMock() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + controller = createAndInitController(view) + } + val statusContainer = view.requireViewById<View>(R.id.system_icons) + statusContainer.performClick() + verify(shadeViewController).expand(any()) + } + + @Test + fun shadeIsNotExpandedOnStatusBarGeneralClick() { + val view = createViewMock() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + controller = createAndInitController(view) + } + view.performClick() + verify(shadeViewController, never()).expand(any()) + } + private fun getCommandQueueCallback(): CommandQueue.Callbacks { val captor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(captor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index 269b70fe6dfb..5e8b62e799c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -207,6 +207,72 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test + fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() { + val previousInsets = + Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10) + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(previousInsets) + context.orCreateTestableResources.overrideConfiguration(Configuration()) + view.onAttachedToWindow() + + val newInsets = Insets.NONE + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(newInsets) + view.onConfigurationChanged(Configuration()) + + assertThat(view.paddingLeft).isEqualTo(previousInsets.left) + assertThat(view.paddingTop).isEqualTo(previousInsets.top) + assertThat(view.paddingRight).isEqualTo(previousInsets.right) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test + fun onConfigurationChanged_densityChanged_updatesInsets() { + val previousInsets = + Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10) + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(previousInsets) + val configuration = Configuration() + configuration.densityDpi = 123 + context.orCreateTestableResources.overrideConfiguration(configuration) + view.onAttachedToWindow() + + val newInsets = Insets.NONE + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(newInsets) + configuration.densityDpi = 456 + view.onConfigurationChanged(configuration) + + assertThat(view.paddingLeft).isEqualTo(newInsets.left) + assertThat(view.paddingTop).isEqualTo(newInsets.top) + assertThat(view.paddingRight).isEqualTo(newInsets.right) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test + fun onConfigurationChanged_fontScaleChanged_updatesInsets() { + val previousInsets = + Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10) + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(previousInsets) + val configuration = Configuration() + configuration.fontScale = 1f + context.orCreateTestableResources.overrideConfiguration(configuration) + view.onAttachedToWindow() + + val newInsets = Insets.NONE + whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) + .thenReturn(newInsets) + configuration.fontScale = 2f + view.onConfigurationChanged(configuration) + + assertThat(view.paddingLeft).isEqualTo(newInsets.left) + assertThat(view.paddingTop).isEqualTo(newInsets.top) + assertThat(view.paddingRight).isEqualTo(newInsets.right) + assertThat(view.paddingBottom).isEqualTo(0) + } + + @Test fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() { val insets = Insets.of(/* left = */ 90, /* top = */ 10, /* right = */ 45, /* bottom = */ 50) whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt index 1dac642836c6..a2af38f77f41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt @@ -19,17 +19,24 @@ package com.android.systemui.statusbar.policy import android.app.ActivityOptions import android.app.IActivityManager import android.app.Notification +import android.app.Notification.FLAG_FOREGROUND_SERVICE +import android.app.Notification.VISIBILITY_PRIVATE +import android.app.Notification.VISIBILITY_PUBLIC +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE import android.media.projection.MediaProjectionInfo import android.media.projection.MediaProjectionManager import android.platform.test.annotations.EnableFlags import android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS -import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.server.notification.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.concurrency.mockExecutorHandler import com.android.systemui.util.mockito.whenever @@ -316,6 +323,25 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { assertFalse(controller.shouldProtectNotification(notificationEntry)) } + @Test + fun shouldProtectNotification_projectionActive_publicNotification_false() { + mediaProjectionCallback.onStart(mediaProjectionInfo) + + // App marked notification visibility as public + val notificationEntry = setupPublicNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) + + assertFalse(controller.shouldProtectNotification(notificationEntry)) + } + + @Test + fun shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_true() { + mediaProjectionCallback.onStart(mediaProjectionInfo) + + val notificationEntry = + setupPublicNotificationEntryWithUserOverriddenChannel(TEST_PROJECTION_PACKAGE_NAME) + + assertTrue(controller.shouldProtectNotification(notificationEntry)) + } private fun setDisabledViaDeveloperOption() { globalSettings.putInt(DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1) @@ -336,21 +362,50 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { private fun setupNotificationEntry( packageName: String, - isFgs: Boolean = false + isFgs: Boolean = false, + overrideVisibility: Boolean = false, + overrideChannelVisibility: Boolean = false, ): NotificationEntry { - val notificationEntry = mock(NotificationEntry::class.java) - val sbn = mock(StatusBarNotification::class.java) - val notification = mock(Notification::class.java) - whenever(notificationEntry.sbn).thenReturn(sbn) - whenever(sbn.packageName).thenReturn(packageName) - whenever(sbn.notification).thenReturn(notification) - whenever(notification.isFgsOrUij).thenReturn(isFgs) - + val notification = Notification() + if (isFgs) { + notification.flags = notification.flags or FLAG_FOREGROUND_SERVICE + } + if (overrideVisibility) { + // Developer has marked notification as public + notification.visibility = VISIBILITY_PUBLIC + } + val notificationEntry = + NotificationEntryBuilder().setNotification(notification).setPkg(packageName).build() + val channel = NotificationChannel("1", "1", IMPORTANCE_HIGH) + if (overrideChannelVisibility) { + // User doesn't allow private notifications at the channel level + channel.lockscreenVisibility = VISIBILITY_PRIVATE + } + notificationEntry.setRanking( + RankingBuilder(notificationEntry.ranking) + .setChannel(channel) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) + .build() + ) return notificationEntry } private fun setupFgsNotificationEntry(packageName: String): NotificationEntry { - return setupNotificationEntry(packageName, /* isFgs= */ true) + return setupNotificationEntry(packageName, isFgs = true) + } + + private fun setupPublicNotificationEntry(packageName: String): NotificationEntry { + return setupNotificationEntry(packageName, overrideVisibility = true) + } + + private fun setupPublicNotificationEntryWithUserOverriddenChannel( + packageName: String + ): NotificationEntry { + return setupNotificationEntry( + packageName, + overrideVisibility = true, + overrideChannelVisibility = true + ) } companion object { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt index 97f84c67f473..43897c985c2c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt @@ -18,7 +18,7 @@ package com.android.systemui.flags import android.platform.test.annotations.EnableFlags import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR -import com.android.systemui.Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER import com.android.systemui.Flags.FLAG_SCENE_CONTAINER @@ -29,7 +29,7 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER @EnableFlags( FLAG_SCENE_CONTAINER, FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, - FLAG_KEYGUARD_SHADE_MIGRATION_NSSL, + FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_MEDIA_IN_SCENE_CONTAINER, ) @Retention(AnnotationRetention.RUNTIME) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt new file mode 100644 index 000000000000..e1b1966aed6c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.systemui.media + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.media.dialog.MediaOutputDialogFactory +import com.android.systemui.util.mockito.mock + +var Kosmos.mediaOutputDialogFactory: MediaOutputDialogFactory by Kosmos.Fixture { mock {} } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt new file mode 100644 index 000000000000..3f20df3376d9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt @@ -0,0 +1,63 @@ +/* + * 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.systemui.volume + +import android.content.packageManager +import android.content.pm.ApplicationInfo +import android.media.session.MediaController +import android.os.Handler +import android.testing.TestableLooper +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.media.mediaOutputDialogFactory +import com.android.systemui.plugins.activityStarter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.volume.data.repository.FakeLocalMediaRepository +import com.android.systemui.volume.data.repository.FakeMediaControllerRepository +import com.android.systemui.volume.panel.component.mediaoutput.data.repository.FakeLocalMediaRepositoryFactory +import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor + +var Kosmos.mediaController: MediaController by Kosmos.Fixture { mock {} } + +val Kosmos.localMediaRepository by Kosmos.Fixture { FakeLocalMediaRepository() } +val Kosmos.localMediaRepositoryFactory: LocalMediaRepositoryFactory by + Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } } + +val Kosmos.mediaOutputActionsInteractor by + Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) } +val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() } +val Kosmos.mediaOutputInteractor by + Kosmos.Fixture { + MediaOutputInteractor( + localMediaRepositoryFactory, + packageManager.apply { + val appInfo: ApplicationInfo = mock { + whenever(loadLabel(any())).thenReturn("test_label") + } + whenever(getApplicationInfo(any(), any<Int>())).thenReturn(appInfo) + }, + testScope.backgroundScope, + testScope.testScheduler, + Handler(TestableLooper.get(testCase).looper), + mediaControllerRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeKosmos.kt new file mode 100644 index 000000000000..5e1f85c70a1b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeKosmos.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. + */ + +package com.android.systemui.volume + +import com.android.settingslib.volume.domain.interactor.AudioModeInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.data.repository.FakeAudioRepository + +val Kosmos.audioRepository by Kosmos.Fixture { FakeAudioRepository() } +val Kosmos.audioModeInteractor by Kosmos.Fixture { AudioModeInteractor(audioRepository) } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt index dddf8e82d5f7..fed3e171862d 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.settingslib.volume.data.repository +package com.android.systemui.volume.data.repository import android.media.AudioDeviceInfo +import com.android.settingslib.volume.data.repository.AudioRepository import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt index 642b72c70e55..7835fc89ea52 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt @@ -1,23 +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 + * 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 + * 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. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ -package com.android.settingslib.volume.data.repository +package com.android.systemui.volume.data.repository import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.model.RoutingSession +import com.android.settingslib.volume.data.repository.LocalMediaRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt new file mode 100644 index 000000000000..6d52e525d238 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt @@ -0,0 +1,34 @@ +/* + * 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.systemui.volume.data.repository + +import android.media.session.MediaController +import com.android.settingslib.volume.data.repository.MediaControllerRepository +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeMediaControllerRepository : MediaControllerRepository { + + private val mutableActiveLocalMediaController = MutableStateFlow<MediaController?>(null) + override val activeLocalMediaController: StateFlow<MediaController?> = + mutableActiveLocalMediaController.asStateFlow() + + fun setActiveLocalMediaController(controller: MediaController?) { + mutableActiveLocalMediaController.value = controller + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt new file mode 100644 index 000000000000..ad8ccb00f45f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt @@ -0,0 +1,25 @@ +/* + * 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.systemui.volume.panel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.media.mediaOutputDialogFactory +import com.android.systemui.plugins.activityStarter +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor + +val Kosmos.mediaOutputActionsInteractor by + Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt new file mode 100644 index 000000000000..1b3480c423e4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt @@ -0,0 +1,26 @@ +/* + * 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.systemui.volume.panel.component.mediaoutput.data.repository + +import com.android.settingslib.volume.data.repository.LocalMediaRepository + +class FakeLocalMediaRepositoryFactory( + val provider: (packageName: String?) -> LocalMediaRepository +) : LocalMediaRepositoryFactory { + + override fun create(packageName: String?): LocalMediaRepository = provider(packageName) +} diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 35ce4814f97d..132804f4f91d 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -24,6 +24,46 @@ java_library { visibility: ["//visibility:public"], } +java_library_host { + name: "ravenwood-helper-libcore-runtime.host", + srcs: [ + "runtime-helper-src/libcore-fake/**/*.java", + ], + visibility: ["//visibility:private"], +} + +java_host_for_device { + name: "ravenwood-helper-libcore-runtime", + libs: [ + "ravenwood-helper-libcore-runtime.host", + ], + visibility: ["//visibility:private"], +} + +java_library { + name: "ravenwood-helper-framework-runtime", + srcs: [ + "runtime-helper-src/framework/**/*.java", + ], + libs: [ + "framework-minus-apex.ravenwood", + ], + visibility: ["//visibility:private"], +} + +// Combine ravenwood-helper-*-runtime and create a single library, which we include +// in the ravenwood runtime. +// We do it this way rather than including the individual jars in the runtime, because +// for some reason we couldn't include a java_host_for_device module in the ravenwood runtime. +java_library { + name: "ravenwood-helper-runtime", + defaults: ["ravenwood-internal-only-visibility-java"], + static_libs: [ + "ravenwood-helper-framework-runtime", + "ravenwood-helper-libcore-runtime", + ], +} + java_library { name: "ravenwood-junit-impl", srcs: [ @@ -58,16 +98,6 @@ java_library { visibility: ["//visibility:public"], } -java_library { - // Prefixed with "200" to ensure it's sorted early in Tradefed classpath - // so that we provide a concrete implementation before Mainline stubs - name: "200-kxml2-android", - static_libs: [ - "kxml2-android", - ], - visibility: ["//frameworks/base"], -} - java_host_for_device { name: "androidx.test.monitor-for-device", libs: [ diff --git a/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md index d84cb6795fef..4b2f96804c97 100644 --- a/ravenwood/api-maintainers.md +++ b/ravenwood/api-maintainers.md @@ -82,7 +82,7 @@ When a pure-Java implementation grows too large or complex to host within the or ``` @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.MyComplexClass_host") +@RavenwoodNativeSubstitutionClass("com.android.platform.test.ravenwood.nativesubstitution.MyComplexClass_host") public class MyComplexClass { private static native void nativeDoThing(long nativePtr); ... diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh index 3f4b8a79e864..259aa702452d 100755 --- a/ravenwood/run-ravenwood-tests.sh +++ b/ravenwood/run-ravenwood-tests.sh @@ -13,10 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Run all the ravenwood tests. +# Run all the ravenwood tests + hoststubgen unit tests. + +all_tests="hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test" # "echo" is to remove the newlines -all_tests=$(echo $(${0%/*}/list-ravenwood-tests.sh) ) +all_tests="$all_tests $(echo $(${0%/*}/list-ravenwood-tests.sh) )" echo "Running tests: $all_tests" atest $all_tests diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/CursorWindow_host.java index eba99107f126..f38d5653d3a9 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/CursorWindow_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.database.Cursor; import android.database.sqlite.SQLiteException; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/EventLog_host.java index 6480cfc2b492..55d4ffb41e78 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/EventLog_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import com.android.internal.os.RuntimeInit; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Log_host.java index cdfa30276961..5930a14cdec8 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Log_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.util.Log; import android.util.Log.Level; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java index 4d39d88d58c3..741411095f53 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.os.BadParcelableException; import android.os.Parcel; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongMultiStateCounter_host.java index a5d0fc6872de..9486651ce48d 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongMultiStateCounter_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongMultiStateCounter_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.os.BadParcelableException; import android.os.Parcel; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/MessageQueue_host.java index 65da4a144160..5e81124b6e70 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/MessageQueue_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/ParcelFileDescriptor_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java index 0ebaac6016e2..2d799142df70 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/ParcelFileDescriptor_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import static android.os.ParcelFileDescriptor.MODE_APPEND; import static android.os.ParcelFileDescriptor.MODE_CREATE; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java index d63bff6f4da3..81ad31e631fe 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java index 2f6a361e3609..eba6c8b2db64 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.util.SparseArray; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java index fbcc64892798..1e120305fa2d 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/runtimehelper/ClassLoadHook.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.runtimehelper; - -import com.android.hoststubgen.hosthelper.HostTestException; +package com.android.platform.test.ravenwood.runtimehelper; import java.io.File; import java.io.PrintStream; @@ -79,7 +77,7 @@ public class ClassLoadHook { private static void ensurePropertyNotSet(String key) { if (System.getProperty(key) != null) { - throw new HostTestException("System property \"" + key + "\" is set unexpectedly"); + throw new RuntimeException("System property \"" + key + "\" is set unexpectedly"); } } diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/ErrnoException.java index 388156aa3694..388156aa3694 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java +++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/ErrnoException.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java index 379c4ae8a059..379c4ae8a059 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java index 7d2b00d9420d..7d2b00d9420d 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/dalvik/system/VMRuntime.java +++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java index 65c285e06bf8..65c285e06bf8 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/EmptyArray.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/EmptyArray.java index a1ae35a88656..a1ae35a88656 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/EmptyArray.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/EmptyArray.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/HexEncoding.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/HexEncoding.java index cc2fb7bbf236..cc2fb7bbf236 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/HexEncoding.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/HexEncoding.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/SneakyThrow.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/SneakyThrow.java index e142c46bc311..e142c46bc311 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/SneakyThrow.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/SneakyThrow.java diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index 6ce471e635cd..fff283dd41bc 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -16,10 +16,9 @@ package com.android.server; +import static android.permission.flags.Flags.sensitiveNotificationAppProtection; import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS; - import static com.android.internal.util.Preconditions.checkNotNull; -import static com.android.server.notification.Flags.sensitiveNotificationAppProtection; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index a341b4acaca1..2e14abbd4d40 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -263,6 +263,10 @@ public class SystemConfig { // location settings are off, for emergency purposes, as read from the configuration files. final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>(); + // These are the packages that are allow-listed to be able to access camera when + // the camera privacy state is for driver assistance apps only. + final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>(); + // These are the action strings of broadcasts which are whitelisted to // be delivered anonymously even to apps which target O+. final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>(); @@ -483,6 +487,10 @@ public class SystemConfig { return mAllowedAssociations; } + public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() { + return mAllowlistCameraPrivacy; + } + public ArraySet<String> getBugreportWhitelistedPackages() { return mBugreportWhitelistedPackages; } @@ -1062,6 +1070,22 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; + case "camera-privacy-allowlisted-app" : { + if (allowOverrideAppRestrictions) { + String pkgname = parser.getAttributeValue(null, "package"); + boolean isMandatory = XmlUtils.readBooleanAttribute( + parser, "mandatory", false); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowlistCameraPrivacy.put(pkgname, isMandatory); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; case "allow-ignore-location-settings": { if (allowOverrideAppRestrictions) { String pkgname = parser.getAttributeValue(null, "package"); diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java new file mode 100644 index 000000000000..3312be231516 --- /dev/null +++ b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java @@ -0,0 +1,238 @@ +/* + * 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.adaptiveauth; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; + +import android.app.KeyguardManager; +import android.content.Context; +import android.hardware.biometrics.AuthenticationStateListener; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.BiometricSourceType; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.util.Log; +import android.util.Slog; +import android.util.SparseIntArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockSettingsInternal; +import com.android.internal.widget.LockSettingsStateListener; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.pm.UserManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import java.util.Objects; + +/** + * @hide + */ +public class AdaptiveAuthService extends SystemService { + private static final String TAG = "AdaptiveAuthService"; + private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG); + + @VisibleForTesting + static final int MAX_ALLOWED_FAILED_AUTH_ATTEMPTS = 5; + private static final int MSG_REPORT_PRIMARY_AUTH_ATTEMPT = 1; + private static final int MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT = 2; + private static final int AUTH_SUCCESS = 1; + private static final int AUTH_FAILURE = 0; + + private final LockPatternUtils mLockPatternUtils; + private final LockSettingsInternal mLockSettings; + private final BiometricManager mBiometricManager; + private final KeyguardManager mKeyguardManager; + private final PowerManager mPowerManager; + private final WindowManagerInternal mWindowManager; + private final UserManagerInternal mUserManager; + @VisibleForTesting + final SparseIntArray mFailedAttemptsForUser = new SparseIntArray(); + + public AdaptiveAuthService(Context context) { + this(context, new LockPatternUtils(context)); + } + + @VisibleForTesting + public AdaptiveAuthService(Context context, LockPatternUtils lockPatternUtils) { + super(context); + mLockPatternUtils = lockPatternUtils; + mLockSettings = Objects.requireNonNull( + LocalServices.getService(LockSettingsInternal.class)); + mBiometricManager = Objects.requireNonNull( + context.getSystemService(BiometricManager.class)); + mKeyguardManager = Objects.requireNonNull(context.getSystemService(KeyguardManager.class)); + mPowerManager = Objects.requireNonNull(context.getSystemService(PowerManager.class)); + mWindowManager = Objects.requireNonNull( + LocalServices.getService(WindowManagerInternal.class)); + mUserManager = Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class)); + } + + @Override + public void onStart() {} + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + init(); + } + } + + @VisibleForTesting + void init() { + mLockSettings.registerLockSettingsStateListener(mLockSettingsStateListener); + mBiometricManager.registerAuthenticationStateListener(mAuthenticationStateListener); + } + + private final LockSettingsStateListener mLockSettingsStateListener = + new LockSettingsStateListener() { + @Override + public void onAuthenticationSucceeded(int userId) { + if (DEBUG) { + Slog.d(TAG, "LockSettingsStateListener#onAuthenticationSucceeded"); + } + mHandler.obtainMessage(MSG_REPORT_PRIMARY_AUTH_ATTEMPT, AUTH_SUCCESS, userId) + .sendToTarget(); + } + + @Override + public void onAuthenticationFailed(int userId) { + Slog.i(TAG, "LockSettingsStateListener#onAuthenticationFailed"); + mHandler.obtainMessage(MSG_REPORT_PRIMARY_AUTH_ATTEMPT, AUTH_FAILURE, userId) + .sendToTarget(); + } + }; + + private final AuthenticationStateListener mAuthenticationStateListener = + new AuthenticationStateListener.Stub() { + @Override + public void onAuthenticationStarted(int requestReason) {} + + @Override + public void onAuthenticationStopped() {} + + @Override + public void onAuthenticationSucceeded(int requestReason, int userId) { + if (DEBUG) { + Slog.d(TAG, "AuthenticationStateListener#onAuthenticationSucceeded"); + } + mHandler.obtainMessage(MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT, AUTH_SUCCESS, userId) + .sendToTarget(); + } + + @Override + public void onAuthenticationFailed(int requestReason, int userId) { + Slog.i(TAG, "AuthenticationStateListener#onAuthenticationFailed"); + mHandler.obtainMessage(MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT, AUTH_FAILURE, userId) + .sendToTarget(); + } + + @Override + public void onAuthenticationAcquired(BiometricSourceType biometricSourceType, + int requestReason, int acquiredInfo) {} + }; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REPORT_PRIMARY_AUTH_ATTEMPT: + handleReportPrimaryAuthAttempt(msg.arg1 != AUTH_FAILURE, msg.arg2); + break; + case MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT: + handleReportBiometricAuthAttempt(msg.arg1 != AUTH_FAILURE, msg.arg2); + break; + } + } + }; + + private void handleReportPrimaryAuthAttempt(boolean success, int userId) { + if (DEBUG) { + Slog.d(TAG, "handleReportPrimaryAuthAttempt: success=" + success + + ", userId=" + userId); + } + reportAuthAttempt(success, userId); + } + + private void handleReportBiometricAuthAttempt(boolean success, int userId) { + if (DEBUG) { + Slog.d(TAG, "handleReportBiometricAuthAttempt: success=" + success + + ", userId=" + userId); + } + reportAuthAttempt(success, userId); + } + + private void reportAuthAttempt(boolean success, int userId) { + if (success) { + // Deleting the entry effectively resets the counter of failed attempts for the user + mFailedAttemptsForUser.delete(userId); + return; + } + + final int numFailedAttempts = mFailedAttemptsForUser.get(userId, 0) + 1; + Slog.i(TAG, "reportAuthAttempt: numFailedAttempts=" + numFailedAttempts + + ", userId=" + userId); + mFailedAttemptsForUser.put(userId, numFailedAttempts); + + // Don't lock again if the device is already locked and if Keyguard is already showing and + // isn't trivially dismissible + if (mKeyguardManager.isDeviceLocked(userId) && mKeyguardManager.isKeyguardLocked()) { + Slog.d(TAG, "Not locking the device because the device is already locked."); + return; + } + + if (numFailedAttempts < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS) { + Slog.d(TAG, "Not locking the device because the number of failed attempts is below" + + " the threshold."); + return; + } + + //TODO: additionally consider the trust signal before locking device + lockDevice(userId); + } + + /** + * Locks the device and requires primary auth or biometric auth for unlocking + */ + private void lockDevice(int userId) { + // Require either primary auth or biometric auth to unlock the device again. Keyguard and + // bouncer will also check the StrongAuthFlag for the user to display correct strings for + // explaining why the device is locked + mLockPatternUtils.requireStrongAuth(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST, userId); + + // If userId is a profile that has a different parent userId (regardless of its profile + // type, or whether it's a profile with unified challenges or not), its parent userId that + // owns the Keyguard will also be locked + final int parentUserId = mUserManager.getProfileParentId(userId); + Slog.i(TAG, "lockDevice: userId=" + userId + ", parentUserId=" + parentUserId); + if (parentUserId != userId) { + mLockPatternUtils.requireStrongAuth(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST, + parentUserId); + } + + // Power off the display + mPowerManager.goToSleep(SystemClock.uptimeMillis()); + + // Lock the device + mWindowManager.lockNow(); + } +} diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index c85723525aa1..1dc384d61c91 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -383,6 +383,11 @@ public final class AppStartInfoTracker { start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid); start.setProcessName(app.processName); start.setPackageName(app.info.packageName); + if (android.content.pm.Flags.stayStopped()) { + // TODO: Verify this is created at the right time to have the correct force-stopped + // state in the ProcessRecord. Also use the WindowProcessRecord if activity. + start.setForceStopped(app.wasForceStopped()); + } } void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 7d82f0c2a63e..df46e5dafce8 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT; import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; @@ -67,7 +68,10 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE; import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; +import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL; +import static android.media.audio.Flags.foregroundAudioControl; import static android.os.Process.SCHED_OTHER; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; @@ -2266,6 +2270,15 @@ public class OomAdjuster { (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; + if (foregroundAudioControl()) { // flag check + final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + | FOREGROUND_SERVICE_TYPE_CAMERA + | FOREGROUND_SERVICE_TYPE_MICROPHONE + | FOREGROUND_SERVICE_TYPE_PHONE_CALL; + capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0 + ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0; + } + final boolean enabled = state.getCachedCompatChange( CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY); if (enabled) { diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index f85b03e8b4eb..1bf779adcce1 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -722,14 +722,8 @@ public class OomAdjusterModernImpl extends OomAdjuster { performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids, fullUpdate, now, UNKNOWN_ADJ); - if (fullUpdate) { - assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); - } else { - activeProcesses.clear(); - activeProcesses.addAll(targetProcesses); - assignCachedAdjIfNecessary(activeProcesses); - activeProcesses.clear(); - } + // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list. + assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); targetProcesses.clear(); @@ -996,11 +990,11 @@ public class OomAdjusterModernImpl extends OomAdjuster { && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ) || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND - && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) { + && service.mState.getCurProcState() <= PROCESS_STATE_TOP) + || (service.isSdkSandbox && cr.binding.attributedClient != null)) { continue; } - computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false, oomAdjReason, cachedAdj, false, false); } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index d23d9fb16d6c..9883f091deef 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1678,7 +1678,11 @@ class ProcessRecord implements WindowProcessListener { final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j); for (int k = clist.size() - 1; k >= 0; k--) { final ConnectionRecord cr = clist.get(k); - consumer.accept(cr.binding.client); + if (isSdkSandbox && cr.binding.attributedClient != null) { + consumer.accept(cr.binding.attributedClient); + } else { + consumer.accept(cr.binding.client); + } } } } @@ -1689,25 +1693,5 @@ class ProcessRecord implements WindowProcessListener { consumer.accept(conn.client); } } - // If this process is a sandbox itself, also add the app on whose behalf - // its running - if (isSdkSandbox) { - for (int is = mServices.numberOfRunningServices() - 1; is >= 0; is--) { - ServiceRecord s = mServices.getRunningServiceAt(is); - ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = - s.getConnections(); - for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) { - ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni); - for (int i = clist.size() - 1; i >= 0; i--) { - ConnectionRecord cr = clist.get(i); - ProcessRecord attributedApp = cr.binding.attributedClient; - if (attributedApp == null || attributedApp == this) { - continue; - } - consumer.accept(attributedApp); - } - } - } - } } } diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 57d233e7c503..562beaf50a7f 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -205,10 +205,10 @@ final class ProcessServiceRecord { } /** - * Returns the FGS typps, but it doesn't tell if the types include "NONE" or not, so - * do not use it outside of this class. + * Returns the FGS types, but it doesn't tell if the types include "NONE" or not, use + * {@link #hasForegroundServices()} */ - private int getForegroundServiceTypes() { + int getForegroundServiceTypes() { return mHasForegroundServices ? mFgServiceTypes : 0; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 5c95d433a6c1..fca119917fd0 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -106,6 +106,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.hardware.SensorPrivacyManager; import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION; import android.net.Uri; import android.os.AsyncTask; @@ -151,6 +152,7 @@ import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; +import com.android.internal.camera.flags.Flags; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.Clock; import com.android.internal.pm.pkg.component.ParsedAttribution; @@ -164,7 +166,6 @@ import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; import com.android.server.LockGuard; -import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemServiceManager; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.pm.PackageList; @@ -223,6 +224,8 @@ public class AppOpsService extends IAppOpsService.Stub { */ private static final int CURRENT_VERSION = 1; + private SensorPrivacyManager mSensorPrivacyManager; + // Write at most every 30 minutes. static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000; @@ -655,11 +658,11 @@ public class AppOpsService extends IAppOpsService.Stub { return attributedOp; } - @NonNull OpEntry createEntryLocked() { + @NonNull OpEntry createEntryLocked(String persistentDeviceId) { // TODO(b/308201969): Update this method when we introduce disk persistence of events // for accesses on external devices. final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get( - PERSISTENT_DEVICE_ID_DEFAULT); + persistentDeviceId); final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries = new ArrayMap<>(attributedOps.size()); for (int i = 0; i < attributedOps.size(); i++) { @@ -1034,7 +1037,7 @@ public class AppOpsService extends IAppOpsService.Stub { new Ops(pkgName, uidState)); } - createSandboxUidStateIfNotExistsForAppLocked(uid); + createSandboxUidStateIfNotExistsForAppLocked(uid, null); } } else if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) { synchronized (AppOpsService.this) { @@ -1046,69 +1049,8 @@ public class AppOpsService extends IAppOpsService.Stub { return; } - ArrayMap<String, String> dstAttributionTags = new ArrayMap<>(); - ArraySet<String> attributionTags = new ArraySet<>(); - attributionTags.add(null); - if (pkg.getAttributions() != null) { - int numAttributions = pkg.getAttributions().size(); - for (int attributionNum = 0; attributionNum < numAttributions; - attributionNum++) { - ParsedAttribution attribution = pkg.getAttributions().get(attributionNum); - attributionTags.add(attribution.getTag()); - - int numInheritFrom = attribution.getInheritFrom().size(); - for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; - inheritFromNum++) { - dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum), - attribution.getTag()); - } - } - } - synchronized (AppOpsService.this) { - UidState uidState = mUidStates.get(uid); - if (uidState == null) { - return; - } - - Ops ops = uidState.pkgOps.get(pkgName); - if (ops == null) { - return; - } - - // Reset cached package properties to re-initialize when needed - ops.bypass = null; - ops.knownAttributionTags.clear(); - - // Merge data collected for removed attributions into their successor - // attributions - int numOps = ops.size(); - for (int opNum = 0; opNum < numOps; opNum++) { - Op op = ops.valueAt(opNum); - for (int deviceIndex = op.mDeviceAttributedOps.size() - 1; deviceIndex >= 0; - deviceIndex--) { - ArrayMap<String, AttributedOp> attributedOps = - op.mDeviceAttributedOps.valueAt(deviceIndex); - for (int tagIndex = attributedOps.size() - 1; tagIndex >= 0; - tagIndex--) { - String tag = attributedOps.keyAt(tagIndex); - if (attributionTags.contains(tag)) { - // attribution still exist after upgrade - continue; - } - - String newAttributionTag = dstAttributionTags.get(tag); - - AttributedOp newAttributedOp = op.getOrCreateAttribution(op, - newAttributionTag, - op.mDeviceAttributedOps.keyAt(deviceIndex)); - newAttributedOp.add(attributedOps.get(tag)); - attributedOps.remove(tag); - - scheduleFastWriteLocked(); - } - } - } + refreshAttributionsLocked(pkg, uid); } } } @@ -1132,41 +1074,6 @@ public class AppOpsService extends IAppOpsService.Stub { mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL, packageUpdateFilter, null, null); - synchronized (this) { - for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) { - int uid = mUidStates.keyAt(uidNum); - UidState uidState = mUidStates.valueAt(uidNum); - - String[] pkgsInUid = getPackagesForUid(uidState.uid); - if (ArrayUtils.isEmpty(pkgsInUid) && uid >= Process.FIRST_APPLICATION_UID) { - uidState.clear(); - mUidStates.removeAt(uidNum); - scheduleFastWriteLocked(); - continue; - } - - ArrayMap<String, Ops> pkgs = uidState.pkgOps; - - int numPkgs = pkgs.size(); - for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { - String pkg = pkgs.keyAt(pkgNum); - - String action; - if (!ArrayUtils.contains(pkgsInUid, pkg)) { - action = ACTION_PACKAGE_REMOVED; - } else { - action = Intent.ACTION_PACKAGE_REPLACED; - } - - SystemServerInitThreadPool.submit( - () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action) - .setData(Uri.fromParts("package", pkg, null)) - .putExtra(Intent.EXTRA_UID, uid)), - "Update app-ops uidState in case package " + pkg + " changed"); - } - } - } - prepareInternalCallbacks(); final IntentFilter packageSuspendFilter = new IntentFilter(); @@ -1231,6 +1138,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } }); + mSensorPrivacyManager = SensorPrivacyManager.getInstance(mContext); } @VisibleForTesting @@ -1253,20 +1161,27 @@ public class AppOpsService extends IAppOpsService.Stub { void initializeUidStates() { UserManagerInternal umi = getUserManagerInternal(); synchronized (this) { + SparseBooleanArray knownUids = new SparseBooleanArray(); + + for (int uid : NON_PACKAGE_UIDS) { + if (!mUidStates.contains(uid)) { + mUidStates.put(uid, new UidState(uid)); + } + knownUids.put(uid, true); + } + int[] userIds = umi.getUserIds(); try (PackageManagerLocal.UnfilteredSnapshot snapshot = getPackageManagerLocal().withUnfilteredSnapshot()) { Map<String, PackageState> packageStates = snapshot.getPackageStates(); for (int i = 0; i < userIds.length; i++) { int userId = userIds[i]; - initializeUserUidStatesLocked(userId, packageStates); + initializeUserUidStatesLocked(userId, packageStates, knownUids); } - } - for (int uid : NON_PACKAGE_UIDS) { - mUidStates.put(uid, new UidState(uid)); + trimUidStatesLocked(knownUids, packageStates); + mUidStatesInitialized = true; } - mUidStatesInitialized = true; } } @@ -1274,26 +1189,34 @@ public class AppOpsService extends IAppOpsService.Stub { synchronized (this) { try (PackageManagerLocal.UnfilteredSnapshot snapshot = getPackageManagerLocal().withUnfilteredSnapshot()) { - initializeUserUidStatesLocked(userId, snapshot.getPackageStates()); + initializeUserUidStatesLocked(userId, snapshot.getPackageStates(), null); } } } private void initializeUserUidStatesLocked(int userId, Map<String, - PackageState> packageStates) { + PackageState> packageStates, SparseBooleanArray knownUids) { for (Map.Entry<String, PackageState> entry : packageStates.entrySet()) { - int appId = entry.getValue().getAppId(); + PackageState packageState = entry.getValue(); + if (packageState.isApex()) { + continue; + } + int appId = packageState.getAppId(); String packageName = entry.getKey(); - initializePackageUidStateLocked(userId, appId, packageName); + initializePackageUidStateLocked(userId, appId, packageName, knownUids); } } /* Be careful not to clear any existing data; only want to add objects that don't already exist. */ - private void initializePackageUidStateLocked(int userId, int appId, String packageName) { + private void initializePackageUidStateLocked(int userId, int appId, String packageName, + SparseBooleanArray knownUids) { int uid = UserHandle.getUid(userId, appId); + if (knownUids != null) { + knownUids.put(uid, true); + } UidState uidState = getUidStateLocked(uid, true); Ops ops = uidState.pkgOps.get(packageName); if (ops == null) { @@ -1311,7 +1234,105 @@ public class AppOpsService extends IAppOpsService.Stub { } } - createSandboxUidStateIfNotExistsForAppLocked(uid); + createSandboxUidStateIfNotExistsForAppLocked(uid, knownUids); + } + + private void trimUidStatesLocked(SparseBooleanArray knownUids, + Map<String, PackageState> packageStates) { + synchronized (this) { + // Remove what may have been added during persistence parsing + for (int i = mUidStates.size() - 1; i >= 0; i--) { + int uid = mUidStates.keyAt(i); + if (knownUids.get(uid, false)) { + if (uid >= Process.FIRST_APPLICATION_UID) { + ArrayMap<String, Ops> pkgOps = mUidStates.valueAt(i).pkgOps; + for (int j = 0; j < pkgOps.size(); j++) { + String pkgName = pkgOps.keyAt(j); + if (!packageStates.containsKey(pkgName)) { + pkgOps.removeAt(j); + continue; + } + AndroidPackage pkg = packageStates.get(pkgName).getAndroidPackage(); + if (pkg != null) { + refreshAttributionsLocked(pkg, uid); + } + } + if (pkgOps.isEmpty()) { + mUidStates.remove(i); + } + } + } else { + mUidStates.removeAt(i); + } + } + } + } + + @GuardedBy("this") + private void refreshAttributionsLocked(AndroidPackage pkg, int uid) { + String pkgName = pkg.getPackageName(); + ArrayMap<String, String> dstAttributionTags = new ArrayMap<>(); + ArraySet<String> attributionTags = new ArraySet<>(); + attributionTags.add(null); + if (pkg.getAttributions() != null) { + int numAttributions = pkg.getAttributions().size(); + for (int attributionNum = 0; attributionNum < numAttributions; + attributionNum++) { + ParsedAttribution attribution = pkg.getAttributions().get(attributionNum); + attributionTags.add(attribution.getTag()); + + int numInheritFrom = attribution.getInheritFrom().size(); + for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; + inheritFromNum++) { + dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum), + attribution.getTag()); + } + } + } + + UidState uidState = mUidStates.get(uid); + if (uidState == null) { + return; + } + + Ops ops = uidState.pkgOps.get(pkgName); + if (ops == null) { + return; + } + + // Reset cached package properties to re-initialize when needed + ops.bypass = null; + ops.knownAttributionTags.clear(); + + // Merge data collected for removed attributions into their successor + // attributions + int numOps = ops.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + Op op = ops.valueAt(opNum); + for (int deviceIndex = op.mDeviceAttributedOps.size() - 1; deviceIndex >= 0; + deviceIndex--) { + ArrayMap<String, AttributedOp> attributedOps = + op.mDeviceAttributedOps.valueAt(deviceIndex); + for (int tagIndex = attributedOps.size() - 1; tagIndex >= 0; + tagIndex--) { + String tag = attributedOps.keyAt(tagIndex); + if (attributionTags.contains(tag)) { + // attribution still exist after upgrade + continue; + } + + String newAttributionTag = dstAttributionTags.get(tag); + + AttributedOp newAttributedOp = op.getOrCreateAttribution(op, + newAttributionTag, + op.mDeviceAttributedOps.keyAt(deviceIndex)); + newAttributedOp.add(attributedOps.get(tag)); + attributedOps.remove(tag); + + scheduleFastWriteLocked(); + } + } + } } /** @@ -1529,13 +1550,14 @@ public class AppOpsService extends IAppOpsService.Stub { mHistoricalRegistry.shutdown(); } - private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) { + private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops, + String persistentDeviceId) { ArrayList<AppOpsManager.OpEntry> resOps = null; if (ops == null) { resOps = new ArrayList<>(); for (int j=0; j<pkgOps.size(); j++) { Op curOp = pkgOps.valueAt(j); - resOps.add(getOpEntryForResult(curOp)); + resOps.add(getOpEntryForResult(curOp, persistentDeviceId)); } } else { for (int j=0; j<ops.length; j++) { @@ -1544,7 +1566,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (resOps == null) { resOps = new ArrayList<>(); } - resOps.add(getOpEntryForResult(curOp)); + resOps.add(getOpEntryForResult(curOp, persistentDeviceId)); } } } @@ -1588,16 +1610,23 @@ public class AppOpsService extends IAppOpsService.Stub { return resOps; } - private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) { - return op.createEntryLocked(); + private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op, String persistentDeviceId) { + return op.createEntryLocked(persistentDeviceId); } @Override public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) { + return getPackagesForOpsForDevice(ops, PERSISTENT_DEVICE_ID_DEFAULT); + } + + @Override + public List<AppOpsManager.PackageOps> getPackagesForOpsForDevice(int[] ops, + @NonNull String persistentDeviceId) { final int callingUid = Binder.getCallingUid(); final boolean hasAllPackageAccess = mContext.checkPermission( Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(), Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED; + ArrayList<AppOpsManager.PackageOps> res = null; synchronized (this) { final int uidStateCount = mUidStates.size(); @@ -1606,21 +1635,24 @@ public class AppOpsService extends IAppOpsService.Stub { if (uidState.pkgOps.isEmpty()) { continue; } + // Caller can always see their packages and with a permission all. + if (!hasAllPackageAccess && callingUid != uidState.uid) { + continue; + } + ArrayMap<String, Ops> packages = uidState.pkgOps; final int packageCount = packages.size(); for (int j = 0; j < packageCount; j++) { Ops pkgOps = packages.valueAt(j); - ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops); + ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops, + persistentDeviceId); if (resOps != null) { if (res == null) { res = new ArrayList<>(); } AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( pkgOps.packageName, pkgOps.uidState.uid, resOps); - // Caller can always see their packages and with a permission all. - if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) { - res.add(resPackage); - } + res.add(resPackage); } } } @@ -1642,7 +1674,8 @@ public class AppOpsService extends IAppOpsService.Stub { if (pkgOps == null) { return null; } - ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops); + ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops, + PERSISTENT_DEVICE_ID_DEFAULT); if (resOps == null || resOps.size() == 0) { return null; } @@ -4246,8 +4279,15 @@ public class AppOpsService extends IAppOpsService.Stub { return uidState; } - private void createSandboxUidStateIfNotExistsForAppLocked(int uid) { + private void createSandboxUidStateIfNotExistsForAppLocked(int uid, + SparseBooleanArray knownUids) { + if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) { + return; + } final int sandboxUid = Process.toSdkSandboxUid(uid); + if (knownUids != null) { + knownUids.put(sandboxUid, true); + } getUidStateLocked(sandboxUid, true); } @@ -4642,6 +4682,10 @@ public class AppOpsService extends IAppOpsService.Stub { return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid)); } + private boolean isAutomotive() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + private boolean isOpRestrictedLocked(int uid, int code, String packageName, String attributionTag, int virtualDeviceId, @Nullable RestrictionBypass appBypass, boolean isCheckOp) { @@ -4658,6 +4702,13 @@ public class AppOpsService extends IAppOpsService.Stub { } } + if ((code == OP_CAMERA) && isAutomotive()) { + if ((Flags.cameraPrivacyAllowlist()) + && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) { + return true; + } + } + int userHandle = UserHandle.getUserId(uid); restrictionSetCount = mOpUserRestrictions.size(); diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index 23a384ff5d3b..bc6ef2005584 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -16,6 +16,7 @@ package com.android.server.appop; +import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; @@ -30,6 +31,7 @@ import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS; import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; import static android.app.AppOpsManager.UID_STATE_TOP; @@ -139,7 +141,6 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { } private int evalModeInternal(int uid, int code, int uidState, int uidCapability) { - if (getUidAppWidgetVisible(uid) || mActivityManagerInternal.isPendingTopUid(uid) || mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) { return MODE_ALLOWED; @@ -173,6 +174,8 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { case OP_RECORD_AUDIO: case OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO: return PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + case OP_TAKE_AUDIO_FOCUS: + return PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; default: return PROCESS_CAPABILITY_NONE; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c59f4f7888ce..559a1d647ea4 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -34,6 +34,7 @@ import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency; import static android.media.audio.Flags.focusFreezeTestApi; +import static android.media.audio.Flags.foregroundAudioControl; import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.INVALID_UID; @@ -1356,7 +1357,8 @@ public class AudioService extends IAudioService.Stub mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); - mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive()); + mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive(), mAppOps, + context.getPackageManager()); } private void initVolumeStreamStates() { @@ -4517,7 +4519,8 @@ public class AudioService extends IAudioService.Stub } private void dumpFlags(PrintWriter pw) { - pw.println("\nFun with Flags: "); + + pw.println("\nFun with Flags:"); pw.println("\tandroid.media.audio.autoPublicVolumeApiHardening:" + autoPublicVolumeApiHardening()); pw.println("\tandroid.media.audio.Flags.automaticBtDeviceType:" @@ -4528,8 +4531,8 @@ public class AudioService extends IAudioService.Stub + focusFreezeTestApi()); pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); - pw.println("\tandroid.media.audiopolicy.enableFadeManagerConfiguration:" - + enableFadeManagerConfiguration()); + pw.println("\tandroid.media.audio.foregroundAudioControl:" + + foregroundAudioControl()); } private void dumpAudioMode(PrintWriter pw) { @@ -10175,10 +10178,38 @@ public class AudioService extends IAudioService.Stub .record(); return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } + + // does caller have system privileges to bypass HardeningEnforcer + boolean permissionOverridesCheck = false; + if ((mContext.checkCallingOrSelfPermission( + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + == PackageManager.PERMISSION_GRANTED) + || (mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) + == PackageManager.PERMISSION_GRANTED)) { + permissionOverridesCheck = true; + } else if (uid < UserHandle.AID_APP_START) { + permissionOverridesCheck = true; + } + + final long token = Binder.clearCallingIdentity(); + try { + if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid, + HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS, + clientId, durationHint, callingPackageName)) { + final String reason = "Audio focus request blocked by hardening"; + Log.w(TAG, reason); + mmi.set(MediaMetrics.Property.EARLY_RETURN, reason).record(); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + } finally { + Binder.restoreCallingIdentity(token); + } + mmi.record(); return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, clientId, callingPackageName, attributionTag, flags, sdk, - forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/); + forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/, + permissionOverridesCheck); } /** see {@link AudioManager#requestAudioFocusForTest(AudioFocusRequest, String, int, int)} */ @@ -10195,7 +10226,7 @@ public class AudioService extends IAudioService.Stub } return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, clientId, callingPackageName, null, flags, - sdk, false /*forceDuck*/, fakeUid); + sdk, false /*forceDuck*/, fakeUid, true /*permissionOverridesCheck*/); } public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa, @@ -11639,6 +11670,7 @@ public class AudioService extends IAudioService.Stub pw.println("\nMessage handler is null"); } dumpFlags(pw); + mHardeningEnforcer.dump(pw); mMediaFocusControl.dump(pw); dumpStreamStates(pw); dumpVolumeGroups(pw); diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java index 4ceb83b2e1c9..409ed17001b7 100644 --- a/services/core/java/com/android/server/audio/HardeningEnforcer.java +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -18,13 +18,21 @@ package com.android.server.audio; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import android.Manifest; +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.media.AudioFocusRequest; import android.media.AudioManager; import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; -import android.util.Log; +import android.util.Slog; + +import com.android.server.utils.EventLogger; + +import java.io.PrintWriter; /** * Class to encapsulate all audio API hardening operations @@ -32,10 +40,19 @@ import android.util.Log; public class HardeningEnforcer { private static final String TAG = "AS.HardeningEnforcer"; + private static final boolean DEBUG = false; + private static final int LOG_NB_EVENTS = 20; final Context mContext; + final AppOpsManager mAppOps; final boolean mIsAutomotive; + final ActivityManager mActivityManager; + final PackageManager mPackageManager; + + final EventLogger mEventLogger = new EventLogger(LOG_NB_EVENTS, + "Hardening enforcement"); + /** * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)} */ @@ -56,10 +73,24 @@ public class HardeningEnforcer { * Matches calls from {@link AudioManager#setRingerMode(int)} */ public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200; + /** + * Matches calls from {@link AudioManager#requestAudioFocus(AudioFocusRequest)} + * and legacy variants + */ + public static final int METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS = 300; - public HardeningEnforcer(Context ctxt, boolean isAutomotive) { + public HardeningEnforcer(Context ctxt, boolean isAutomotive, AppOpsManager appOps, + PackageManager pm) { mContext = ctxt; mIsAutomotive = isAutomotive; + mAppOps = appOps; + mActivityManager = ctxt.getSystemService(ActivityManager.class); + mPackageManager = pm; + } + + protected void dump(PrintWriter pw) { + // log + mEventLogger.dump(pw); } /** @@ -84,7 +115,7 @@ public class HardeningEnforcer { } // TODO metrics? // TODO log for audio dumpsys? - Log.e(TAG, "Preventing volume method " + volumeMethod + " for " + Slog.e(TAG, "Preventing volume method " + volumeMethod + " for " + getPackNameForUid(Binder.getCallingUid())); return true; } @@ -92,10 +123,40 @@ public class HardeningEnforcer { return false; } + /** + * Checks whether the call in the current thread should be allowed or blocked + * @param focusMethod name of the method to check, for logging purposes + * @param clientId id of the requester + * @param durationHint focus type being requested + * @return false if the method call is allowed, true if it should be a no-op + */ + protected boolean blockFocusMethod(int callingUid, int focusMethod, @NonNull String clientId, + int durationHint, @NonNull String packageName) { + if (packageName.isEmpty()) { + packageName = getPackNameForUid(callingUid); + } + + if (checkAppOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName)) { + if (DEBUG) { + Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking"); + } + return false; + } + + String errorMssg = "Focus request DENIED for uid:" + callingUid + + " clientId:" + clientId + " req:" + durationHint + + " procState:" + mActivityManager.getUidProcessState(callingUid); + + // TODO metrics + mEventLogger.enqueueAndSlog(errorMssg, EventLogger.Event.ALOGI, TAG); + + return true; + } + private String getPackNameForUid(int uid) { final long token = Binder.clearCallingIdentity(); try { - final String[] names = mContext.getPackageManager().getPackagesForUid(uid); + final String[] names = mPackageManager.getPackagesForUid(uid); if (names == null || names.length == 0 || TextUtils.isEmpty(names[0])) { @@ -106,4 +167,18 @@ public class HardeningEnforcer { Binder.restoreCallingIdentity(token); } } + + /** + * Checks the given op without throwing + * @param op the appOp code + * @param uid the calling uid + * @param packageName the package name of the caller + * @return return false if the operation is not allowed + */ + private boolean checkAppOp(int op, int uid, @NonNull String packageName) { + if (mAppOps.checkOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) { + return false; + } + return true; + } } diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 1376bde2fb71..35d38e2373f5 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -1090,11 +1090,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * accessibility. * @param testUid ignored if flags doesn't contain AudioManager.AUDIOFOCUS_FLAG_TEST * otherwise the UID being injected for testing + * @param permissionOverridesCheck true if permission checks guaranteed that the call should + * go through, false otherwise (e.g. non-privileged caller) * @return */ protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName, - String attributionTag, int flags, int sdk, boolean forceDuck, int testUid) { + String attributionTag, int flags, int sdk, boolean forceDuck, int testUid, + boolean permissionOverridesCheck) { new MediaMetrics.Item(mMetricsId) .setUid(Binder.getCallingUid()) .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName) @@ -1126,10 +1129,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } - if ((flags != AudioManager.AUDIOFOCUS_FLAG_TEST) - // note we're using the real uid for appOp evaluation - && (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), - callingPackageName, attributionTag, null) != AppOpsManager.MODE_ALLOWED)) { + final int res = mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), + callingPackageName, attributionTag, null); + if (!permissionOverridesCheck && res != AppOpsManager.MODE_ALLOWED) { return AudioManager.AUDIOFOCUS_REQUEST_FAILED; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4bb8e1913199..bc169ca40117 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2901,16 +2901,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void clearClientSessionsLocked() { if (getCurMethodLocked() != null) { - // TODO(b/322816970): Replace this with lambda. - mClientController.forAllClients(new Consumer<ClientState>() { - - @GuardedBy("ImfLock.class") - @Override - public void accept(ClientState c) { - clearClientSessionLocked(c); - clearClientSessionForAccessibilityLocked(c); - } - }); + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c -> { + clearClientSessionLocked(c); + clearClientSessionForAccessibilityLocked(c); + }; + mClientController.forAllClients(clearClientSession); finishSessionLocked(mEnabledSession); for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) { @@ -4653,15 +4649,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub super.startImeTrace_enforcePermission(); ImeTracing.getInstance().startTrace(null /* printwriter */); synchronized (ImfLock.class) { - // TODO(b/322816970): Replace this with lambda. - mClientController.forAllClients(new Consumer<ClientState>() { - - @GuardedBy("ImfLock.class") - @Override - public void accept(ClientState c) { - c.mClient.setImeTraceEnabled(true /* enabled */); - } - }); + mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(true /* enabled */)); } } @@ -4673,15 +4661,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ImeTracing.getInstance().stopTrace(null /* printwriter */); synchronized (ImfLock.class) { - // TODO(b/322816970): Replace this with lambda. - mClientController.forAllClients(new Consumer<ClientState>() { - - @GuardedBy("ImfLock.class") - @Override - public void accept(ClientState c) { - c.mClient.setImeTraceEnabled(false /* enabled */); - } - }); + mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(false /* enabled */)); } } @@ -5916,15 +5896,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // We only have sessions when we bound to an input method. Remove this session // from all clients. if (getCurMethodLocked() != null) { - // TODO(b/322816970): Replace this with lambda. - mClientController.forAllClients(new Consumer<ClientState>() { + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = + c -> clearClientSessionForAccessibilityLocked(c, + accessibilityConnectionId); + mClientController.forAllClients(clearClientSession); - @GuardedBy("ImfLock.class") - @Override - public void accept(ClientState c) { - clearClientSessionForAccessibilityLocked(c, accessibilityConnectionId); - } - }); AccessibilitySessionState session = mEnabledAccessibilitySessions.get( accessibilityConnectionId); if (session != null) { @@ -6126,24 +6103,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // Dump ClientController#mClients p.println(" ClientStates:"); - // TODO(b/322816970): Replace this with lambda. - mClientController.forAllClients(new Consumer<ClientState>() { + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> { + p.println(" " + c + ":"); + p.println(" client=" + c.mClient); + p.println(" fallbackInputConnection=" + + c.mFallbackInputConnection); + p.println(" sessionRequested=" + + c.mSessionRequested); + p.println( + " sessionRequestedForAccessibility=" + + c.mSessionRequestedForAccessibility); + p.println(" curSession=" + c.mCurSession); + }; + mClientController.forAllClients(clientControllerDump); - @GuardedBy("ImfLock.class") - @Override - public void accept(ClientState c) { - p.println(" " + c + ":"); - p.println(" client=" + c.mClient); - p.println(" fallbackInputConnection=" - + c.mFallbackInputConnection); - p.println(" sessionRequested=" - + c.mSessionRequested); - p.println( - " sessionRequestedForAccessibility=" - + c.mSessionRequestedForAccessibility); - p.println(" curSession=" + c.mCurSession); - } - }); p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java deleted file mode 100644 index ac42646499a3..000000000000 --- a/services/core/java/com/android/server/location/GeocoderProxy.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2010 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.location; - -import android.annotation.Nullable; -import android.content.Context; -import android.location.GeocoderParams; -import android.location.IGeocodeListener; -import android.location.IGeocodeProvider; -import android.os.IBinder; -import android.os.RemoteException; - -import com.android.server.servicewatcher.CurrentUserServiceSupplier; -import com.android.server.servicewatcher.ServiceWatcher; - -import java.util.Collections; - -/** - * Proxy for IGeocodeProvider implementations. - * - * @hide - */ -public class GeocoderProxy { - - private static final String SERVICE_ACTION = "com.android.location.service.GeocodeProvider"; - - /** - * Creates and registers this proxy. If no suitable service is available for the proxy, returns - * null. - */ - @Nullable - public static GeocoderProxy createAndRegister(Context context) { - GeocoderProxy proxy = new GeocoderProxy(context); - if (proxy.register()) { - return proxy; - } else { - return null; - } - } - - private final ServiceWatcher mServiceWatcher; - - private GeocoderProxy(Context context) { - mServiceWatcher = ServiceWatcher.create(context, "GeocoderProxy", - CurrentUserServiceSupplier.createFromConfig(context, SERVICE_ACTION, - com.android.internal.R.bool.config_enableGeocoderOverlay, - com.android.internal.R.string.config_geocoderProviderPackageName), - null); - } - - private boolean register() { - boolean resolves = mServiceWatcher.checkServiceResolves(); - if (resolves) { - mServiceWatcher.register(); - } - return resolves; - } - - /** - * Geocodes stuff. - */ - public void getFromLocation(double latitude, double longitude, int maxResults, - GeocoderParams params, IGeocodeListener listener) { - mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() { - @Override - public void run(IBinder binder) throws RemoteException { - IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder); - provider.getFromLocation(latitude, longitude, maxResults, params, listener); - } - - @Override - public void onError(Throwable t) { - try { - listener.onResults(t.toString(), Collections.emptyList()); - } catch (RemoteException e) { - // ignore - } - } - }); - } - - /** - * Geocodes stuff. - */ - public void getFromLocationName(String locationName, - double lowerLeftLatitude, double lowerLeftLongitude, - double upperRightLatitude, double upperRightLongitude, int maxResults, - GeocoderParams params, IGeocodeListener listener) { - mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() { - @Override - public void run(IBinder binder) throws RemoteException { - IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder); - provider.getFromLocationName(locationName, lowerLeftLatitude, - lowerLeftLongitude, upperRightLatitude, upperRightLongitude, - maxResults, params, listener); - } - - @Override - public void onError(Throwable t) { - try { - listener.onResults(t.toString(), Collections.emptyList()); - } catch (RemoteException e) { - // ignore - } - } - }); - } -} diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 323cdc54edae..c5f38553ed81 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -52,13 +52,11 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.location.Criteria; -import android.location.GeocoderParams; import android.location.Geofence; import android.location.GnssAntennaInfo; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementRequest; -import android.location.IGeocodeListener; import android.location.IGnssAntennaInfoListener; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; @@ -75,8 +73,11 @@ import android.location.LocationManagerInternal.LocationPackageTagsListener; import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; +import android.location.provider.ForwardGeocodeRequest; +import android.location.provider.IGeocodeCallback; import android.location.provider.IProviderRequestListener; import android.location.provider.ProviderProperties; +import android.location.provider.ReverseGeocodeRequest; import android.location.util.identity.CallerIdentity; import android.os.Binder; import android.os.Build; @@ -141,6 +142,7 @@ import com.android.server.location.provider.MockLocationProvider; import com.android.server.location.provider.PassiveLocationProvider; import com.android.server.location.provider.PassiveLocationProviderManager; import com.android.server.location.provider.StationaryThrottlingLocationProvider; +import com.android.server.location.provider.proxy.ProxyGeocodeProvider; import com.android.server.location.provider.proxy.ProxyLocationProvider; import com.android.server.location.settings.LocationSettings; import com.android.server.location.settings.LocationUserSettings; @@ -253,7 +255,7 @@ public class LocationManagerService extends ILocationManager.Stub implements private final GeofenceManager mGeofenceManager; private volatile @Nullable GnssManagerService mGnssManagerService = null; - private GeocoderProxy mGeocodeProvider; + private ProxyGeocodeProvider mGeocodeProvider; private final Object mDeprecatedGnssBatchingLock = new Object(); @GuardedBy("mDeprecatedGnssBatchingLock") @@ -493,7 +495,7 @@ public class LocationManagerService extends ILocationManager.Stub implements } // bind to geocoder provider - mGeocodeProvider = GeocoderProxy.createAndRegister(mContext); + mGeocodeProvider = ProxyGeocodeProvider.createAndRegister(mContext); if (mGeocodeProvider == null) { Log.e(TAG, "no geocoder provider found"); } @@ -1363,23 +1365,22 @@ public class LocationManagerService extends ILocationManager.Stub implements } @Override - public boolean geocoderIsPresent() { + public boolean isGeocodeAvailable() { return mGeocodeProvider != null; } @Override - public void getFromLocation(double latitude, double longitude, int maxResults, - GeocoderParams params, IGeocodeListener listener) { - // validate identity - CallerIdentity identity = CallerIdentity.fromBinder(mContext, params.getClientPackage(), - params.getClientAttributionTag()); - Preconditions.checkArgument(identity.getUid() == params.getClientUid()); + public void reverseGeocode(ReverseGeocodeRequest request, IGeocodeCallback callback) { + CallerIdentity identity = + CallerIdentity.fromBinder( + mContext, request.getCallingPackage(), request.getCallingAttributionTag()); + Preconditions.checkArgument(identity.getUid() == request.getCallingUid()); if (mGeocodeProvider != null) { - mGeocodeProvider.getFromLocation(latitude, longitude, maxResults, params, listener); + mGeocodeProvider.reverseGeocode(request, callback); } else { try { - listener.onResults(null, Collections.emptyList()); + callback.onError(null); } catch (RemoteException e) { // ignore } @@ -1387,22 +1388,17 @@ public class LocationManagerService extends ILocationManager.Stub implements } @Override - public void getFromLocationName(String locationName, - double lowerLeftLatitude, double lowerLeftLongitude, - double upperRightLatitude, double upperRightLongitude, int maxResults, - GeocoderParams params, IGeocodeListener listener) { - // validate identity - CallerIdentity identity = CallerIdentity.fromBinder(mContext, params.getClientPackage(), - params.getClientAttributionTag()); - Preconditions.checkArgument(identity.getUid() == params.getClientUid()); + public void forwardGeocode(ForwardGeocodeRequest request, IGeocodeCallback callback) { + CallerIdentity identity = + CallerIdentity.fromBinder( + mContext, request.getCallingPackage(), request.getCallingAttributionTag()); + Preconditions.checkArgument(identity.getUid() == request.getCallingUid()); if (mGeocodeProvider != null) { - mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude, - lowerLeftLongitude, upperRightLatitude, upperRightLongitude, - maxResults, params, listener); + mGeocodeProvider.forwardGeocode(request, callback); } else { try { - listener.onResults(null, Collections.emptyList()); + callback.onError(null); } catch (RemoteException e) { // ignore } diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyGeocodeProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyGeocodeProvider.java new file mode 100644 index 000000000000..ac945f1d2921 --- /dev/null +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyGeocodeProvider.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2010 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.location.provider.proxy; + +import android.annotation.Nullable; +import android.content.Context; +import android.location.provider.ForwardGeocodeRequest; +import android.location.provider.GeocodeProviderBase; +import android.location.provider.IGeocodeCallback; +import android.location.provider.IGeocodeProvider; +import android.location.provider.ReverseGeocodeRequest; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.server.servicewatcher.CurrentUserServiceSupplier; +import com.android.server.servicewatcher.ServiceWatcher; + +/** Proxy for IGeocodeProvider implementations. */ +public class ProxyGeocodeProvider { + + /** + * Creates and registers this proxy. If no suitable service is available for the proxy, returns + * null. + */ + @Nullable + public static ProxyGeocodeProvider createAndRegister(Context context) { + ProxyGeocodeProvider proxy = new ProxyGeocodeProvider(context); + if (proxy.register()) { + return proxy; + } else { + return null; + } + } + + private final ServiceWatcher mServiceWatcher; + + private ProxyGeocodeProvider(Context context) { + mServiceWatcher = + ServiceWatcher.create( + context, + "GeocoderProxy", + CurrentUserServiceSupplier.createFromConfig( + context, + GeocodeProviderBase.ACTION_GEOCODE_PROVIDER, + com.android.internal.R.bool.config_enableGeocoderOverlay, + com.android.internal.R.string.config_geocoderProviderPackageName), + null); + } + + private boolean register() { + boolean resolves = mServiceWatcher.checkServiceResolves(); + if (resolves) { + mServiceWatcher.register(); + } + return resolves; + } + + /** Reverse geocodes. */ + public void reverseGeocode(ReverseGeocodeRequest request, IGeocodeCallback callback) { + mServiceWatcher.runOnBinder( + new ServiceWatcher.BinderOperation() { + @Override + public void run(IBinder binder) throws RemoteException { + IGeocodeProvider.Stub.asInterface(binder).reverseGeocode(request, callback); + } + + @Override + public void onError(Throwable t) { + try { + callback.onError(t.toString()); + } catch (RemoteException e) { + // ignore + } + } + }); + } + + /** Forward geocodes. */ + public void forwardGeocode(ForwardGeocodeRequest request, IGeocodeCallback callback) { + mServiceWatcher.runOnBinder( + new ServiceWatcher.BinderOperation() { + @Override + public void run(IBinder binder) throws RemoteException { + IGeocodeProvider.Stub.asInterface(binder).forwardGeocode(request, callback); + } + + @Override + public void onError(Throwable t) { + try { + callback.onError(t.toString()); + } catch (RemoteException e) { + // ignore + } + } + }); + } +} diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 7fb3e001c4c3..9a76ebd148aa 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -18,6 +18,7 @@ package com.android.server.locksettings; import static android.security.Flags.reportPrimaryAuthAttempts; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; +import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS; import static android.Manifest.permission.SET_INITIAL_LOCK; @@ -27,6 +28,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRY import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE; import static android.content.Context.KEYGUARD_SERVICE; +import static android.content.Intent.ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; @@ -1201,8 +1203,9 @@ public class LockSettingsService extends ILockSettings.Stub { final boolean inSetupWizard = Settings.Secure.getIntForUser(cr, Settings.Secure.USER_SETUP_COMPLETE, 0, mainUserId) == 0; - final boolean secureFrp = Settings.Global.getInt(cr, - Settings.Global.SECURE_FRP_MODE, 0) == 1; + final boolean secureFrp = android.security.Flags.frpEnforcement() + ? mStorage.isFactoryResetProtectionActive() + : (Settings.Global.getInt(cr, Settings.Global.SECURE_FRP_MODE, 0) == 1); if (inSetupWizard && secureFrp) { throw new SecurityException("Cannot change credential in SUW while factory reset" @@ -2332,8 +2335,13 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSpManager) { if (isSpecialUserId(userId)) { - return mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(), + response = mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(), credential, progressCallback); + if (android.security.Flags.frpEnforcement() && response.isMatched() + && userId == USER_FRP) { + mStorage.deactivateFactoryResetProtectionWithoutSecret(); + } + return response; } long protectorId = getCurrentLskfBasedProtectorId(userId); @@ -3054,6 +3062,7 @@ public class LockSettingsService extends ILockSettings.Stub { setCurrentLskfBasedProtectorId(newProtectorId, userId); LockPatternUtils.invalidateCredentialTypeCache(); synchronizeUnifiedChallengeForProfiles(userId, profilePasswords); + sendMainUserCredentialChangedNotificationIfNeeded(userId); setUserPasswordMetrics(credential, userId); mUnifiedProfilePasswordCache.removePassword(userId); @@ -3071,6 +3080,24 @@ public class LockSettingsService extends ILockSettings.Stub { return newProtectorId; } + private void sendMainUserCredentialChangedNotificationIfNeeded(int userId) { + if (!android.security.Flags.frpEnforcement()) { + return; + } + + if (userId != mInjector.getUserManagerInternal().getMainUserId()) { + return; + } + + sendBroadcast(new Intent(ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED), + UserHandle.of(userId), CONFIGURE_FACTORY_RESET_PROTECTION); + } + + @VisibleForTesting + void sendBroadcast(Intent intent, UserHandle userHandle, String permission) { + mContext.sendBroadcastAsUser(intent, userHandle, permission, /* options */ null); + } + private void removeBiometricsForUser(int userId) { removeAllFingerprintForUser(userId); removeAllFaceForUser(userId); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 6d123ccebc7c..158d444bcff2 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -34,6 +34,7 @@ import android.os.Environment; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.service.persistentdata.PersistentDataBlockManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.AtomicFile; @@ -587,6 +588,10 @@ class LockSettingsStorage { return mPersistentDataBlockManagerInternal; } + /** + * Writes main user credential handle to the persistent data block, to enable factory reset + * protection to be deactivated with the credential. + */ public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi, byte[] payload) { PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager(); @@ -610,6 +615,31 @@ class LockSettingsStorage { } } + public void deactivateFactoryResetProtectionWithoutSecret() { + PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager(); + if (persistentDataBlock != null) { + persistentDataBlock.deactivateFactoryResetProtectionWithoutSecret(); + } else { + Slog.wtf(TAG, "Failed to get PersistentDataBlockManagerInternal"); + } + } + + public boolean isFactoryResetProtectionActive() { + PersistentDataBlockManager persistentDataBlockManager = + mContext.getSystemService(PersistentDataBlockManager.class); + if (persistentDataBlockManager != null) { + return persistentDataBlockManager.isFactoryResetProtectionActive(); + } else { + Slog.wtf(TAG, "Failed to get PersistentDataBlockManager"); + // This should never happen, but in the event it does, let's not block the user. This + // may be the wrong call, since if an attacker can find a way to prevent us from + // getting the PersistentDataBlockManager they can defeat FRP, but if they can block + // access to PersistentDataBlockManager they must have compromised the system and we've + // probably already lost this battle. + return false; + } + } + /** * Provides a concrete data structure to represent the minimal information from * a user's LSKF-based SP protector that is needed to verify the user's LSKF, diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index f9ffb1cebac4..b5c51af47009 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -26,6 +26,8 @@ import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.OBSERVE_NETWORK_POLICY; import static android.Manifest.permission.READ_PHONE_STATE; import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; +import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; +import static android.app.ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; import static android.app.ActivityManager.isProcStateConsideredInteraction; import static android.app.ActivityManager.printCapabilitiesSummary; @@ -85,8 +87,10 @@ import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_ import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM; import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP; +import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; +import static android.net.NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; @@ -97,6 +101,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.RULE_REJECT_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED; +import static android.net.NetworkPolicyManager.TOP_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.allowedReasonsToString; import static android.net.NetworkPolicyManager.blockedReasonsToString; import static android.net.NetworkPolicyManager.isProcStateAllowedNetworkWhileBackground; @@ -469,7 +474,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ private static final int MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS = 24; - private static final int UID_MSG_STATE_CHANGED = 100; + @VisibleForTesting + static final int UID_MSG_STATE_CHANGED = 100; private static final int UID_MSG_GONE = 101; private static final String PROP_SUB_PLAN_OWNER = "persist.sys.sub_plan_owner"; @@ -1075,8 +1081,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE; - // TODO (b/319728914): Filter out the unnecessary changes when using no cutpoint. - mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes, cutpoint, "android"); mNetworkManager.registerObserver(mAlertObserver); @@ -1185,6 +1189,51 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final private IUidObserver mUidObserver = new UidObserver() { + + /** + * Returns whether the uid state change information is relevant for the service. If the + * state information does not lead to any change in the network rules, it can safely be + * ignored. + */ + @GuardedBy("mUidStateCallbackInfos") + private boolean isUidStateChangeRelevant(UidStateCallbackInfo previousInfo, + int newProcState, long newProcStateSeq, int newCapability) { + if (previousInfo.procStateSeq == -1) { + // No previous record. Always process the first state change callback. + return true; + } + if (newProcStateSeq <= previousInfo.procStateSeq) { + // Stale callback. Ignore. + return false; + } + final int previousProcState = previousInfo.procState; + if (mBackgroundNetworkRestricted && (previousProcState >= BACKGROUND_THRESHOLD_STATE) + != (newProcState >= BACKGROUND_THRESHOLD_STATE)) { + // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: Network rules for the + // BACKGROUND chain may change. + return true; + } + if ((previousProcState <= TOP_THRESHOLD_STATE) + != (newProcState <= TOP_THRESHOLD_STATE)) { + // Proc-state change crossed TOP_THRESHOLD_STATE: Network rules for the + // LOW_POWER_STANDBY chain may change. + return true; + } + if ((previousProcState <= FOREGROUND_THRESHOLD_STATE) + != (newProcState <= FOREGROUND_THRESHOLD_STATE)) { + // Proc-state change crossed FOREGROUND_THRESHOLD_STATE: Network rules for many + // different chains may change. + return true; + } + final int networkCapabilities = PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK + | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; + if ((previousInfo.capability & networkCapabilities) + != (newCapability & networkCapabilities)) { + return true; + } + return false; + } + @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, @ProcessCapability int capability) { synchronized (mUidStateCallbackInfos) { @@ -1193,13 +1242,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { callbackInfo = new UidStateCallbackInfo(); mUidStateCallbackInfos.put(uid, callbackInfo); } - if (callbackInfo.procStateSeq == -1 || procStateSeq > callbackInfo.procStateSeq) { + if (isUidStateChangeRelevant(callbackInfo, procState, procStateSeq, capability)) { callbackInfo.update(uid, procState, procStateSeq, capability); - } - if (!callbackInfo.isPending) { - mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, callbackInfo) - .sendToTarget(); - callbackInfo.isPending = true; + if (!callbackInfo.isPending) { + mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, callbackInfo) + .sendToTarget(); + callbackInfo.isPending = true; + } } } } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 722654a9102c..53244f9e5ecf 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -50,15 +50,6 @@ flag { } flag { - name: "sensitive_notification_app_protection" - namespace: "systemui" - description: "This flag controls the sensitive notification app protections while screen sharing" - bug: "312784351" - # Referenced in WM where WM starts before DeviceConfig - is_fixed_read_only: true -} - -flag { name: "notification_reduce_messagequeue_usage" namespace: "systemui" description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler" diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index d987622676b5..872952299055 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -1143,10 +1143,9 @@ public final class OverlayManagerService extends SystemService { }; private static final class PackageManagerHelperImpl implements PackageManagerHelper { - private static final class PackageStateUsers { + private static class PackageStateUsers { private PackageState mPackageState; - private Boolean mDefinesOverlayable = null; - private final ArraySet<Integer> mInstalledUsers = new ArraySet<>(); + private final Set<Integer> mInstalledUsers = new ArraySet<>(); private PackageStateUsers(@NonNull PackageState packageState) { this.mPackageState = packageState; } @@ -1196,11 +1195,13 @@ public final class OverlayManagerService extends SystemService { return userPackages; } - private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName, + @Override + @Nullable + public PackageState getPackageStateForUser(@NonNull final String packageName, final int userId) { final PackageStateUsers pkg = mCache.get(packageName); if (pkg != null && pkg.mInstalledUsers.contains(userId)) { - return pkg; + return pkg.mPackageState; } try { if (!mPackageManager.isPackageAvailable(packageName, userId)) { @@ -1214,14 +1215,8 @@ public final class OverlayManagerService extends SystemService { return addPackageUser(packageName, userId); } - @Override - public PackageState getPackageStateForUser(@NonNull final String packageName, - final int userId) { - final PackageStateUsers pkg = getRawPackageStateForUser(packageName, userId); - return pkg != null ? pkg.mPackageState : null; - } - - private PackageStateUsers addPackageUser(@NonNull final String packageName, + @NonNull + private PackageState addPackageUser(@NonNull final String packageName, final int user) { final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName); if (pkg == null) { @@ -1233,20 +1228,20 @@ public final class OverlayManagerService extends SystemService { } @NonNull - private PackageStateUsers addPackageUser(@NonNull final PackageState pkg, + private PackageState addPackageUser(@NonNull final PackageState pkg, final int user) { PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName()); if (pkgUsers == null) { pkgUsers = new PackageStateUsers(pkg); mCache.put(pkg.getPackageName(), pkgUsers); - } else if (pkgUsers.mPackageState != pkg) { + } else { pkgUsers.mPackageState = pkg; - pkgUsers.mDefinesOverlayable = null; } pkgUsers.mInstalledUsers.add(user); - return pkgUsers; + return pkgUsers.mPackageState; } + @NonNull private void removePackageUser(@NonNull final String packageName, final int user) { final PackageStateUsers pkgUsers = mCache.get(packageName); @@ -1264,15 +1259,15 @@ public final class OverlayManagerService extends SystemService { } } + @Nullable public PackageState onPackageAdded(@NonNull final String packageName, final int userId) { - final var pu = addPackageUser(packageName, userId); - return pu != null ? pu.mPackageState : null; + return addPackageUser(packageName, userId); } + @Nullable public PackageState onPackageUpdated(@NonNull final String packageName, final int userId) { - final var pu = addPackageUser(packageName, userId); - return pu != null ? pu.mPackageState : null; + return addPackageUser(packageName, userId); } public void onPackageRemoved(@NonNull final String packageName, final int userId) { @@ -1312,30 +1307,22 @@ public final class OverlayManagerService extends SystemService { return (pkgs.length == 0) ? null : pkgs[0]; } + @Nullable @Override public OverlayableInfo getOverlayableForTarget(@NonNull String packageName, @NonNull String targetOverlayableName, int userId) throws IOException { - final var psu = getRawPackageStateForUser(packageName, userId); - final var pkg = (psu == null || psu.mPackageState == null) - ? null : psu.mPackageState.getAndroidPackage(); + var packageState = getPackageStateForUser(packageName, userId); + var pkg = packageState == null ? null : packageState.getAndroidPackage(); if (pkg == null) { throw new IOException("Unable to get target package"); } - if (Boolean.FALSE.equals(psu.mDefinesOverlayable)) { - return null; - } - ApkAssets apkAssets = null; try { apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(), ApkAssets.PROPERTY_ONLY_OVERLAYABLES); - if (psu.mDefinesOverlayable == null) { - psu.mDefinesOverlayable = apkAssets.definesOverlayable(); - } - return Boolean.FALSE.equals(psu.mDefinesOverlayable) - ? null : apkAssets.getOverlayableInfo(targetOverlayableName); + return apkAssets.getOverlayableInfo(targetOverlayableName); } finally { if (apkAssets != null) { try { @@ -1349,29 +1336,24 @@ public final class OverlayManagerService extends SystemService { @Override public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) throws IOException { - final var psu = getRawPackageStateForUser(targetPackageName, userId); - var pkg = (psu == null || psu.mPackageState == null) - ? null : psu.mPackageState.getAndroidPackage(); + var packageState = getPackageStateForUser(targetPackageName, userId); + var pkg = packageState == null ? null : packageState.getAndroidPackage(); if (pkg == null) { throw new IOException("Unable to get target package"); } - if (psu.mDefinesOverlayable == null) { - ApkAssets apkAssets = null; - try { - apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(), - ApkAssets.PROPERTY_ONLY_OVERLAYABLES); - psu.mDefinesOverlayable = apkAssets.definesOverlayable(); - } finally { - if (apkAssets != null) { - try { - apkAssets.close(); - } catch (Throwable ignored) { - } + ApkAssets apkAssets = null; + try { + apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath()); + return apkAssets.definesOverlayable(); + } finally { + if (apkAssets != null) { + try { + apkAssets.close(); + } catch (Throwable ignored) { } } } - return psu.mDefinesOverlayable; } @Override diff --git a/services/core/java/com/android/server/ondeviceintelligence/OWNERS b/services/core/java/com/android/server/ondeviceintelligence/OWNERS new file mode 100644 index 000000000000..09774f78d712 --- /dev/null +++ b/services/core/java/com/android/server/ondeviceintelligence/OWNERS @@ -0,0 +1 @@ +file:/core/java/android/app/ondeviceintelligence/OWNERS diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 295528e14ca7..f9d81127fd86 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1625,10 +1625,11 @@ public class LauncherAppsService extends SystemService { } @Override + @NonNull public List<String> getPreInstalledSystemPackages(UserHandle user) { if (!canAccessProfile(user.getIdentifier(), "Can't access preinstalled packages for another user")) { - return null; + return new ArrayList<>(); } final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java index 85d2df320fc9..bbce26c85549 100644 --- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java +++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java @@ -27,11 +27,14 @@ import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; import static com.android.server.pm.InstructionSets.getPreferredInstructionSet; import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.os.Build; import android.os.Environment; import android.os.FileUtils; +import android.os.SystemProperties; import android.os.Trace; import android.text.TextUtils; import android.util.ArraySet; @@ -50,9 +53,15 @@ import libcore.io.IoUtils; import java.io.File; import java.io.IOException; +import java.util.Set; final class PackageAbiHelperImpl implements PackageAbiHelper { + @Nullable + private static String[] sNativelySupported32BitAbis = null; + @Nullable + private static String[] sNativelySupported64BitAbis = null; + private static String calculateBundledApkRoot(final String codePathString) { final File codePath = new File(codePathString); final File codeRoot; @@ -122,13 +131,20 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { } } - private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet) throws - PackageManagerException { + private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet, + boolean forceMatch) throws PackageManagerException { if (copyRet < 0) { if (copyRet != PackageManager.NO_NATIVE_LIBRARIES && copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { throw new PackageManagerException(copyRet, message); } + + if (forceMatch && copyRet == PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { + throw new PackageManagerException( + PackageManager.INSTALL_FAILED_MULTI_ARCH_NOT_MATCH_ALL_NATIVE_ABIS, + "The multiArch app's native libs don't support all the natively" + + " supported ABIs of the device."); + } } } @@ -296,7 +312,40 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { return new Abis(primaryCpuAbi, secondaryCpuAbi); } + @NonNull + private static String[] getNativelySupportedAbis(@NonNull String[] supportedAbis) { + Set<String> nativelySupportedAbis = new ArraySet<>(); + for (int i = 0; i < supportedAbis.length; i++) { + final String currentAbi = supportedAbis[i]; + // In presence of a native bridge this means the Abi is emulated. + final String currentIsa = VMRuntime.getInstructionSet(currentAbi); + if (TextUtils.isEmpty(SystemProperties.get("ro.dalvik.vm.isa." + currentIsa))) { + nativelySupportedAbis.add(currentAbi); + } + } + return nativelySupportedAbis.toArray(new String[0]); + } + + private static String[] getNativelySupported32BitAbis() { + if (sNativelySupported32BitAbis != null) { + return sNativelySupported32BitAbis; + } + + sNativelySupported32BitAbis = getNativelySupportedAbis(Build.SUPPORTED_32_BIT_ABIS); + return sNativelySupported32BitAbis; + } + + private static String[] getNativelySupported64BitAbis() { + if (sNativelySupported64BitAbis != null) { + return sNativelySupported64BitAbis; + } + + sNativelySupported64BitAbis = getNativelySupportedAbis(Build.SUPPORTED_64_BIT_ABIS); + return sNativelySupported64BitAbis; + } + @Override + @SuppressWarnings("AndroidFrameworkCompatChange") // the check is before the apk is installed public Pair<Abis, NativeLibraryPaths> derivePackageAbi(AndroidPackage pkg, boolean isSystemApp, boolean isUpdatedSystemApp, String cpuAbiOverride, File appLib32InstallDir) throws PackageManagerException { @@ -334,18 +383,33 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { primaryCpuAbi = null; secondaryCpuAbi = null; if (pkg.isMultiArch()) { + // Force the match for these cases + // 1. pkg.getTargetSdkVersion >= Build.VERSION_CODES.VANILLA_ICE_CREAM + // 2. cpuAbiOverride is null. If it is non-null, it is set via shell for testing + final boolean forceMatch = Flags.forceMultiArchNativeLibsMatch() + && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.VANILLA_ICE_CREAM + && cpuAbiOverride == null; + + String[] supported32BitAbis = forceMatch ? getNativelySupported32BitAbis() + : Build.SUPPORTED_32_BIT_ABIS; + String[] supported64BitAbis = forceMatch ? getNativelySupported64BitAbis() + : Build.SUPPORTED_64_BIT_ABIS; + + final boolean systemSupports32BitAbi = supported32BitAbis.length > 0; + final boolean systemSupports64BitAbi = supported64BitAbis.length > 0; + int abi32 = PackageManager.NO_NATIVE_LIBRARIES; int abi64 = PackageManager.NO_NATIVE_LIBRARIES; - if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { + if (systemSupports32BitAbi) { if (extractLibs) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); abi32 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, - nativeLibraryRoot, Build.SUPPORTED_32_BIT_ABIS, + nativeLibraryRoot, supported32BitAbis, useIsaSpecificSubdirs, onIncremental); } else { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi"); abi32 = NativeLibraryHelper.findSupportedAbi( - handle, Build.SUPPORTED_32_BIT_ABIS); + handle, supported32BitAbis); } Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -357,24 +421,26 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { } maybeThrowExceptionForMultiArchCopy( - "Error unpackaging 32 bit native libs for multiarch app.", abi32); + "Error unpackaging 32 bit native libs for multiarch app.", abi32, + forceMatch && systemSupports32BitAbi); - if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { + if (systemSupports64BitAbi) { if (extractLibs) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyNativeBinaries"); abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle, - nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS, + nativeLibraryRoot, supported64BitAbis, useIsaSpecificSubdirs, onIncremental); } else { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "findSupportedAbi"); abi64 = NativeLibraryHelper.findSupportedAbi( - handle, Build.SUPPORTED_64_BIT_ABIS); + handle, supported64BitAbis); } Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } maybeThrowExceptionForMultiArchCopy( - "Error unpackaging 64 bit native libs for multiarch app.", abi64); + "Error unpackaging 64 bit native libs for multiarch app.", abi64, + forceMatch && systemSupports64BitAbi); if (abi64 >= 0) { // Shared library native libs should be in the APK zip aligned @@ -382,11 +448,11 @@ final class PackageAbiHelperImpl implements PackageAbiHelper { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Shared library native lib extraction not supported"); } - primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64]; + primaryCpuAbi = supported64BitAbis[abi64]; } if (abi32 >= 0) { - final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32]; + final String abi = supported32BitAbis[abi32]; if (abi64 >= 0) { if (pkg.is32BitAbiPreferred()) { secondaryCpuAbi = primaryCpuAbi; diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index a9d2858fd36e..c2f74a8895cb 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -232,7 +232,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_UNMUTE_MICROPHONE, UserManager.DISALLOW_UNMUTE_DEVICE, UserManager.DISALLOW_CAMERA, - UserManager.DISALLOW_ASSIST_CONTENT + UserManager.DISALLOW_ASSIST_CONTENT, + UserManager.DISALLOW_CONFIG_DEFAULT_APPS ); /** @@ -288,7 +289,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_SMS, UserManager.DISALLOW_USB_FILE_TRANSFER, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, - UserManager.DISALLOW_UNMUTE_MICROPHONE + UserManager.DISALLOW_UNMUTE_MICROPHONE, + UserManager.DISALLOW_CONFIG_DEFAULT_APPS ); /** diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index f44fcf002a62..21e2bf2e76e5 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -42,6 +42,7 @@ import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.AppOpsManager.AttributionFlags; import android.app.IActivityManager; +import android.companion.virtual.VirtualDeviceManager; import android.content.AttributionSource; import android.content.AttributionSourceState; import android.content.Context; @@ -78,6 +79,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.TriFunction; import com.android.server.LocalServices; +import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.pm.UserManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider; import com.android.server.pm.pkg.AndroidPackage; @@ -135,6 +137,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Nullable private HotwordDetectionServiceProvider mHotwordDetectionServiceProvider; + @Nullable + private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal; + PermissionManagerService(@NonNull Context context, @NonNull ArrayMap<String, FeatureInfo> availableFeatures) { // The package info cache is the cache for package and permission information. @@ -146,6 +151,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { mContext = context; mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class); mAppOpsManager = context.getSystemService(AppOpsManager.class); + mVirtualDeviceManagerInternal = + LocalServices.getService(VirtualDeviceManagerInternal.class); mAttributionSourceRegistry = new AttributionSourceRegistry(context); @@ -246,16 +253,30 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PackageManager.PERMISSION_DENIED; } + String persistentDeviceId = getPersistentDeviceId(deviceId); + final CheckPermissionDelegate checkPermissionDelegate; synchronized (mLock) { checkPermissionDelegate = mCheckPermissionDelegate; } - - if (checkPermissionDelegate == null) { - return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName, deviceId); + if (checkPermissionDelegate == null) { + return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName, + persistentDeviceId); } return checkPermissionDelegate.checkUidPermission(uid, permissionName, - deviceId, mPermissionManagerServiceImpl::checkUidPermission); + persistentDeviceId, mPermissionManagerServiceImpl::checkUidPermission); + } + + private String getPersistentDeviceId(int deviceId) { + if (deviceId == Context.DEVICE_ID_DEFAULT) { + return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; + } + + if (mVirtualDeviceManagerInternal == null) { + mVirtualDeviceManagerInternal = + LocalServices.getService(VirtualDeviceManagerInternal.class); + } + return mVirtualDeviceManagerInternal.getPersistentIdForDevice(deviceId); } @Override @@ -608,15 +629,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName, int deviceId, int userId) { + String persistentDeviceId = getPersistentDeviceId(deviceId); return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName, - permissionName, deviceId, userId); + permissionName, persistentDeviceId, userId); } @Override public boolean isPermissionRevokedByPolicy(String packageName, String permissionName, int deviceId, int userId) { + String persistentDeviceId = getPersistentDeviceId(deviceId); return mPermissionManagerServiceImpl.isPermissionRevokedByPolicy(packageName, - permissionName, deviceId, userId); + permissionName, persistentDeviceId, userId); } @Override @@ -914,13 +937,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { * * @param uid the UID to be checked * @param permissionName the name of the permission to be checked - * @param deviceId The device ID + * @param persistentDeviceId The persistent device ID * @param superImpl the original implementation that can be delegated to * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise */ - int checkUidPermission(int uid, @NonNull String permissionName, int deviceId, - TriFunction<Integer, String, Integer, Integer> superImpl); + int checkUidPermission(int uid, @NonNull String permissionName, String persistentDeviceId, + TriFunction<Integer, String, String, Integer> superImpl); /** * @return list of delegated permissions @@ -965,17 +988,18 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override - public int checkUidPermission(int uid, @NonNull String permissionName, int deviceId, - @NonNull TriFunction<Integer, String, Integer, Integer> superImpl) { + public int checkUidPermission(int uid, @NonNull String permissionName, + String persistentDeviceId, + @NonNull TriFunction<Integer, String, String, Integer> superImpl) { if (uid == mDelegatedUid && isDelegatedPermission(permissionName)) { final long identity = Binder.clearCallingIdentity(); try { - return superImpl.apply(Process.SHELL_UID, permissionName, deviceId); + return superImpl.apply(Process.SHELL_UID, permissionName, persistentDeviceId); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(uid, permissionName, deviceId); + return superImpl.apply(uid, permissionName, persistentDeviceId); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index c5b637d8b48b..70913c351eeb 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -682,7 +682,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public int getPermissionFlags(String packageName, String permName, String persistentDeviceId, + public int getPermissionFlags(String packageName, String permName, String deviceId, int userId) { final int callingUid = Binder.getCallingUid(); return getPermissionFlagsInternal(packageName, permName, callingUid, userId); @@ -726,8 +726,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, String persistentDeviceId, - int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, String deviceId, int userId) { final int callingUid = Binder.getCallingUid(); boolean overridePolicy = false; @@ -917,8 +916,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public int checkPermission(String pkgName, String permName, String persistentDeviceId, - int userId) { + public int checkPermission(String pkgName, String permName, String deviceId, int userId) { if (!mUserManagerInt.exists(userId)) { return PackageManager.PERMISSION_DENIED; } @@ -985,11 +983,11 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } private int checkUidPermission(int uid, String permName) { - return checkUidPermission(uid, permName, Context.DEVICE_ID_DEFAULT); + return checkUidPermission(uid, permName, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); } @Override - public int checkUidPermission(int uid, String permName, int deviceId) { + public int checkUidPermission(int uid, String permName, String deviceId) { final int userId = UserHandle.getUserId(uid); if (!mUserManagerInt.exists(userId)) { return PackageManager.PERMISSION_DENIED; @@ -1001,7 +999,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @Override public Map<String, PermissionManager.PermissionState> getAllPermissionStates( - @NonNull String packageName, @NonNull String persistentDeviceId, int userId) { + @NonNull String packageName, @NonNull String deviceId, int userId) { throw new UnsupportedOperationException( "This method is supported in newer implementation only"); } @@ -1315,8 +1313,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void grantRuntimePermission(String packageName, String permName, - String persistentDeviceId, int userId) { + public void grantRuntimePermission(String packageName, String permName, String deviceId, + int userId) { final int callingUid = Binder.getCallingUid(); final boolean overridePolicy = checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) @@ -1489,12 +1487,13 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void revokeRuntimePermission(String packageName, String permName, - String persistentDeviceId, int userId, String reason) { + public void revokeRuntimePermission(String packageName, String permName, String deviceId, + int userId, String reason) { final int callingUid = Binder.getCallingUid(); final boolean overridePolicy = checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY, - Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED; + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) + == PackageManager.PERMISSION_GRANTED; revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId, reason, mDefaultPermissionCallback); @@ -1880,7 +1879,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, @UserIdInt int userId) { + String deviceId, @UserIdInt int userId) { final int callingUid = Binder.getCallingUid(); if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( @@ -1943,7 +1942,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + public boolean isPermissionRevokedByPolicy(String packageName, String permName, String deviceId, int userId) { if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 7c1042535973..47032ea2d6af 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -141,11 +141,11 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName the package name for which to get the flags * @param permName the permission for which to get the flags - * @param persistentDeviceId The device for which to get the flags + * @param deviceId The device for which to get the flags * @param userId the user for which to get permission flags * @return the permission flags */ - int getPermissionFlags(String packageName, String permName, String persistentDeviceId, + int getPermissionFlags(String packageName, String permName, String deviceId, @UserIdInt int userId); /** @@ -156,11 +156,11 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * @param permName The permission for which to update the flags * @param flagMask The flags which to replace * @param flagValues The flags with which to replace - * @param persistentDeviceId The device for which to update the permission flags + * @param deviceId The device for which to update the permission flags * @param userId The user for which to update the permission flags */ void updatePermissionFlags(String packageName, String permName, int flagMask, int flagValues, - boolean checkAdjustPolicyFlagPermission, String persistentDeviceId, + boolean checkAdjustPolicyFlagPermission, String deviceId, @UserIdInt int userId); /** @@ -295,12 +295,12 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName the package to which to grant the permission * @param permName the permission name to grant - * @param persistentDeviceId the device for which to grant the permission + * @param deviceId the device for which to grant the permission * @param userId the user for which to grant the permission * * @see #revokeRuntimePermission(String, String, String, int, String) */ - void grantRuntimePermission(String packageName, String permName, String persistentDeviceId, + void grantRuntimePermission(String packageName, String permName, String deviceId, @UserIdInt int userId); /** @@ -316,13 +316,13 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName the package from which to revoke the permission * @param permName the permission name to revoke - * @param persistentDeviceId the device for which to revoke the permission + * @param deviceId the device for which to revoke the permission * @param userId the user for which to revoke the permission * @param reason the reason for the revoke, or {@code null} for unspecified * * @see #grantRuntimePermission(String, String, String, int) */ - void revokeRuntimePermission(String packageName, String permName, String persistentDeviceId, + void revokeRuntimePermission(String packageName, String permName, String deviceId, @UserIdInt int userId, String reason); /** @@ -347,7 +347,7 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * @return whether you can show permission rationale UI */ boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, @UserIdInt int userId); + String deviceId, @UserIdInt int userId); /** * Checks whether a particular permission has been revoked for a package by policy. Typically, @@ -361,8 +361,8 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * @param userId the device for which you are checking the permission * @return whether the permission is restricted by policy */ - boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, - @UserIdInt int userId); + boolean isPermissionRevokedByPolicy(String packageName, String permName, + String deviceId, @UserIdInt int userId); /** * Get set of permissions that have been split into more granular or dependent permissions. @@ -389,11 +389,11 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param pkgName package name * @param permName permission name - * @param persistentDeviceId persistent device ID + * @param deviceId persistent device ID * @param userId user ID * @return permission result {@link PackageManager.PermissionResult} */ - int checkPermission(String pkgName, String permName, String persistentDeviceId, + int checkPermission(String pkgName, String permName, String deviceId, @UserIdInt int userId); /** @@ -401,23 +401,23 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param uid UID * @param permName permission name - * @param deviceId device ID + * @param deviceId persistent device ID * @return permission result {@link PackageManager.PermissionResult} */ - int checkUidPermission(int uid, String permName, int deviceId); + int checkUidPermission(int uid, String permName, String deviceId); /** * Gets the permission states for requested package, persistent device and user. * * @param packageName name of the package you are checking against - * @param persistentDeviceId id of the persistent device you are checking against + * @param deviceId id of the persistent device you are checking against * @param userId id of the user for which to get permission flags * @return mapping of all permission states keyed by their permission names * * @hide */ Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName, - @NonNull String persistentDeviceId, @UserIdInt int userId); + @NonNull String deviceId, @UserIdInt int userId); /** * Get all the package names requesting app op permissions. diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java index 91a778d49d61..c18f856594ed 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java @@ -121,24 +121,22 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public int getPermissionFlags(String packageName, String permName, String persistentDeviceId, + public int getPermissionFlags(String packageName, String permName, String deviceId, int userId) { Log.i(LOG_TAG, "getPermissionFlags(packageName = " + packageName + ", permName = " - + permName + ", persistentDeviceId = " + persistentDeviceId + ", userId = " + userId - + ")"); - return mService.getPermissionFlags(packageName, permName, persistentDeviceId, userId); + + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); + return mService.getPermissionFlags(packageName, permName, deviceId, userId); } @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, String persistentDeviceId, - int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, String deviceId, int userId) { Log.i(LOG_TAG, "updatePermissionFlags(packageName = " + packageName + ", permName = " + permName + ", flagMask = " + flagMask + ", flagValues = " + flagValues + ", checkAdjustPolicyFlagPermission = " + checkAdjustPolicyFlagPermission - + ", persistentDeviceId = " + persistentDeviceId + ", userId = " + userId + ")"); + + ", deviceId = " + deviceId + ", userId = " + userId + ")"); mService.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, persistentDeviceId, userId); + checkAdjustPolicyFlagPermission, deviceId, userId); } @Override @@ -186,21 +184,20 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public void grantRuntimePermission(String packageName, String permName, - String persistentDeviceId, int userId) { + public void grantRuntimePermission(String packageName, String permName, String deviceId, + int userId) { Log.i(LOG_TAG, "grantRuntimePermission(packageName = " + packageName + ", permName = " - + permName + ", persistentDeviceId = " + persistentDeviceId + ", userId = " + userId - + ")"); - mService.grantRuntimePermission(packageName, permName, persistentDeviceId, userId); + + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); + mService.grantRuntimePermission(packageName, permName, deviceId, userId); } @Override - public void revokeRuntimePermission(String packageName, String permName, - String persistentDeviceId, int userId, String reason) { + public void revokeRuntimePermission(String packageName, String permName, String deviceId, + int userId, String reason) { Log.i(LOG_TAG, "revokeRuntimePermission(packageName = " + packageName + ", permName = " - + permName + ", persistentDeviceId = " + persistentDeviceId + ", userId = " + userId - + ", reason = " + reason + ")"); - mService.revokeRuntimePermission(packageName, permName, persistentDeviceId, userId, reason); + + permName + ", deviceId = " + deviceId + ", userId = " + userId + ", reason = " + + reason + ")"); + mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); } @Override @@ -212,16 +209,16 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, int userId) { + String deviceId, int userId) { Log.i(LOG_TAG, "shouldShowRequestPermissionRationale(packageName = " + packageName - + ", permName = " + permName + ", deviceId = " + deviceId - + ", userId = " + userId + ")"); + + ", permName = " + permName + ", deviceId = " + deviceId + ", userId = " + + userId + ")"); return mService.shouldShowRequestPermissionRationale(packageName, permName, deviceId, userId); } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + public boolean isPermissionRevokedByPolicy(String packageName, String permName, String deviceId, int userId) { Log.i(LOG_TAG, "isPermissionRevokedByPolicy(packageName = " + packageName + ", permName = " + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); @@ -235,26 +232,27 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public int checkPermission(String pkgName, String permName, String persistentDeviceId, + public int checkPermission(String pkgName, String permName, String deviceId, int userId) { Log.i(LOG_TAG, "checkPermission(pkgName = " + pkgName + ", permName = " + permName - + ", persistentDeviceId = " + persistentDeviceId + ", userId = " + userId + ")"); - return mService.checkPermission(pkgName, permName, persistentDeviceId, userId); + + ", deviceId = " + deviceId + ", userId = " + userId + ")"); + return mService.checkPermission(pkgName, permName, deviceId, userId); } @Override - public int checkUidPermission(int uid, String permName, int deviceId) { - Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " - + permName + ", deviceId = " + deviceId + ")"); + public int checkUidPermission(int uid, String permName, String deviceId) { + Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName + + ", deviceId = " + deviceId + ")"); return mService.checkUidPermission(uid, permName, deviceId); } @Override public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName, - @NonNull String persistentDeviceId, int userId) { - Log.i(LOG_TAG, "getAllPermissionStates(packageName = " + packageName - + ", persistentDeviceId = " + persistentDeviceId + ", userId = " + userId + ")"); - return mService.getAllPermissionStates(packageName, persistentDeviceId, userId); + @NonNull String deviceId, int userId) { + Log.i(LOG_TAG, + "getAllPermissionStates(packageName = " + packageName + ", deviceId = " + deviceId + + ", userId = " + userId + ")"); + return mService.getAllPermissionStates(packageName, deviceId, userId); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java index 0a4ff0797c97..40139baf0e98 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java @@ -154,12 +154,10 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int getPermissionFlags(String packageName, String permName, String persistentDeviceId, + public int getPermissionFlags(String packageName, String permName, String deviceId, @UserIdInt int userId) { - int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, - persistentDeviceId, userId); - int newVal = mNewImplementation.getPermissionFlags(packageName, permName, - persistentDeviceId, userId); + int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, deviceId, userId); + int newVal = mNewImplementation.getPermissionFlags(packageName, permName, deviceId, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("getPermissionFlags"); @@ -169,12 +167,12 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, String persistentDeviceId, + int flagValues, boolean checkAdjustPolicyFlagPermission, String deviceId, @UserIdInt int userId) { mOldImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, persistentDeviceId, userId); + checkAdjustPolicyFlagPermission, deviceId, userId); mNewImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, persistentDeviceId, userId); + checkAdjustPolicyFlagPermission, deviceId, userId); } @Override @@ -239,21 +237,17 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public void grantRuntimePermission(String packageName, String permName, - String persistentDeviceId, @UserIdInt int userId) { - mOldImplementation.grantRuntimePermission(packageName, permName, persistentDeviceId, - userId); - mNewImplementation.grantRuntimePermission(packageName, permName, persistentDeviceId, - userId); + public void grantRuntimePermission(String packageName, String permName, String deviceId, + @UserIdInt int userId) { + mOldImplementation.grantRuntimePermission(packageName, permName, deviceId, userId); + mNewImplementation.grantRuntimePermission(packageName, permName, deviceId, userId); } @Override - public void revokeRuntimePermission(String packageName, String permName, - String persistentDeviceId, @UserIdInt int userId, String reason) { - mOldImplementation.revokeRuntimePermission(packageName, permName, persistentDeviceId, - userId, reason); - mNewImplementation.revokeRuntimePermission(packageName, permName, persistentDeviceId, - userId, reason); + public void revokeRuntimePermission(String packageName, String permName, String deviceId, + @UserIdInt int userId, String reason) { + mOldImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); + mNewImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); } @Override @@ -265,7 +259,7 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, @UserIdInt int userId) { + String deviceId, @UserIdInt int userId) { boolean oldVal = mOldImplementation.shouldShowRequestPermissionRationale(packageName, permName, deviceId, userId); boolean newVal = mNewImplementation.shouldShowRequestPermissionRationale(packageName, @@ -278,7 +272,7 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + public boolean isPermissionRevokedByPolicy(String packageName, String permName, String deviceId, @UserIdInt int userId) { boolean oldVal = mOldImplementation.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId); @@ -303,12 +297,9 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int checkPermission(String pkgName, String permName, String persistentDeviceId, - int userId) { - int oldVal = mOldImplementation.checkPermission(pkgName, permName, persistentDeviceId, - userId); - int newVal = mNewImplementation.checkPermission(pkgName, permName, persistentDeviceId, - userId); + public int checkPermission(String pkgName, String permName, String deviceId, int userId) { + int oldVal = mOldImplementation.checkPermission(pkgName, permName, deviceId, userId); + int newVal = mNewImplementation.checkPermission(pkgName, permName, deviceId, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("checkPermission"); @@ -317,7 +308,7 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int checkUidPermission(int uid, String permName, int deviceId) { + public int checkUidPermission(int uid, String permName, String deviceId) { int oldVal = mOldImplementation.checkUidPermission(uid, permName, deviceId); int newVal = mNewImplementation.checkUidPermission(uid, permName, deviceId); @@ -329,8 +320,8 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @Override public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName, - @NonNull String persistentDeviceId, int userId) { - return mNewImplementation.getAllPermissionStates(packageName, persistentDeviceId, userId); + @NonNull String deviceId, int userId) { + return mNewImplementation.getAllPermissionStates(packageName, deviceId, userId); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java index bc29e6773bca..981d3d92b15a 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java @@ -159,11 +159,11 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public int getPermissionFlags(String packageName, String permName, String persistentDeviceId, + public int getPermissionFlags(String packageName, String permName, String deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionFlags"); try { - return mService.getPermissionFlags(packageName, permName, persistentDeviceId, userId); + return mService.getPermissionFlags(packageName, permName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -171,13 +171,12 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, String persistentDeviceId, - int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, String deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#updatePermissionFlags"); try { mService.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, persistentDeviceId, userId); + checkAdjustPolicyFlagPermission, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -256,25 +255,24 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public void grantRuntimePermission(String packageName, String permName, - String persistentDeviceId, int userId) { + public void grantRuntimePermission(String packageName, String permName, String deviceId, + int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#grantRuntimePermission"); try { - mService.grantRuntimePermission(packageName, permName, persistentDeviceId, userId); + mService.grantRuntimePermission(packageName, permName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public void revokeRuntimePermission(String packageName, String permName, - String persistentDeviceId, int userId, String reason) { + public void revokeRuntimePermission(String packageName, String permName, String deviceId, + int userId, String reason) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#revokeRuntimePermission"); try { - mService.revokeRuntimePermission(packageName, permName, persistentDeviceId, userId, - reason); + mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); } finally { Trace.traceEnd(TRACE_TAG); } @@ -293,19 +291,19 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int deviceId, int userId) { + String deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#shouldShowRequestPermissionRationale"); try { - return mService.shouldShowRequestPermissionRationale( - packageName, permName, deviceId, userId); + return mService.shouldShowRequestPermissionRationale(packageName, permName, deviceId, + userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + public boolean isPermissionRevokedByPolicy(String packageName, String permName, String deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#isPermissionRevokedByPolicy"); @@ -328,18 +326,17 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public int checkPermission(String pkgName, String permName, String persistentDeviceId, - int userId) { + public int checkPermission(String pkgName, String permName, String deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkPermission"); try { - return mService.checkPermission(pkgName, permName, persistentDeviceId, userId); + return mService.checkPermission(pkgName, permName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public int checkUidPermission(int uid, String permName, int deviceId) { + public int checkUidPermission(int uid, String permName, String deviceId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkUidPermission"); try { return mService.checkUidPermission(uid, permName, deviceId); @@ -350,11 +347,11 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag @Override public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName, - @NonNull String persistentDeviceId, int userId) { + @NonNull String deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl" + "#getAllPermissionStates"); try { - return mService.getAllPermissionStates(packageName, persistentDeviceId, userId); + return mService.getAllPermissionStates(packageName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index 59766ec7a175..f8c678aa3aa3 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -45,6 +45,11 @@ import static android.hardware.SensorPrivacyManager.Sources.OTHER; import static android.hardware.SensorPrivacyManager.Sources.QS_TILE; import static android.hardware.SensorPrivacyManager.Sources.SETTINGS; import static android.hardware.SensorPrivacyManager.Sources.SHELL; +import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; +import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; +import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; +import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED; +import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED; import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE; import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE; import static android.os.UserHandle.USER_NULL; @@ -52,6 +57,9 @@ import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; +import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; +import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA; @@ -63,8 +71,11 @@ import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_ import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.write; +import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -87,6 +98,7 @@ import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.drawable.Icon; +import android.hardware.CameraPrivacyAllowlistEntry; import android.hardware.ISensorPrivacyListener; import android.hardware.ISensorPrivacyManager; import android.hardware.SensorPrivacyManager; @@ -123,6 +135,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.camera.flags.Flags; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; @@ -131,6 +144,7 @@ import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.pm.UserManagerInternal; @@ -139,6 +153,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; @@ -154,7 +169,24 @@ public final class SensorPrivacyService extends SystemService { SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy"; public static final int REMINDER_DIALOG_DELAY_MILLIS = 500; - + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__TOGGLE_ON = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__TOGGLE_OFF = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__ACTION_UNKNOWN = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN; private final Context mContext; private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl; private final UserManagerInternal mUserManagerInternal; @@ -176,6 +208,9 @@ public final class SensorPrivacyService extends SystemService { private CallStateHelper mCallStateHelper; private KeyguardManager mKeyguardManager; + List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist = + new ArrayList<CameraPrivacyAllowlistEntry>(); + private int mCurrentUser = USER_NULL; public SensorPrivacyService(Context context) { @@ -192,6 +227,15 @@ public final class SensorPrivacyService extends SystemService { mPackageManagerInternal = getLocalService(PackageManagerInternal.class); mNotificationManager = mContext.getSystemService(NotificationManager.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(); + ArrayMap<String, Boolean> cameraPrivacyAllowlist = + SystemConfig.getInstance().getCameraPrivacyAllowlist(); + + for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) { + CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry(); + ent.packageName = entry.getKey(); + ent.isMandatory = entry.getValue(); + mCameraPrivacyAllowlist.add(ent); + } } @Override @@ -324,8 +368,15 @@ public final class SensorPrivacyService extends SystemService { mHandler, mHandler::handleSensorPrivacyChanged); mSensorPrivacyStateController.setSensorPrivacyListener( mHandler, - (toggleType, userId, sensor, state) -> mHandler.handleSensorPrivacyChanged( - userId, toggleType, sensor, state.isEnabled())); + (toggleType, userId, sensor, state) -> { + mHandler.handleSensorPrivacyChanged( + userId, toggleType, sensor, state.isEnabled()); + if (Flags.cameraPrivacyAllowlist()) { + mHandler.handleSensorPrivacyChanged( + userId, toggleType, sensor, state.getState()); + } + }); + } // If sensor privacy is enabled for a sensor, but the device doesn't support sensor privacy @@ -400,9 +451,15 @@ public final class SensorPrivacyService extends SystemService { * @param packageName The package name of the app using the sensor * @param sensor The sensor that is attempting to be used */ + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) private void onSensorUseStarted(int uid, String packageName, int sensor) { UserHandle user = UserHandle.of(mCurrentUser); - if (!isCombinedToggleSensorPrivacyEnabled(sensor)) { + + if (Flags.cameraPrivacyAllowlist() && (sensor == CAMERA) && isAutomotive(mContext)) { + if (!isCameraPrivacyEnabled(packageName)) { + return; + } + } else if (!isCombinedToggleSensorPrivacyEnabled(sensor)) { return; } @@ -727,6 +784,12 @@ public final class SensorPrivacyService extends SystemService { == Configuration.UI_MODE_TYPE_TELEVISION; } + private boolean isAutomotive(Context context) { + int uiMode = context.getResources().getConfiguration().uiMode; + return (uiMode & Configuration.UI_MODE_TYPE_MASK) + == Configuration.UI_MODE_TYPE_CAR; + } + /** * Sets the sensor privacy to the provided state and notifies all listeners of the new * state. @@ -766,6 +829,225 @@ public final class SensorPrivacyService extends SystemService { setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, enable); } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void setToggleSensorPrivacyState(int userId, int source, int sensor, int state) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " setToggleSensorPrivacyState(" + + "userId=" + userId + + " source=" + source + + " sensor=" + sensor + + " state=" + state + + ")"); + } + enforceManageSensorPrivacyPermission(); + if (userId == UserHandle.USER_CURRENT) { + userId = mCurrentUser; + } + + if (!canChangeToggleSensorPrivacy(userId, sensor)) { + return; + } + if (!supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor)) { + // Do not enable sensor privacy if the device doesn't support it. + return; + } + + setToggleSensorPrivacyStateUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, + state); + } + + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private void setToggleSensorPrivacyStateUnchecked(int toggleType, int userId, int source, + int sensor, int state) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " setToggleSensorPrivacyStateUnchecked(" + + "userId=" + userId + + " source=" + source + + " sensor=" + sensor + + " state=" + state + + ")"); + } + long[] lastChange = new long[1]; + mSensorPrivacyStateController.atomic(() -> { + SensorState sensorState = mSensorPrivacyStateController + .getState(toggleType, userId, sensor); + lastChange[0] = sensorState.getLastChange(); + mSensorPrivacyStateController.setState( + toggleType, userId, sensor, state, mHandler, + changeSuccessful -> { + if (changeSuccessful) { + if (userId == mUserManagerInternal.getProfileParentId(userId)) { + mHandler.sendMessage(PooledLambda.obtainMessage( + SensorPrivacyServiceImpl::logSensorPrivacyStateToggle, + this, + source, sensor, state, lastChange[0], false)); + } + } + }); + }); + } + + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private void logSensorPrivacyStateToggle(int source, int sensor, int state, + long lastChange, boolean onShutDown) { + long logMins = Math.max(0, (getCurrentTimeMillis() - lastChange) / (1000 * 60)); + + int logAction = ACTION__ACTION_UNKNOWN; + if (!onShutDown) { + switch(state) { + case ENABLED : + logAction = ACTION__TOGGLE_OFF; + break; + case DISABLED : + logAction = ACTION__TOGGLE_ON; + break; + case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS : + logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; + break; + case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS : + logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; + break; + case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS : + logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; + break; + default : + logAction = ACTION__ACTION_UNKNOWN; + break; + } + } + + int logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN; + switch(sensor) { + case CAMERA: + logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA; + break; + case MICROPHONE: + logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE; + break; + default: + logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN; + break; + } + + int logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN; + switch(source) { + case QS_TILE : + logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__QS_TILE; + break; + case DIALOG : + logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__DIALOG; + break; + case SETTINGS: + logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SETTINGS; + break; + default: + logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN; + break; + } + + if (DEBUG || DEBUG_LOGGING) { + Log.d(TAG, "Logging sensor toggle interaction:" + " logSensor=" + logSensor + + " logAction=" + logAction + " logSource=" + logSource + " logMins=" + + logMins); + } + write(PRIVACY_SENSOR_TOGGLE_INTERACTION, logSensor, logAction, logSource, logMins); + + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor, + int state) { + enforceManageSensorPrivacyPermission(); + if (userId == UserHandle.USER_CURRENT) { + userId = mCurrentUser; + } + int parentId = mUserManagerInternal.getProfileParentId(userId); + forAllUsers(userId2 -> { + if (parentId == mUserManagerInternal.getProfileParentId(userId2)) { + setToggleSensorPrivacyState(userId2, source, sensor, state); + } + }); + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() { + enforceObserveSensorPrivacyPermission(); + return mCameraPrivacyAllowlist; + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public boolean isCameraPrivacyEnabled(String packageName) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " isCameraPrivacyEnabled(" + + "packageName=" + packageName + + ")"); + } + enforceObserveSensorPrivacyPermission(); + + int state = mSensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, mCurrentUser, + CAMERA).getState(); + if (state == ENABLED) { + return true; + } else if (state == DISABLED) { + return false; + } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) { + for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { + if ((packageName.equals(entry.packageName)) && !entry.isMandatory) { + return false; + } + } + return true; + } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) { + for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { + if ((packageName.equals(entry.packageName)) && entry.isMandatory) { + return false; + } + } + return true; + } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) { + for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { + if (packageName.equals(entry.packageName)) { + return false; + } + } + return true; + } + return false; + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public int getToggleSensorPrivacyState(int toggleType, int sensor) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " getToggleSensorPrivacyState(" + + "toggleType=" + toggleType + + " sensor=" + sensor + + ")"); + } + enforceObserveSensorPrivacyPermission(); + + return mSensorPrivacyStateController.getState(toggleType, mCurrentUser, sensor) + .getState(); + } + private void setToggleSensorPrivacyUnchecked(int toggleType, int userId, int source, int sensor, boolean enable) { if (DEBUG) { @@ -899,16 +1181,23 @@ public final class SensorPrivacyService extends SystemService { * Enforces the caller contains the necessary permission to change the state of sensor * privacy. */ + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) private void enforceManageSensorPrivacyPermission() { - enforcePermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY, - "Changing sensor privacy requires the following permission: " - + MANAGE_SENSOR_PRIVACY); + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { + return; + } + + String message = "Changing sensor privacy requires the following permission: " + + MANAGE_SENSOR_PRIVACY; + throw new SecurityException(message); } /** * Enforces the caller contains the necessary permission to observe changes to the sate of * sensor privacy. */ + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) private void enforceObserveSensorPrivacyPermission() { String systemUIPackage = mContext.getString(R.string.config_systemUi); int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal @@ -917,15 +1206,13 @@ public final class SensorPrivacyService extends SystemService { // b/221782106, possible race condition with role grant might bootloop device. return; } - enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY, - "Observing sensor privacy changes requires the following permission: " - + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY); - } - - private void enforcePermission(String permission, String message) { - if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { return; } + + String message = "Observing sensor privacy changes requires the following permission: " + + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY; throw new SecurityException(message); } @@ -1293,11 +1580,13 @@ public final class SensorPrivacyService extends SystemService { } @Override + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { (new ShellCommand() { @Override + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public int onCommand(String cmd) { if (cmd == null) { return handleDefaultCommands(cmd); @@ -1327,6 +1616,45 @@ public final class SensorPrivacyService extends SystemService { setToggleSensorPrivacy(userId, SHELL, sensor, false); } break; + case "automotive_driver_assistance_apps" : { + if (Flags.cameraPrivacyAllowlist()) { + int sensor = sensorStrToId(getNextArgRequired()); + if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { + pw.println("Command not valid for this sensor"); + return -1; + } + + setToggleSensorPrivacyState(userId, SHELL, sensor, + AUTOMOTIVE_DRIVER_ASSISTANCE_APPS); + } + } + break; + case "automotive_driver_assistance_helpful_apps" : { + if (Flags.cameraPrivacyAllowlist()) { + int sensor = sensorStrToId(getNextArgRequired()); + if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { + pw.println("Command not valid for this sensor"); + return -1; + } + + setToggleSensorPrivacyState(userId, SHELL, sensor, + AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS); + } + } + break; + case "automotive_driver_assistance_required_apps" : { + if (Flags.cameraPrivacyAllowlist()) { + int sensor = sensorStrToId(getNextArgRequired()); + if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { + pw.println("Command not valid for this sensor"); + return -1; + } + + setToggleSensorPrivacyState(userId, SHELL, sensor, + AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS); + } + } + break; default: return handleDefaultCommands(cmd); } @@ -1349,6 +1677,24 @@ public final class SensorPrivacyService extends SystemService { pw.println(" disable USER_ID SENSOR"); pw.println(" Disable privacy for a certain sensor."); pw.println(""); + if (Flags.cameraPrivacyAllowlist()) { + if (isAutomotive(mContext)) { + pw.println(" automotive_driver_assistance_apps USER_ID SENSOR"); + pw.println(" Disable privacy for automotive apps which help you" + + " drive and apps which are required by OEM"); + pw.println(""); + pw.println(" automotive_driver_assistance_helpful_apps " + + "USER_ID SENSOR"); + pw.println(" Disable privacy for automotive apps which " + + "help you drive."); + pw.println(""); + pw.println(" automotive_driver_assistance_required_apps " + + "USER_ID SENSOR"); + pw.println(" Disable privacy for automotive apps which are " + + "required by OEM."); + pw.println(""); + } + } } }).exec(this, in, out, err, args, callback, resultReceiver); } @@ -1457,6 +1803,38 @@ public final class SensorPrivacyService extends SystemService { mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType); } + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void handleSensorPrivacyChanged(int userId, int toggleType, int sensor, + int state) { + if (userId == mCurrentUser) { + mSensorPrivacyServiceImpl.setGlobalRestriction(sensor, + mSensorPrivacyServiceImpl.isCombinedToggleSensorPrivacyEnabled(sensor)); + } + + if (userId != mCurrentUser) { + return; + } + synchronized (mListenerLock) { + try { + final int count = mToggleSensorListeners.beginBroadcast(); + for (int i = 0; i < count; i++) { + ISensorPrivacyListener listener = mToggleSensorListeners.getBroadcastItem( + i); + try { + listener.onSensorPrivacyStateChanged(toggleType, sensor, state); + } catch (RemoteException e) { + Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", + e); + } + } + } finally { + mToggleSensorListeners.finishBroadcast(); + } + } + + mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType); + } + public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key, IBinder token) { sendMessage(PooledLambda.obtainMessage( diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java index 96949589447c..14b13e0d3692 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java @@ -16,9 +16,11 @@ package com.android.server.sensorprivacy; +import android.annotation.FlaggedApi; import android.os.Handler; import com.android.internal.annotations.GuardedBy; +import com.android.internal.camera.flags.Flags; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; @@ -51,6 +53,14 @@ abstract class SensorPrivacyStateController { } } + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + void setState(int toggleType, int userId, int sensor, int state, Handler callbackHandler, + SetStateResultCallback callback) { + synchronized (mLock) { + setStateLocked(toggleType, userId, sensor, state, callbackHandler, callback); + } + } + void setSensorPrivacyListener(Handler handler, SensorPrivacyListener listener) { synchronized (mLock) { @@ -128,6 +138,11 @@ abstract class SensorPrivacyStateController { Handler callbackHandler, SetStateResultCallback callback); @GuardedBy("mLock") + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + abstract void setStateLocked(int toggleType, int userId, int sensor, int state, + Handler callbackHandler, SetStateResultCallback callback); + + @GuardedBy("mLock") abstract void setSensorPrivacyListenerLocked(Handler handler, SensorPrivacyListener listener); diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java index 3dcb4cf996c4..4b491ce30923 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java @@ -16,8 +16,12 @@ package com.android.server.sensorprivacy; +import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED; + +import android.annotation.FlaggedApi; import android.os.Handler; +import com.android.internal.camera.flags.Flags; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; @@ -85,6 +89,33 @@ class SensorPrivacyStateControllerImpl extends SensorPrivacyStateController { sendSetStateCallback(callbackHandler, callback, false); } + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + void setStateLocked(int toggleType, int userId, int sensor, int state, + Handler callbackHandler, SetStateResultCallback callback) { + // Changing the SensorState's mEnabled updates the timestamp of its last change. + // A nonexistent state -> unmuted should not set the timestamp. + SensorState lastState = mPersistedState.getState(toggleType, userId, sensor); + if (lastState == null) { + if (state == DISABLED) { + sendSetStateCallback(callbackHandler, callback, false); + return; + } else { + SensorState sensorState = new SensorState(state); + mPersistedState.setState(toggleType, userId, sensor, sensorState); + notifyStateChangeLocked(toggleType, userId, sensor, sensorState); + sendSetStateCallback(callbackHandler, callback, true); + return; + } + } + if (lastState.setState(state)) { + notifyStateChangeLocked(toggleType, userId, sensor, lastState); + sendSetStateCallback(callbackHandler, callback, true); + return; + } + sendSetStateCallback(callbackHandler, callback, false); + } + private void notifyStateChangeLocked(int toggleType, int userId, int sensor, SensorState sensorState) { if (mListenerHandler != null && mListener != null) { diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index d2f6701e313e..4af8c616b2bc 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -65,6 +65,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.net.Uri; +import android.os.BadParcelableException; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -630,16 +631,24 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements if (intent == null) { return null; } - Uri data = intent.getData(); - ClipData clip = intent.getClipData(); - if (data == null && clip == null) { - return null; - } + // Default userId for uris in the intent (if they don't specify it themselves) int contentUserHint = intent.getContentUserHint(); if (contentUserHint == UserHandle.USER_CURRENT) { contentUserHint = UserHandle.getUserId(callingUid); } + + if (android.security.Flags.contentUriPermissionApis()) { + enforceRequireContentUriPermissionFromCallerOnIntentExtraStream(intent, contentUserHint, + mode, callingUid, requireContentUriPermissionFromCaller); + } + + Uri data = intent.getData(); + ClipData clip = intent.getClipData(); + if (data == null && clip == null) { + return null; + } + int targetUid; if (needed != null) { targetUid = needed.targetUid; @@ -733,6 +742,37 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements } } + private void enforceRequireContentUriPermissionFromCallerOnIntentExtraStream(Intent intent, + int contentUserHint, int mode, int callingUid, + @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) { + try { + final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class); + if (uri != null) { + final GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode); + enforceRequireContentUriPermissionFromCaller( + requireContentUriPermissionFromCaller, grantUri, callingUid); + } + } catch (BadParcelableException e) { + Slog.w(TAG, "Failed to unparcel an URI in EXTRA_STREAM, skipping" + + " requireContentUriPermissionFromCaller: " + e); + } + + try { + final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, + Uri.class); + if (uris != null) { + for (int i = uris.size() - 1; i >= 0; i--) { + final GrantUri grantUri = GrantUri.resolve(contentUserHint, uris.get(i), mode); + enforceRequireContentUriPermissionFromCaller( + requireContentUriPermissionFromCaller, grantUri, callingUid); + } + } + } catch (BadParcelableException e) { + Slog.w(TAG, "Failed to unparcel an ArrayList of URIs in EXTRA_STREAM, skipping" + + " requireContentUriPermissionFromCaller: " + e); + } + } + @GuardedBy("mLock") private void readGrantedUriPermissionsLocked() { if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()"); diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java index 4772bbfe97dd..2e1049b9ea32 100644 --- a/services/core/java/com/android/server/utils/EventLogger.java +++ b/services/core/java/com/android/server/utils/EventLogger.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; +import android.util.Slog; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -84,6 +85,17 @@ public class EventLogger { enqueue(event.printLog(logType, tag)); } + /** + * Add a string-based event to the system log, and print it to the log with a specific severity. + * @param msg the message to appear in the log + * @param logType the log severity (verbose/info/warning/error) + * @param tag the tag under which the log entry will appear + */ + public synchronized void enqueueAndSlog(String msg, @Event.LogType int logType, String tag) { + final Event event = new StringEvent(msg); + enqueue(event.printSlog(logType, tag)); + } + /** Dumps events into the given {@link DumpSink}. */ public synchronized void dump(DumpSink dumpSink) { dumpSink.sink(mTag, new ArrayList<>(mEvents)); @@ -138,7 +150,7 @@ public class EventLogger { /** * Causes the string message for the event to appear in the logcat. * Here is an example of how to create a new event (a StringEvent), adding it to the logger - * (an instance of AudioEventLogger) while also making it show in the logcat: + * (an instance of EventLogger) while also making it show in the logcat: * <pre> * myLogger.log( * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) ); @@ -167,9 +179,9 @@ public class EventLogger { /** * Same as {@link #printLog(String)} with a log type - * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV} - * @param tag - * @return + * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV}, {@link #ALOGW} + * @param tag the tag the log entry will be printed under + * @return the event itself */ public Event printLog(@LogType int type, String tag) { switch (type) { @@ -191,6 +203,32 @@ public class EventLogger { } /** + * Causes the string message for the event to appear in the system log. + * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV}, {@link #ALOGW} + * @param tag the tag the log entry will be printed under + * @return the event itself + * @see #printLog(int, String) + */ + public Event printSlog(@LogType int type, String tag) { + switch (type) { + case ALOGI: + Slog.i(tag, eventToString()); + break; + case ALOGE: + Slog.e(tag, eventToString()); + break; + case ALOGW: + Slog.w(tag, eventToString()); + break; + case ALOGV: + default: + Slog.v(tag, eventToString()); + break; + } + return this; + } + + /** * Convert event to String. * This method is only called when the logger history is about to the dumped, * so this method is where expensive String conversions should be made, not when the Event diff --git a/services/core/java/com/android/server/vibrator/TEST_MAPPING b/services/core/java/com/android/server/vibrator/TEST_MAPPING index 92b327d9abff..c64941bebbf4 100644 --- a/services/core/java/com/android/server/vibrator/TEST_MAPPING +++ b/services/core/java/com/android/server/vibrator/TEST_MAPPING @@ -5,6 +5,9 @@ }, { "path": "cts/tests/vibrator" + }, + { + "path": "frameworks/hardware/interfaces/vibrator/aidl" } ] } diff --git a/services/core/java/com/android/server/wm/ActivityCallerState.java b/services/core/java/com/android/server/wm/ActivityCallerState.java index 4416605d9f04..e7972904adaa 100644 --- a/services/core/java/com/android/server/wm/ActivityCallerState.java +++ b/services/core/java/com/android/server/wm/ActivityCallerState.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.os.Process.INVALID_UID; + import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -51,6 +53,9 @@ final class ActivityCallerState { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityCallerState" : TAG_ATM; // XML tags for CallerInfo + private static final String ATTR_CALLER_UID = "caller_uid"; + private static final String ATTR_CALLER_PACKAGE = "caller_package"; + private static final String ATTR_CALLER_IS_SHARE_ENABLED = "caller_is_share_enabled"; private static final String TAG_READABLE_CONTENT_URI = "readable_content_uri"; private static final String TAG_WRITABLE_CONTENT_URI = "writable_content_uri"; private static final String TAG_INACCESSIBLE_CONTENT_URI = "inaccessible_content_uri"; @@ -71,12 +76,33 @@ final class ActivityCallerState { return mCallerTokenInfoMap.getOrDefault(callerToken, null); } + boolean hasCaller(IBinder callerToken) { + return getCallerInfoOrNull(callerToken) != null; + } + + int getUid(IBinder callerToken) { + CallerInfo callerInfo = getCallerInfoOrNull(callerToken); + return callerInfo != null ? callerInfo.mUid : INVALID_UID; + } + + String getPackage(IBinder callerToken) { + CallerInfo callerInfo = getCallerInfoOrNull(callerToken); + return callerInfo != null ? callerInfo.mPackageName : null; + } + + boolean isShareIdentityEnabled(IBinder callerToken) { + CallerInfo callerInfo = getCallerInfoOrNull(callerToken); + return callerInfo != null ? callerInfo.mIsShareIdentityEnabled : false; + } + void add(IBinder callerToken, CallerInfo callerInfo) { mCallerTokenInfoMap.put(callerToken, callerInfo); } - void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid) { - final CallerInfo callerInfo = new CallerInfo(); + void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid, + String callerPackageName, boolean isCallerShareIdentityEnabled) { + final CallerInfo callerInfo = new CallerInfo(callerUid, callerPackageName, + isCallerShareIdentityEnabled); mCallerTokenInfoMap.put(callerToken, callerInfo); final ArraySet<Uri> contentUris = getContentUrisFromIntent(intent); @@ -180,12 +206,26 @@ final class ActivityCallerState { } public static final class CallerInfo { + final int mUid; + final String mPackageName; + final boolean mIsShareIdentityEnabled; final ArraySet<GrantUri> mReadableContentUris = new ArraySet<>(); final ArraySet<GrantUri> mWritableContentUris = new ArraySet<>(); final ArraySet<GrantUri> mInaccessibleContentUris = new ArraySet<>(); + CallerInfo(int uid, String packageName, boolean isShareIdentityEnabled) { + mUid = uid; + mPackageName = packageName; + mIsShareIdentityEnabled = isShareIdentityEnabled; + } + public void saveToXml(TypedXmlSerializer out) throws IOException, XmlPullParserException { + out.attributeInt(null, ATTR_CALLER_UID, mUid); + if (mPackageName != null) { + out.attribute(null, ATTR_CALLER_PACKAGE, mPackageName); + } + out.attributeBoolean(null, ATTR_CALLER_IS_SHARE_ENABLED, mIsShareIdentityEnabled); for (int i = mReadableContentUris.size() - 1; i >= 0; i--) { saveGrantUriToXml(out, mReadableContentUris.valueAt(i), TAG_READABLE_CONTENT_URI); } @@ -202,7 +242,12 @@ final class ActivityCallerState { public static CallerInfo restoreFromXml(TypedXmlPullParser in) throws IOException, XmlPullParserException { - CallerInfo callerInfo = new CallerInfo(); + int uid = in.getAttributeInt(null, ATTR_CALLER_UID, 0); + String packageName = in.getAttributeValue(null, ATTR_CALLER_PACKAGE); + boolean isShareIdentityEnabled = in.getAttributeBoolean(null, + ATTR_CALLER_IS_SHARE_ENABLED, false); + + CallerInfo callerInfo = new CallerInfo(uid, packageName, isShareIdentityEnabled); final int outerDepth = in.getDepth(); int event; while (((event = in.next()) != END_DOCUMENT) diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index efaa467e43e4..ed5df5fab017 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -695,30 +695,57 @@ class ActivityClientController extends IActivityClientController.Stub { @Override public int getLaunchedFromUid(IBinder token) { + return getUid(token, /* callerToken */ null, /* isActivityCallerCall */ false); + } + + @Override + public String getLaunchedFromPackage(IBinder token) { + return getPackage(token, /* callerToken */ null, /* isActivityCallerCall */ false); + } + + @Override + public int getActivityCallerUid(IBinder activityToken, IBinder callerToken) { + return getUid(activityToken, callerToken, /* isActivityCallerCall */ true); + } + + @Override + public String getActivityCallerPackage(IBinder activityToken, IBinder callerToken) { + return getPackage(activityToken, callerToken, /* isActivityCallerCall */ true); + } + + private int getUid(IBinder activityToken, IBinder callerToken, boolean isActivityCallerCall) { final int uid = Binder.getCallingUid(); final boolean isInternalCaller = isInternalCallerGetLaunchedFrom(uid); synchronized (mGlobalLock) { - final ActivityRecord r = ActivityRecord.forTokenLocked(token); - if (r != null && (isInternalCaller || canGetLaunchedFromLocked(uid, r))) { - return r.launchedFromUid; + final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken); + if (r != null && (isInternalCaller || canGetLaunchedFromLocked(uid, r, callerToken, + isActivityCallerCall)) && isValidCaller(r, callerToken, isActivityCallerCall)) { + return isActivityCallerCall ? r.getCallerUid(callerToken) : r.launchedFromUid; } } return INVALID_UID; } - @Override - public String getLaunchedFromPackage(IBinder token) { + private String getPackage(IBinder activityToken, IBinder callerToken, + boolean isActivityCallerCall) { final int uid = Binder.getCallingUid(); final boolean isInternalCaller = isInternalCallerGetLaunchedFrom(uid); synchronized (mGlobalLock) { - final ActivityRecord r = ActivityRecord.forTokenLocked(token); - if (r != null && (isInternalCaller || canGetLaunchedFromLocked(uid, r))) { - return r.launchedFromPackage; + final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken); + if (r != null && (isInternalCaller || canGetLaunchedFromLocked(uid, r, callerToken, + isActivityCallerCall)) && isValidCaller(r, callerToken, isActivityCallerCall)) { + return isActivityCallerCall + ? r.getCallerPackage(callerToken) : r.launchedFromPackage; } } return null; } + private boolean isValidCaller(ActivityRecord r, IBinder callerToken, + boolean isActivityCallerCall) { + return isActivityCallerCall ? r.hasCaller(callerToken) : callerToken == null; + } + /** * @param uri This uri must NOT contain an embedded userId. * @param userId The userId in which the uri is to be resolved. @@ -768,9 +795,13 @@ class ActivityClientController extends IActivityClientController.Stub { * verifying whether the provided {@code ActivityRecord r} has opted in to sharing its * identity or if the uid of the activity matches that of the launching app. */ - private static boolean canGetLaunchedFromLocked(int uid, ActivityRecord r) { + private static boolean canGetLaunchedFromLocked(int uid, ActivityRecord r, + IBinder callerToken, boolean isActivityCallerCall) { if (CompatChanges.isChangeEnabled(ACCESS_SHARED_IDENTITY, uid)) { - return r.mShareIdentity || r.launchedFromUid == uid; + boolean isShareIdentityEnabled = isActivityCallerCall + ? r.isCallerShareIdentityEnabled(callerToken) : r.mShareIdentity; + int callerUid = isActivityCallerCall ? r.getCallerUid(callerToken) : r.launchedFromUid; + return isShareIdentityEnabled || callerUid == uid; } return false; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 09c329be7d09..b3a8b7818415 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2024,12 +2024,31 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + boolean hasCaller(IBinder callerToken) { + return mCallerState.hasCaller(callerToken); + } + + int getCallerUid(IBinder callerToken) { + return mCallerState.getUid(callerToken); + } + + String getCallerPackage(IBinder callerToken) { + return mCallerState.getPackage(callerToken); + } + + boolean isCallerShareIdentityEnabled(IBinder callerToken) { + return mCallerState.isShareIdentityEnabled(callerToken); + } + void computeInitialCallerInfo() { - computeCallerInfo(initialCallerInfoAccessToken, intent, launchedFromUid); + computeCallerInfo(initialCallerInfoAccessToken, intent, launchedFromUid, + launchedFromPackage, mShareIdentity); } - void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid) { - mCallerState.computeCallerInfo(callerToken, intent, callerUid); + void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid, + String callerPackageName, boolean isCallerShareIdentityEnabled) { + mCallerState.computeCallerInfo(callerToken, intent, callerUid, callerPackageName, + isCallerShareIdentityEnabled); } boolean checkContentUriPermission(IBinder callerToken, GrantUri grantUri, int modeFlags) { @@ -4960,11 +4979,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * method will be called at the proper time. */ final void deliverNewIntentLocked(int callingUid, Intent intent, NeededUriGrants intentGrants, - String referrer) { + String referrer, boolean isShareIdentityEnabled) { + IBinder callerToken = new Binder(); + if (android.security.Flags.contentUriPermissionApis()) { + computeCallerInfo(callerToken, intent, callingUid, referrer, isShareIdentityEnabled); + } // The activity now gets access to the data associated with this Intent. mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants, getUriPermissionsLocked()); - final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer)); + final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer), + callerToken); boolean unsent = true; final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping(); diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java index db27f607c867..01d077a5bc55 100644 --- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java +++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java @@ -22,12 +22,10 @@ import static com.android.server.wm.ActivityStarter.ASM_RESTRICTIONS; import android.annotation.NonNull; import android.app.compat.CompatChanges; -import android.content.pm.PackageManager; import android.provider.DeviceConfig; import com.android.internal.annotations.GuardedBy; -import java.util.HashSet; import java.util.concurrent.Executor; /** @@ -50,74 +48,49 @@ class ActivitySecurityModelFeatureFlags { private static final String KEY_ASM_RESTRICTIONS_ENABLED = KEY_ASM_PREFIX + "asm_restrictions_enabled"; private static final String KEY_ASM_TOASTS_ENABLED = KEY_ASM_PREFIX + "asm_toasts_enabled"; - private static final String KEY_ASM_EXEMPTED_PACKAGES = KEY_ASM_PREFIX - + "asm_exempted_packages"; + private static final int VALUE_DISABLE = 0; private static final int VALUE_ENABLE_FOR_V = 1; private static final int VALUE_ENABLE_FOR_ALL = 2; private static final int DEFAULT_VALUE = VALUE_DISABLE; - private static final String DEFAULT_EXCEPTION_LIST = ""; private static int sAsmToastsEnabled; private static int sAsmRestrictionsEnabled; - private static final HashSet<String> sExcludedPackageNames = new HashSet<>(); - private static PackageManager sPm; @GuardedBy("ActivityTaskManagerService.mGlobalLock") - static void initialize(@NonNull Executor executor, @NonNull PackageManager pm) { + static void initialize(@NonNull Executor executor) { updateFromDeviceConfig(); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, executor, properties -> updateFromDeviceConfig()); - sPm = pm; } @GuardedBy("ActivityTaskManagerService.mGlobalLock") static boolean shouldShowToast(int uid) { - return flagEnabledForUid(sAsmToastsEnabled, uid); + return sAsmToastsEnabled == VALUE_ENABLE_FOR_ALL + || (sAsmToastsEnabled == VALUE_ENABLE_FOR_V + && CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, uid)); } @GuardedBy("ActivityTaskManagerService.mGlobalLock") static boolean shouldRestrictActivitySwitch(int uid) { - return flagEnabledForUid(sAsmRestrictionsEnabled, uid); - } - - private static boolean flagEnabledForUid(int flag, int uid) { - boolean flagEnabled = flag == VALUE_ENABLE_FOR_ALL - || (flag == VALUE_ENABLE_FOR_V - && CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, uid)); - - if (flagEnabled) { - String[] packageNames = sPm.getPackagesForUid(uid); - if (packageNames == null) { - return true; - } - for (int i = 0; i < packageNames.length; i++) { - if (sExcludedPackageNames.contains(packageNames[i])) { - return false; - } - } - return true; + if (android.security.Flags.asmRestrictionsEnabled()) { + return CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, uid) + || asmRestrictionsEnabledForAll(); } return false; } + @GuardedBy("ActivityTaskManagerService.mGlobalLock") + static boolean asmRestrictionsEnabledForAll() { + return sAsmRestrictionsEnabled == VALUE_ENABLE_FOR_ALL; + } + private static void updateFromDeviceConfig() { sAsmToastsEnabled = DeviceConfig.getInt(NAMESPACE, KEY_ASM_TOASTS_ENABLED, DEFAULT_VALUE); sAsmRestrictionsEnabled = DeviceConfig.getInt(NAMESPACE, KEY_ASM_RESTRICTIONS_ENABLED, DEFAULT_VALUE); - - String rawExceptionList = DeviceConfig.getString(NAMESPACE, - KEY_ASM_EXEMPTED_PACKAGES, DEFAULT_EXCEPTION_LIST); - sExcludedPackageNames.clear(); - String[] packages = rawExceptionList.split(","); - for (String packageName : packages) { - String packageNameTrimmed = packageName.trim(); - if (!packageNameTrimmed.isEmpty()) { - sExcludedPackageNames.add(packageNameTrimmed); - } - } } } diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java index a54dd826b5bb..3609837f417b 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java @@ -23,18 +23,20 @@ import android.window.TaskSnapshot; */ class ActivitySnapshotCache extends SnapshotCache<ActivityRecord> { - ActivitySnapshotCache(WindowManagerService service) { - super(service, "Activity"); + ActivitySnapshotCache() { + super("Activity"); } @Override void putSnapshot(ActivityRecord ar, TaskSnapshot snapshot) { final int hasCode = System.identityHashCode(ar); - final CacheEntry entry = mRunningCache.get(hasCode); - if (entry != null) { - mAppIdMap.remove(entry.topApp); + synchronized (mLock) { + final CacheEntry entry = mRunningCache.get(hasCode); + if (entry != null) { + mAppIdMap.remove(entry.topApp); + } + mAppIdMap.put(ar, hasCode); + mRunningCache.put(hasCode, new CacheEntry(snapshot, ar)); } - mAppIdMap.put(ar, hasCode); - mRunningCache.put(hasCode, new CacheEntry(snapshot, ar)); } } diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 1f013b913283..f83003d4e278 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -102,7 +102,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord Environment::getDataSystemCeDirectory); mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider); mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider); - initialize(new ActivitySnapshotCache(service)); + initialize(new ActivitySnapshotCache()); final boolean snapshotEnabled = !service.mContext diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 07afa5fc21be..65d01efc6143 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2909,7 +2909,7 @@ class ActivityStarter { activity.logStartActivity(EventLogTags.WM_NEW_INTENT, activity.getTask()); activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, intentGrants, - mStartActivity.launchedFromPackage); + mStartActivity.launchedFromPackage, mStartActivity.mShareIdentity); mIntentDelivered = true; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0e6c06dad486..445295a1c3ff 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -881,7 +881,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mTaskSupervisor.onSystemReady(); mActivityClientController.onSystemReady(); // TODO(b/258792202) Cleanup once ASM is ready to launch - ActivitySecurityModelFeatureFlags.initialize(mContext.getMainExecutor(), pm); + ActivitySecurityModelFeatureFlags.initialize(mContext.getMainExecutor()); mGrammaticalManagerInternal = LocalServices.getService( GrammaticalInflectionManagerInternal.class); } diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index b65b2b2ae959..3bc531950128 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -56,6 +56,7 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Process; import android.os.SystemClock; @@ -529,6 +530,9 @@ public class BackgroundActivityStartController { static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked"); static final BalVerdict ALLOW_BY_DEFAULT = new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default"); + // Careful using this - it will bypass all ASM checks. + static final BalVerdict ALLOW_PRIVILEGED = + new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, false, "PRIVILEGED"); private final @BalCode int mCode; private final boolean mBackground; private final String mMessage; @@ -913,8 +917,10 @@ public class BackgroundActivityStartController { // Normal apps with visible app window will be allowed to start activity if app switching // is allowed, or apps like live wallpaper with non app visible window will be allowed. + // The home app can start apps even if app switches are usually disallowed. final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW - || state.mAppSwitchState == APP_SWITCH_FG_ONLY; + || state.mAppSwitchState == APP_SWITCH_FG_ONLY + || isHomeApp(state.mRealCallingUid, state.mRealCallingPackage); if (balImproveRealCallerVisibilityCheck()) { if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) { return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, @@ -1232,7 +1238,8 @@ public class BackgroundActivityStartController { boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags .shouldRestrictActivitySwitch(callingUid); int[] finishCount = new int[0]; - if (shouldBlockActivityStart) { + if (shouldBlockActivityStart + && blockCrossUidActivitySwitchFromBelowForActivity(targetTaskTop)) { ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched); if (activity == null) { // mStartActivity is not in task, so clear everything @@ -1317,7 +1324,7 @@ public class BackgroundActivityStartController { boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags .shouldRestrictActivitySwitch(callingUid) - && bas.mBlockActivityStartIfFlagEnabled; + && bas.mBlockActivityStartIfFlagEnabled; PackageManager pm = mService.mContext.getPackageManager(); String callingPackage = pm.getNameForUid(callingUid); @@ -1371,19 +1378,19 @@ public class BackgroundActivityStartController { int uid, @Nullable ActivityRecord sourceRecord) { // If the source is visible, consider it 'top'. if (sourceRecord != null && sourceRecord.isVisibleRequested()) { - return new BlockActivityStart(false, false); + return BlockActivityStart.ACTIVITY_START_ALLOWED; } // Always allow actual top activity to clear task ActivityRecord topActivity = task.getTopMostActivity(); if (topActivity != null && topActivity.isUid(uid)) { - return new BlockActivityStart(false, false); + return BlockActivityStart.ACTIVITY_START_ALLOWED; } // If UID is visible in target task, allow launch if (task.forAllActivities((Predicate<ActivityRecord>) ar -> ar.isUid(uid) && ar.isVisibleRequested())) { - return new BlockActivityStart(false, false); + return BlockActivityStart.ACTIVITY_START_ALLOWED; } // Consider the source activity, whether or not it is finishing. Do not consider any other @@ -1450,12 +1457,11 @@ public class BackgroundActivityStartController { private BlockActivityStart blockCrossUidActivitySwitchFromBelow(ActivityRecord ar, int sourceUid) { if (ar.isUid(sourceUid)) { - return new BlockActivityStart(false, false); + return BlockActivityStart.ACTIVITY_START_ALLOWED; } - // If mAllowCrossUidActivitySwitchFromBelow is set, honor it. - if (ar.mAllowCrossUidActivitySwitchFromBelow) { - return new BlockActivityStart(false, false); + if (!blockCrossUidActivitySwitchFromBelowForActivity(ar)) { + return BlockActivityStart.ACTIVITY_START_ALLOWED; } // At this point, we would block if the feature is launched and both apps were V+ @@ -1466,8 +1472,11 @@ public class BackgroundActivityStartController { ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(ar.getUid()) && ActivitySecurityModelFeatureFlags .shouldRestrictActivitySwitch(sourceUid); - return new BackgroundActivityStartController - .BlockActivityStart(restrictActivitySwitch, true); + if (restrictActivitySwitch) { + return BlockActivityStart.BLOCK; + } else { + return BlockActivityStart.LOG_ONLY; + } } /** @@ -1673,14 +1682,52 @@ public class BackgroundActivityStartController { } } - static class BlockActivityStart { + /** + * Activity level allowCrossUidActivitySwitchFromBelow defaults to false. + * Package level defaults to true. + * We block the launch if dev has explicitly set package level to false, and activity level has + * not opted out + */ + private boolean blockCrossUidActivitySwitchFromBelowForActivity(@NonNull ActivityRecord ar) { + // We don't need to check package level if activity has opted out. + if (ar.mAllowCrossUidActivitySwitchFromBelow) { + return false; + } + + if (ActivitySecurityModelFeatureFlags.asmRestrictionsEnabledForAll()) { + return true; + } + + String packageName = ar.packageName; + if (packageName == null) { + return false; + } + + PackageManager pm = mService.mContext.getPackageManager(); + ApplicationInfo applicationInfo; + + try { + applicationInfo = pm.getApplicationInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Slog.wtf(TAG, "Package name: " + packageName + " not found."); + return false; + } + + return !applicationInfo.allowCrossUidActivitySwitchFromBelow; + } + + private static class BlockActivityStart { + private static final BlockActivityStart ACTIVITY_START_ALLOWED = + new BlockActivityStart(false, false); + private static final BlockActivityStart LOG_ONLY = new BlockActivityStart(false, true); + private static final BlockActivityStart BLOCK = new BlockActivityStart(true, true); // We should block if feature flag is enabled private final boolean mBlockActivityStartIfFlagEnabled; // Used for logging/toasts. Would we block if target sdk was V and feature was // enabled? private final boolean mWouldBlockActivityStartIgnoringFlag; - BlockActivityStart(boolean shouldBlockActivityStart, + private BlockActivityStart(boolean shouldBlockActivityStart, boolean wouldBlockActivityStartIgnoringFlags) { this.mBlockActivityStartIfFlagEnabled = shouldBlockActivityStart; this.mWouldBlockActivityStartIgnoringFlag = wouldBlockActivityStartIgnoringFlags; diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index e6ef90bd33d2..68bff439b712 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -17,6 +17,7 @@ package com.android.server.wm; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_EMBEDDED_WINDOWS; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -34,6 +35,9 @@ import android.view.InputApplicationHandle; import android.view.InputChannel; import android.window.InputTransferToken; +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.input.InputManagerService; + /** * Keeps track of embedded windows. * @@ -52,9 +56,13 @@ class EmbeddedWindowController { private final Object mGlobalLock; private final ActivityTaskManagerService mAtmService; - EmbeddedWindowController(ActivityTaskManagerService atmService) { + private final InputManagerService mInputManagerService; + + EmbeddedWindowController(ActivityTaskManagerService atmService, + InputManagerService inputManagerService) { mAtmService = atmService; mGlobalLock = atmService.getGlobalLock(); + mInputManagerService = inputManagerService; } /** @@ -135,6 +143,60 @@ class EmbeddedWindowController { return mWindowsByWindowToken.get(windowToken); } + private boolean isValidTouchGestureParams(WindowState hostWindowState, + EmbeddedWindow embeddedWindow) { + if (embeddedWindow == null) { + ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS, + "Attempt to transfer touch gesture with non-existent embedded window"); + return false; + } + final WindowState wsAssociatedWithEmbedded = embeddedWindow.getWindowState(); + if (wsAssociatedWithEmbedded == null) { + ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS, + "Attempt to transfer touch gesture using embedded window with no associated " + + "host"); + return false; + } + if (wsAssociatedWithEmbedded.mClient.asBinder() != hostWindowState.mClient.asBinder()) { + ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS, + "Attempt to transfer touch gesture with host window not associated with " + + "embedded window"); + return false; + } + + if (embeddedWindow.getInputChannelToken() == null) { + ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS, + "Attempt to transfer touch gesture using embedded window that has no input " + + "channel"); + return false; + } + if (hostWindowState.mInputChannelToken == null) { + ProtoLog.w(WM_DEBUG_EMBEDDED_WINDOWS, + "Attempt to transfer touch gesture using a host window with no input channel"); + return false; + } + return true; + } + + boolean transferToHost(InputTransferToken embeddedWindowToken, + WindowState transferToHostWindowState) { + EmbeddedWindow ew = getByInputTransferToken(embeddedWindowToken); + if (!isValidTouchGestureParams(transferToHostWindowState, ew)) { + return false; + } + return mInputManagerService.transferTouchFocus(ew.getInputChannelToken(), + transferToHostWindowState.mInputChannelToken); + } + + boolean transferToEmbedded(WindowState hostWindowState, InputTransferToken transferToToken) { + final EmbeddedWindowController.EmbeddedWindow ew = getByInputTransferToken(transferToToken); + if (!isValidTouchGestureParams(hostWindowState, ew)) { + return false; + } + return mInputManagerService.transferTouchFocus(hostWindowState.mInputChannelToken, + ew.getInputChannelToken()); + } + static class EmbeddedWindow implements InputTarget { final IBinder mClient; @Nullable final WindowState mHostWindowState; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 609ad1e76370..bf45804192a9 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -612,10 +612,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } void refreshSecureSurfaceState() { - forAllWindows((w) -> { - if (w.mHasSurface) { - w.setSecureLocked(w.isSecureLocked()); - } + forAllWindows(w -> { + w.setSecureLocked(w.isSecureLocked()); }, true /* traverseTopToBottom */); } diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java index a7d6903bbe30..5fe48d12d498 100644 --- a/services/core/java/com/android/server/wm/SensitiveContentPackages.java +++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java @@ -56,6 +56,17 @@ public class SensitiveContentPackages { } /** + * Clears apps added to collection of apps in which screen capture should be disabled. + * + * @param packageInfos set of {@link PackageInfo} whose windows should be unblocked + * from capture. + * @return {@code true} if packages set is modified, {@code false} otherwise. + */ + public boolean removeBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos) { + return mProtectedPackages.removeAll(packageInfos); + } + + /** * Clears the set of package/uid pairs that should be blocked from screen capture * * @return {@code true} if packages set is modified, {@code false} otherwise. diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 3c8c55e278e3..975208fb4b4c 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -970,40 +970,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow) { - if (embeddedWindow == null) { - return false; - } - - final long identity = Binder.clearCallingIdentity(); - boolean didTransfer = false; - try { - didTransfer = mService.transferEmbeddedTouchFocusToHost(embeddedWindow); - } finally { - Binder.restoreCallingIdentity(identity); - } - return didTransfer; - } - - @Override - public boolean transferHostTouchGestureToEmbedded(IWindow hostWindow, - InputTransferToken inputTransferToken) { - if (hostWindow == null) { - return false; - } - - final long identity = Binder.clearCallingIdentity(); - boolean didTransfer; - try { - didTransfer = mService.transferHostTouchGestureToEmbedded(this, hostWindow, - inputTransferToken); - } finally { - Binder.restoreCallingIdentity(identity); - } - return didTransfer; - } - - @Override public boolean moveFocusToAdjacentWindow(IWindow fromWindow, @FocusDirection int direction) { final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java index 401b2604c28f..86804360f6f4 100644 --- a/services/core/java/com/android/server/wm/SnapshotCache.java +++ b/services/core/java/com/android/server/wm/SnapshotCache.java @@ -19,6 +19,8 @@ import android.annotation.Nullable; import android.util.ArrayMap; import android.window.TaskSnapshot; +import com.android.internal.annotations.GuardedBy; + import java.io.PrintWriter; /** @@ -26,25 +28,31 @@ import java.io.PrintWriter; * @param <TYPE> The basic type, either Task or ActivityRecord */ abstract class SnapshotCache<TYPE extends WindowContainer> { - protected final WindowManagerService mService; + protected final Object mLock = new Object(); + protected final String mName; + + @GuardedBy("mLock") protected final ArrayMap<ActivityRecord, Integer> mAppIdMap = new ArrayMap<>(); + + @GuardedBy("mLock") protected final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>(); - SnapshotCache(WindowManagerService service, String name) { - mService = service; + SnapshotCache(String name) { mName = name; } abstract void putSnapshot(TYPE window, TaskSnapshot snapshot); void clearRunningCache() { - mRunningCache.clear(); + synchronized (mLock) { + mRunningCache.clear(); + } } @Nullable final TaskSnapshot getSnapshot(Integer id) { - synchronized (mService.mGlobalLock) { + synchronized (mLock) { // Try the running cache. final CacheEntry entry = mRunningCache.get(id); if (entry != null) { @@ -56,17 +64,21 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { /** Called when an app token has been removed. */ void onAppRemoved(ActivityRecord activity) { - final Integer id = mAppIdMap.get(activity); - if (id != null) { - removeRunningEntry(id); + synchronized (mLock) { + final Integer id = mAppIdMap.get(activity); + if (id != null) { + removeRunningEntry(id); + } } } /** Called when an app window token's process died. */ void onAppDied(ActivityRecord activity) { - final Integer id = mAppIdMap.get(activity); - if (id != null) { - removeRunningEntry(id); + synchronized (mLock) { + final Integer id = mAppIdMap.get(activity); + if (id != null) { + removeRunningEntry(id); + } } } @@ -75,10 +87,12 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { } void removeRunningEntry(Integer id) { - final CacheEntry entry = mRunningCache.get(id); - if (entry != null) { - mAppIdMap.remove(entry.topApp); - mRunningCache.remove(id); + synchronized (mLock) { + final CacheEntry entry = mRunningCache.get(id); + if (entry != null) { + mAppIdMap.remove(entry.topApp); + mRunningCache.remove(id); + } } } @@ -86,11 +100,14 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { final String doublePrefix = prefix + " "; final String triplePrefix = doublePrefix + " "; pw.println(prefix + "SnapshotCache " + mName); - for (int i = mRunningCache.size() - 1; i >= 0; i--) { - final CacheEntry entry = mRunningCache.valueAt(i); - pw.println(doublePrefix + "Entry token=" + mRunningCache.keyAt(i)); - pw.println(triplePrefix + "topApp=" + entry.topApp); - pw.println(triplePrefix + "snapshot=" + entry.snapshot); + + synchronized (mLock) { + for (int i = mRunningCache.size() - 1; i >= 0; i--) { + final CacheEntry entry = mRunningCache.valueAt(i); + pw.println(doublePrefix + "Entry token=" + mRunningCache.keyAt(i)); + pw.println(triplePrefix + "topApp=" + entry.topApp); + pw.println(triplePrefix + "snapshot=" + entry.snapshot); + } } } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java index 33486ccb995f..b69ac1bb2795 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java @@ -28,19 +28,21 @@ class TaskSnapshotCache extends SnapshotCache<Task> { private final AppSnapshotLoader mLoader; - TaskSnapshotCache(WindowManagerService service, AppSnapshotLoader loader) { - super(service, "Task"); + TaskSnapshotCache(AppSnapshotLoader loader) { + super("Task"); mLoader = loader; } void putSnapshot(Task task, TaskSnapshot snapshot) { - final CacheEntry entry = mRunningCache.get(task.mTaskId); - if (entry != null) { - mAppIdMap.remove(entry.topApp); + synchronized (mLock) { + final CacheEntry entry = mRunningCache.get(task.mTaskId); + if (entry != null) { + mAppIdMap.remove(entry.topApp); + } + final ActivityRecord top = task.getTopMostActivity(); + mAppIdMap.put(top, task.mTaskId); + mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top)); } - final ActivityRecord top = task.getTopMostActivity(); - mAppIdMap.put(top, task.mTaskId); - mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top)); } /** diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index d8e18e47fa89..8b622d28b651 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -68,7 +68,7 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot Environment::getDataSystemCeDirectory); mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider); - initialize(new TaskSnapshotCache(service, new AppSnapshotLoader(mPersistInfoProvider))); + initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider))); final boolean snapshotEnabled = !service.mContext .getResources() diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index ae4c3b9a510a..669c61c4b40c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -1043,6 +1043,15 @@ public abstract class WindowManagerInternal { /** * Clears apps added to collection of apps in which screen capture should be disabled. * + * @param packageInfos set of {@link PackageInfo} whose windows should be unblocked + * from capture. + */ + public abstract void removeBlockScreenCaptureForApps( + @NonNull ArraySet<PackageInfo> packageInfos); + + /** + * Clears all apps added to collection of apps in which screen capture should be disabled. + * * <p> This clears and resets any existing set or added applications from * * {@link #addBlockScreenCaptureForApps(ArraySet)} */ diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 631ebcd2b6e9..3d6bd4fd4edd 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -101,6 +101,7 @@ import static android.window.WindowProviderService.isWindowProviderService; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_EMBEDDED_WINDOWS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; @@ -1344,7 +1345,7 @@ public class WindowManagerService extends IWindowManager.Stub LocalServices.addService(WindowManagerInternal.class, new LocalService()); LocalServices.addService( ImeTargetVisibilityPolicy.class, new ImeTargetVisibilityPolicyImpl()); - mEmbeddedWindowController = new EmbeddedWindowController(mAtmService); + mEmbeddedWindowController = new EmbeddedWindowController(mAtmService, inputManager); mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources( mContext.getResources()); @@ -8632,6 +8633,17 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void removeBlockScreenCaptureForApps(ArraySet<PackageInfo> packageInfos) { + synchronized (mGlobalLock) { + boolean modified = + mSensitiveContentPackages.removeBlockScreenCaptureForApps(packageInfos); + if (modified) { + WindowManagerService.this.refreshScreenCaptureDisabled(); + } + } + } + + @Override public void clearBlockedApps() { synchronized (mGlobalLock) { boolean modified = mSensitiveContentPackages.clearBlockedApps(); @@ -9044,73 +9056,37 @@ public class WindowManagerService extends IWindowManager.Stub null /* region */, clientToken); } - boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow) { - final IBinder windowBinder = embeddedWindow.asBinder(); - final IBinder hostInputChannel, embeddedInputChannel; - synchronized (mGlobalLock) { - final EmbeddedWindowController.EmbeddedWindow ew = - mEmbeddedWindowController.getByWindowToken(windowBinder); - if (ew == null) { - Slog.w(TAG, "Attempt to transfer touch focus from non-existent embedded window"); - return false; - } - final WindowState hostWindowState = ew.getWindowState(); - if (hostWindowState == null) { - Slog.w(TAG, "Attempt to transfer touch focus from embedded window with no" + - " associated host"); - return false; - } - embeddedInputChannel = ew.getInputChannelToken(); - if (embeddedInputChannel == null) { - Slog.w(TAG, "Attempt to transfer touch focus from embedded window with no input" + - " channel"); - return false; - } - hostInputChannel = hostWindowState.mInputChannelToken; - if (hostInputChannel == null) { - Slog.w(TAG, "Attempt to transfer touch focus to a host window with no" + - " input channel"); - return false; - } - return mInputManager.transferTouchFocus(embeddedInputChannel, hostInputChannel); + @Override + public boolean transferTouchGesture(InputTransferToken transferFromToken, + InputTransferToken transferToToken) { + if (transferFromToken == null || transferToToken == null) { + ProtoLog.e(WM_DEBUG_EMBEDDED_WINDOWS, + "transferTouchGesture failed because args transferFromToken or " + + "transferToToken is null"); + return false; } - } - - boolean transferHostTouchGestureToEmbedded(Session session, IWindow hostWindow, - InputTransferToken inputTransferToken) { - final IBinder hostInputChannel, embeddedInputChannel; - synchronized (mGlobalLock) { - final WindowState hostWindowState = windowForClientLocked(session, hostWindow, false); - if (hostWindowState == null) { - Slog.w(TAG, "Attempt to transfer touch gesture with invalid host window"); - return false; - } - final EmbeddedWindowController.EmbeddedWindow ew = - mEmbeddedWindowController.getByInputTransferToken(inputTransferToken); - if (ew == null || ew.mHostWindowState == null) { - Slog.w(TAG, "Attempt to transfer touch gesture to non-existent embedded window"); - return false; - } - if (ew.mHostWindowState.mClient.asBinder() != hostWindow.asBinder()) { - Slog.w(TAG, "Attempt to transfer touch gesture to embedded window not associated" - + " with host window"); - return false; - } - embeddedInputChannel = ew.getInputChannelToken(); - if (embeddedInputChannel == null) { - Slog.w(TAG, "Attempt to transfer touch focus from embedded window with no input" - + " channel"); - return false; - } - hostInputChannel = hostWindowState.mInputChannelToken; - if (hostInputChannel == null) { - Slog.w(TAG, - "Attempt to transfer touch focus to a host window with no input channel"); - return false; + final long identity = Binder.clearCallingIdentity(); + boolean didTransfer; + try { + synchronized (mGlobalLock) { + // If the transferToToken exists in the input to window map, it means the request + // is to transfer from embedded to host. Otherwise, the transferToToken + // represents an embedded window so transfer from host to embedded. + WindowState windowStateTo = mInputToWindowMap.get(transferToToken.mToken); + if (windowStateTo != null) { + didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken, + windowStateTo); + } else { + WindowState windowStateFrom = mInputToWindowMap.get(transferFromToken.mToken); + didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom, + transferToToken); + } } - return mInputManager.transferTouchFocus(hostInputChannel, embeddedInputChannel); + } finally { + Binder.restoreCallingIdentity(identity); } + return didTransfer; } private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid, diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index f56e50e2e9fd..6e993b340352 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1897,7 +1897,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return true; } - if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()) { + if (android.permission.flags.Flags.sensitiveNotificationAppProtection()) { if (mWmService.mSensitiveContentPackages .shouldBlockScreenCaptureForApp(getOwningPackage(), getOwningUid())) { return true; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 9bf41f9c89f6..58e198e532cd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -28,10 +28,13 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUTOFILL; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_BLUETOOTH; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CALLS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION; @@ -50,6 +53,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MICROPHONE; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MTE; @@ -60,6 +64,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PRINTING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS; @@ -73,6 +78,7 @@ 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; @@ -85,6 +91,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WINDOWS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA; 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.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; @@ -236,6 +243,7 @@ import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled; import static android.app.admin.flags.Flags.headlessDeviceOwnerSingleUserEnabled; +import static android.app.admin.flags.Flags.permissionMigrationForZeroTrustImplEnabled; import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled; import static android.app.admin.flags.Flags.assistContentUserRestrictionEnabled; import static android.app.admin.flags.Flags.securityLogV2Enabled; @@ -325,6 +333,7 @@ import android.app.admin.DevicePolicyStringResource; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.FullyManagedDeviceProvisioningParams; +import android.app.admin.IAuditLogEventsCallback; import android.app.admin.IDevicePolicyManager; import android.app.admin.IntegerPolicyValue; import android.app.admin.IntentFilterPolicyKey; @@ -2057,7 +2066,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mLockPatternUtils = injector.newLockPatternUtils(); mLockSettingsInternal = injector.getLockSettingsInternal(); // TODO: why does SecurityLogMonitor need to be created even when mHasFeature == false? - mSecurityLogMonitor = new SecurityLogMonitor(this); + mSecurityLogMonitor = new SecurityLogMonitor(this, mHandler); mHasFeature = mInjector.hasFeature(); mIsWatch = mInjector.getPackageManager() @@ -2715,8 +2724,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void maybeStartSecurityLogMonitorOnActivityManagerReady() { - synchronized (getLockObject()) { - if (mInjector.securityLogIsLoggingEnabled()) { + if (!mInjector.securityLogIsLoggingEnabled()) { + return; + } + + if (securityLogV2Enabled()) { + boolean auditLoggingEnabled = Boolean.TRUE.equals( + mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL)); + boolean securityLoggingEnabled = Boolean.TRUE.equals( + mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL)); + setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); + } else { + synchronized (getLockObject()) { mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); mInjector.runCryptoSelfTest(); maybePauseDeviceWideLoggingLocked(); @@ -15777,7 +15798,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void enforceSecurityLoggingPolicy(boolean enabled) { - enforceLoggingPolicy(enabled); + if (!securityLogV2Enabled()) { + return; + } + Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL); + enforceLoggingPolicy(enabled, Boolean.TRUE.equals(auditLoggingEnabled)); + } + + @Override + public void enforceAuditLoggingPolicy(boolean enabled) { + if (!securityLogV2Enabled()) { + return; + } + Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL); + enforceLoggingPolicy(Boolean.TRUE.equals(securityLoggingEnabled), enabled); } private List<EnforcingUser> getEnforcingUsers(Set<EnforcingAdmin> admins) { @@ -15797,17 +15833,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private void enforceLoggingPolicy(boolean securityLoggingEnabled) { - Slogf.i(LOG_TAG, "Enforcing security logging, securityLoggingEnabled: %b", - securityLoggingEnabled); - SecurityLog.setLoggingEnabledProperty(securityLoggingEnabled); - if (securityLoggingEnabled) { - mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); + private void enforceLoggingPolicy( + boolean securityLoggingEnabled, boolean auditLoggingEnabled) { + Slogf.i(LOG_TAG, "Enforcing logging policy, security: %b audit: %b", + securityLoggingEnabled, auditLoggingEnabled); + SecurityLog.setLoggingEnabledProperty(securityLoggingEnabled || auditLoggingEnabled); + setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); + } + + private void setLoggingConfiguration( + boolean securityLoggingEnabled, boolean auditLoggingEnabled) { + final int loggingEnabledUser = getSecurityLoggingEnabledUser(); + mSecurityLogMonitor.setLoggingParams( + loggingEnabledUser, securityLoggingEnabled, auditLoggingEnabled); + if (securityLoggingEnabled || auditLoggingEnabled) { synchronized (getLockObject()) { maybePauseDeviceWideLoggingLocked(); } - } else { - mSecurityLogMonitor.stop(); } } @@ -16253,7 +16295,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void notifyPendingSystemUpdate(@Nullable SystemUpdateInfo info) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE), + hasCallingOrSelfPermission(NOTIFY_PENDING_SYSTEM_UPDATE), "Only the system update service can broadcast update information"); mInjector.binderWithCleanCallingIdentity(() -> { @@ -16294,12 +16336,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // Send broadcasts to corresponding profile owners if any. for (final int userId : runningUserIds) { + final ComponentName profileOwnerPackage; synchronized (getLockObject()) { - final ComponentName profileOwnerPackage = - mOwners.getProfileOwnerComponent(userId); - if (profileOwnerPackage != null) { - intent.setComponent(profileOwnerPackage); - mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); + profileOwnerPackage = mOwners.getProfileOwnerComponent(userId); + } + if (profileOwnerPackage != null) { + intent.setComponent(profileOwnerPackage); + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); + } + + if (permissionMigrationForZeroTrustImplEnabled()) { + final UserHandle user = UserHandle.of(userId); + final String roleHolderPackage = getRoleHolderPackageNameOnUser( + RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, userId); + if (roleHolderPackage != null) { + broadcastExplicitIntentToPackage(intent, roleHolderPackage, user); } } } @@ -16307,13 +16358,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin) { - Objects.requireNonNull(admin, "ComponentName is null"); - - final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin, String callerPackage) { + if (permissionMigrationForZeroTrustImplEnabled()) { + CallerIdentity caller = getCallerIdentity(admin, callerPackage); + enforcePermissions(new String[] {NOTIFY_PENDING_SYSTEM_UPDATE, + MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}, caller.getPackageName(), + caller.getUserId()); + } else { + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity caller = getCallerIdentity(admin); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + } return mOwners.getSystemUpdateInfo(); } @@ -17873,6 +17930,82 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public void setAuditLogEnabled(String callingPackage, boolean enabled) { + if (!mHasFeature) { + return; + } + final CallerIdentity caller = getCallerIdentity(callingPackage); + + if (!securityLogV2Enabled()) { + throw new UnsupportedOperationException("Audit log not enabled"); + } + + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + null /* admin */, + MANAGE_DEVICE_POLICY_AUDIT_LOGGING, + caller.getPackageName(), + caller.getUserId()); + if (enabled) { + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.AUDIT_LOGGING, + admin, + new BooleanPolicyValue(true)); + } else { + mDevicePolicyEngine.removeGlobalPolicy( + PolicyDefinition.AUDIT_LOGGING, + admin); + mSecurityLogMonitor.setAuditLogEventsCallback(caller.getUid(), null /* callback */); + } + } + + @Override + public boolean isAuditLogEnabled(String callingPackage) { + if (!mHasFeature) { + return false; + } + + if (!securityLogV2Enabled()) { + throw new UnsupportedOperationException("Audit log not enabled"); + } + + final CallerIdentity caller = getCallerIdentity(callingPackage); + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + null /* admin */, + MANAGE_DEVICE_POLICY_AUDIT_LOGGING, + caller.getPackageName(), + caller.getUserId()); + + Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.AUDIT_LOGGING, admin); + + return Boolean.TRUE.equals(policy); + } + + @Override + public void setAuditLogEventsCallback(String callingPackage, IAuditLogEventsCallback callback) { + if (!mHasFeature) { + return; + } + + final CallerIdentity caller = getCallerIdentity(callingPackage); + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + null /* admin */, + MANAGE_DEVICE_POLICY_AUDIT_LOGGING, + caller.getPackageName(), + caller.getUserId()); + + Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.AUDIT_LOGGING, admin); + + if (!Boolean.TRUE.equals(policy)) { + throw new IllegalStateException( + "Managing app has to enable audit log before setting events callback"); + } + + mSecurityLogMonitor.setAuditLogEventsCallback(caller.getUid(), callback); + } + + @Override public long forceSecurityLogs() { Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) || hasCallingOrSelfPermission(permission.FORCE_DEVICE_POLICY_MANAGER_LOGS), @@ -20817,14 +20950,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(callerPackage); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + if (permissionMigrationForZeroTrustImplEnabled()) { + enforcePermission(MANAGE_DEVICE_POLICY_CERTIFICATES, caller.getPackageName()); + } else { + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller) + || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + } synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked( caller.getUserId()); - final String esid = requiredAdmin.mEnrollmentSpecificId; + final String esid = requiredAdmin != null ? requiredAdmin.mEnrollmentSpecificId : null; return esid != null ? esid : ""; } } @@ -21962,6 +22099,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public boolean isTheftDetectionTriggered(String callerPackageName) { + final CallerIdentity caller = getCallerIdentity(callerPackageName); + if (!android.app.admin.flags.Flags.deviceTheftImplEnabled()) { + return false; + } + enforcePermission(MANAGE_DEVICE_POLICY_THEFT_DETECTION, caller.getPackageName(), + caller.getUserId()); + + //STOPSHIP: replace 1<<9 with + // LockPatternUtils.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST once ag/26042068 lands + return 0 != (mLockPatternUtils.getStrongAuthForUser(caller.getUserId()) & (1 << 9)); + } + + @Override public void setWifiSsidPolicy(String callerPackageName, WifiSsidPolicy policy) { CallerIdentity caller; @@ -22402,14 +22553,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } - // Permission that will need to be created in V. - private static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = - "manage_device_policy_block_uninstall"; - private static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = - "manage_device_policy_camera_toggle"; - private static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = - "manage_device_policy_microphone_toggle"; - // DPC types private static final int NOT_A_DPC = -1; private static final int DEFAULT_DEVICE_OWNER = 0; @@ -22495,7 +22638,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_WINDOWS, MANAGE_DEVICE_POLICY_WIPE_DATA, SET_TIME, - SET_TIME_ZONE + SET_TIME_ZONE, + MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES ); private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of( MANAGE_DEVICE_POLICY_ACROSS_USERS, @@ -22559,7 +22703,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, MANAGE_DEVICE_POLICY_TIME, MANAGE_DEVICE_POLICY_VPN, - MANAGE_DEVICE_POLICY_WIPE_DATA + MANAGE_DEVICE_POLICY_WIPE_DATA, + MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES ); /** diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 3474db3c7c1f..1247f900260a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -141,6 +141,13 @@ final class PolicyDefinition<V> { PolicyEnforcerCallbacks::enforceSecurityLogging, new BooleanPolicySerializer()); + static PolicyDefinition<Boolean> AUDIT_LOGGING = new PolicyDefinition<>( + new NoArgsPolicyKey(DevicePolicyIdentifiers.AUDIT_LOGGING_POLICY), + TRUE_MORE_RESTRICTIVE, + POLICY_FLAG_GLOBAL_ONLY_POLICY, + PolicyEnforcerCallbacks::enforceAuditLogging, + new BooleanPolicySerializer()); + static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>( new NoArgsPolicyKey(DevicePolicyIdentifiers.LOCK_TASK_POLICY), new TopPriority<>(List.of( @@ -365,6 +372,8 @@ final class PolicyDefinition<V> { GENERIC_PERMISSION_GRANT); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.SECURITY_LOGGING_POLICY, SECURITY_LOGGING); + POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUDIT_LOGGING_POLICY, + AUDIT_LOGGING); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.LOCK_TASK_POLICY, LOCK_TASK); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY, USER_CONTROLLED_DISABLED_PACKAGES); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 4aaefa670ea2..54242ab279b0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -136,6 +136,14 @@ final class PolicyEnforcerCallbacks { return true; } + static boolean enforceAuditLogging( + @Nullable Boolean value, @NonNull Context context, int userId, + @NonNull PolicyKey policyKey) { + final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); + dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value)); + return true; + } + static boolean setLockTask( @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) { List<String> packages = Collections.emptyList(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index 7a4454b11fce..02f39189ae72 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -16,22 +16,32 @@ package com.android.server.devicepolicy; +import static android.app.admin.flags.Flags.securityLogV2Enabled; + import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import android.app.admin.DeviceAdminReceiver; +import android.app.admin.IAuditLogEventsCallback; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; +import android.os.Handler; +import android.os.IBinder; import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.utils.Slogf; import java.io.IOException; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -53,15 +63,11 @@ class SecurityLogMonitor implements Runnable { private int mEnabledUser; - SecurityLogMonitor(DevicePolicyManagerService service) { - this(service, 0 /* id */); - } - - @VisibleForTesting - SecurityLogMonitor(DevicePolicyManagerService service, long id) { + SecurityLogMonitor(DevicePolicyManagerService service, Handler handler) { mService = service; - mId = id; + mId = 0; mLastForceNanos = System.nanoTime(); + mHandler = handler; } private static final boolean DEBUG = false; // STOPSHIP if true. @@ -118,6 +124,9 @@ class SecurityLogMonitor implements Runnable { @GuardedBy("mLock") private boolean mCriticalLevelLogged = false; + private boolean mLegacyLogEnabled; + private boolean mAuditLogEnabled; + /** * Last events fetched from log to check for overlap between batches. We can leave it empty if * we are sure there will be no overlap anymore, e.g. when we get empty batch. @@ -143,6 +152,40 @@ class SecurityLogMonitor implements Runnable { private long mLastForceNanos = 0; /** + * Handler shared with DPMS. + */ + private final Handler mHandler; + + /** + * Oldest events get purged from audit log buffer if total number exceeds this value. + */ + private static final int MAX_AUDIT_LOG_EVENTS = 10000; + /** + * Events older than this get purged from audit log buffer. + */ + private static final long MAX_AUDIT_LOG_EVENT_AGE_NS = TimeUnit.HOURS.toNanos(8); + + /** + * Audit log callbacks keyed by UID. The code should maintain the following invariant: all + * callbacks in this map have received (or are scheduled to receive) all events in + * mAuditLogEventsBuffer. To ensure this, before a callback is put into this map, it must be + * scheduled to receive all the events in the buffer, and conversely, before a new chunk of + * events is added to the buffer, it must be scheduled to be sent to all callbacks already in + * this list. All scheduling should happen on mHandler, so that they aren't reordered, and + * while holding the lock. This ensures that no callback misses an event or receives a duplicate + * or out of order events. + */ + @GuardedBy("mLock") + private final SparseArray<IAuditLogEventsCallback> mAuditLogCallbacks = new SparseArray<>(); + + /** + * Audit log event buffer. It is shrunk automatically whenever either there are too many events + * or the oldest one is too old. + */ + @GuardedBy("mLock") + private final ArrayDeque<SecurityEvent> mAuditLogEventBuffer = new ArrayDeque<>(); + + /** * Start security logging. * * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all @@ -154,18 +197,8 @@ class SecurityLogMonitor implements Runnable { mLock.lock(); try { if (mMonitorThread == null) { - mPendingLogs = new ArrayList<>(); - mCriticalLevelLogged = false; - mId = 0; - mAllowedToRetrieve = false; - mNextAllowedRetrievalTimeMillis = -1; - mPaused = false; - - mMonitorThread = new Thread(this); - mMonitorThread.start(); - - SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED); - Slog.i(TAG, "Security log monitor thread started"); + resetLegacyBufferLocked(); + startMonitorThreadLocked(); } else { Slog.i(TAG, "Security log monitor thread is already running"); } @@ -176,29 +209,82 @@ class SecurityLogMonitor implements Runnable { void stop() { Slog.i(TAG, "Stopping security logging."); - SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED); mLock.lock(); try { if (mMonitorThread != null) { - mMonitorThread.interrupt(); - try { - mMonitorThread.join(TimeUnit.SECONDS.toMillis(5)); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted while waiting for thread to stop", e); - } - // Reset state and clear buffer - mPendingLogs = new ArrayList<>(); - mId = 0; - mAllowedToRetrieve = false; - mNextAllowedRetrievalTimeMillis = -1; - mPaused = false; - mMonitorThread = null; + stopMonitorThreadLocked(); + resetLegacyBufferLocked(); } } finally { mLock.unlock(); } } + void setLoggingParams(int enabledUser, boolean legacyLogEnabled, boolean auditLogEnabled) { + Slogf.i(TAG, "Setting logging params, user = %d -> %d, legacy: %b -> %b, audit %b -> %b", + mEnabledUser, enabledUser, mLegacyLogEnabled, legacyLogEnabled, mAuditLogEnabled, + auditLogEnabled); + mLock.lock(); + try { + mEnabledUser = enabledUser; + if (mMonitorThread == null && (legacyLogEnabled || auditLogEnabled)) { + startMonitorThreadLocked(); + } else if (mMonitorThread != null && !legacyLogEnabled && !auditLogEnabled) { + stopMonitorThreadLocked(); + } + + if (mLegacyLogEnabled != legacyLogEnabled) { + resetLegacyBufferLocked(); + mLegacyLogEnabled = legacyLogEnabled; + } + + if (mAuditLogEnabled != auditLogEnabled) { + resetAuditBufferLocked(); + mAuditLogEnabled = auditLogEnabled; + } + } finally { + mLock.unlock(); + } + + } + + @GuardedBy("mLock") + private void startMonitorThreadLocked() { + mId = 0; + mPaused = false; + mMonitorThread = new Thread(this); + mMonitorThread.start(); + SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED); + Slog.i(TAG, "Security log monitor thread started"); + } + + @GuardedBy("mLock") + private void stopMonitorThreadLocked() { + mMonitorThread.interrupt(); + try { + mMonitorThread.join(TimeUnit.SECONDS.toMillis(5)); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for thread to stop", e); + } + mMonitorThread = null; + SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED); + } + + @GuardedBy("mLock") + private void resetLegacyBufferLocked() { + mPendingLogs = new ArrayList<>(); + mCriticalLevelLogged = false; + mAllowedToRetrieve = false; + mNextAllowedRetrievalTimeMillis = -1; + Slog.i(TAG, "Legacy buffer reset."); + } + + @GuardedBy("mLock") + private void resetAuditBufferLocked() { + mAuditLogEventBuffer.clear(); + mAuditLogCallbacks.clear(); + } + /** * If logs are being collected, keep collecting them but stop notifying the device owner that * new logs are available (since they cannot be retrieved). @@ -338,8 +424,7 @@ class SecurityLogMonitor implements Runnable { */ @GuardedBy("mLock") private void mergeBatchLocked(final ArrayList<SecurityEvent> newLogs) { - // Reserve capacity so that copying doesn't occur. - mPendingLogs.ensureCapacity(mPendingLogs.size() + newLogs.size()); + List<SecurityEvent> dedupedLogs = new ArrayList<>(); // Run through the first events of the batch to check if there is an overlap with previous // batch and if so, skip overlapping events. Events are sorted by timestamp, so we can // compare it in linear time by advancing two pointers, one for each batch. @@ -358,8 +443,7 @@ class SecurityLogMonitor implements Runnable { if (lastNanos > currentNanos) { // New event older than the last we've seen so far, must be due to reordering. if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos); - assignLogId(curEvent); - mPendingLogs.add(curEvent); + dedupedLogs.add(curEvent); curPos++; } else if (lastNanos < currentNanos) { if (DEBUG) Slog.d(TAG, "Event disappeared from the overlap: " + lastNanos); @@ -371,8 +455,7 @@ class SecurityLogMonitor implements Runnable { if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos); } else { // Wow, what a coincidence, or probably the clock is too coarse. - assignLogId(curEvent); - mPendingLogs.add(curEvent); + dedupedLogs.add(curEvent); if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos); } lastPos++; @@ -380,12 +463,23 @@ class SecurityLogMonitor implements Runnable { } } // Assign an id to the new logs, after the overlap with mLastEvents. - List<SecurityEvent> idLogs = newLogs.subList(curPos, newLogs.size()); - for (SecurityEvent event : idLogs) { + dedupedLogs.addAll(newLogs.subList(curPos, newLogs.size())); + for (SecurityEvent event : dedupedLogs) { assignLogId(event); } + + if (!securityLogV2Enabled() || mLegacyLogEnabled) { + addToLegacyBuffer(dedupedLogs); + } + + if (securityLogV2Enabled() && mAuditLogEnabled) { + addAuditLogEvents(dedupedLogs); + } + } + + private void addToLegacyBuffer(List<SecurityEvent> dedupedLogs) { // Save the rest of the new batch. - mPendingLogs.addAll(idLogs); + mPendingLogs.addAll(dedupedLogs); checkCriticalLevel(); @@ -453,7 +547,10 @@ class SecurityLogMonitor implements Runnable { saveLastEvents(newLogs); newLogs.clear(); - notifyDeviceOwnerOrProfileOwnerIfNeeded(force); + + if (!securityLogV2Enabled() || mLegacyLogEnabled) { + notifyDeviceOwnerOrProfileOwnerIfNeeded(force); + } } catch (IOException e) { Log.e(TAG, "Failed to read security log", e); } catch (InterruptedException e) { @@ -532,4 +629,121 @@ class SecurityLogMonitor implements Runnable { return 0; } } + + public void setAuditLogEventsCallback(int uid, IAuditLogEventsCallback callback) { + mLock.lock(); + try { + if (callback == null) { + mAuditLogCallbacks.remove(uid); + Slogf.i(TAG, "Cleared audit log callback for UID %d", uid); + return; + } + // Create a copy while holding the lock, so that that new events are not added + // resulting in duplicates. + final List<SecurityEvent> events = new ArrayList<>(mAuditLogEventBuffer); + scheduleSendAuditLogs(uid, callback, events); + mAuditLogCallbacks.append(uid, callback); + } finally { + mLock.unlock(); + } + Slogf.i(TAG, "Set audit log callback for UID %d", uid); + } + + private void addAuditLogEvents(List<SecurityEvent> events) { + mLock.lock(); + try { + if (mPaused) { + // TODO: maybe we need to stash the logs in some temp buffer wile paused so that + // they can be accessed after affiliation is fixed. + return; + } + if (!events.isEmpty()) { + for (int i = 0; i < mAuditLogCallbacks.size(); i++) { + final int uid = mAuditLogCallbacks.keyAt(i); + scheduleSendAuditLogs(uid, mAuditLogCallbacks.valueAt(i), events); + } + } + if (DEBUG) { + Slogf.d(TAG, "Adding audit %d events to % already present in the buffer", + events.size(), mAuditLogEventBuffer.size()); + } + mAuditLogEventBuffer.addAll(events); + trimAuditLogBufferLocked(); + if (DEBUG) { + Slogf.d(TAG, "Audit event buffer size after trimming: %d", + mAuditLogEventBuffer.size()); + } + } finally { + mLock.unlock(); + } + } + + @GuardedBy("mLock") + private void trimAuditLogBufferLocked() { + long nowNanos = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + + final Iterator<SecurityEvent> iterator = mAuditLogEventBuffer.iterator(); + while (iterator.hasNext()) { + final SecurityEvent event = iterator.next(); + if (mAuditLogEventBuffer.size() <= MAX_AUDIT_LOG_EVENTS + && nowNanos - event.getTimeNanos() <= MAX_AUDIT_LOG_EVENT_AGE_NS) { + break; + } + + iterator.remove(); + } + } + + private void scheduleSendAuditLogs( + int uid, IAuditLogEventsCallback callback, List<SecurityEvent> events) { + if (DEBUG) { + Slogf.d(TAG, "Scheduling to send %d audit log events to UID %d", events.size(), uid); + } + mHandler.post(() -> sendAuditLogs(uid, callback, events)); + } + + private void sendAuditLogs( + int uid, IAuditLogEventsCallback callback, List<SecurityEvent> events) { + try { + final int size = events.size(); + if (DEBUG) { + Slogf.d(TAG, "Sending %d audit log events to UID %d", size, uid); + } + callback.onNewAuditLogEvents(events); + if (DEBUG) { + Slogf.d(TAG, "Sent %d audit log events to UID %d", size, uid); + } + } catch (RemoteException e) { + Slogf.e(TAG, e, "Failed to invoke audit log callback for UID %d", uid); + removeAuditLogEventsCallbackIfDead(uid, callback); + } + } + + private void removeAuditLogEventsCallbackIfDead(int uid, IAuditLogEventsCallback callback) { + final IBinder binder = callback.asBinder(); + if (binder.isBinderAlive()) { + Slog.i(TAG, "Callback binder is still alive, not removing."); + return; + } + + mLock.lock(); + try { + int index = mAuditLogCallbacks.indexOfKey(uid); + if (index < 0) { + Slogf.i(TAG, "Callback not registered for UID %d, nothing to remove", uid); + return; + } + + final IBinder storedBinder = mAuditLogCallbacks.valueAt(index).asBinder(); + if (!storedBinder.equals(binder)) { + Slogf.i(TAG, "Callback is already replaced for UID %d, not removing", uid); + return; + } + + Slogf.i(TAG, "Removing callback for UID %d", uid); + mAuditLogCallbacks.removeAt(index); + } finally { + mLock.unlock(); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 1d89d1722f74..ee758dbd0516 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -107,6 +107,7 @@ import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockSettingsInternal; +import com.android.server.adaptiveauth.AdaptiveAuthService; import com.android.server.am.ActivityManagerService; import com.android.server.appbinding.AppBindingService; import com.android.server.appop.AppOpMigrationHelper; @@ -2615,6 +2616,12 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(AuthService.class); t.traceEnd(); + if (android.adaptiveauth.Flags.enableAdaptiveAuth()) { + t.traceBegin("StartAdaptiveAuthService"); + mSystemServiceManager.startService(AdaptiveAuthService.class); + t.traceEnd(); + } + if (!isWatch) { // We don't run this on watches as there are no plans to use the data logged // on watch devices. @@ -3014,7 +3021,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } - if (com.android.server.notification.Flags.sensitiveNotificationAppProtection() + if (android.permission.flags.Flags.sensitiveNotificationAppProtection() || android.view.flags.Flags.sensitiveContentAppProtection()) { t.traceBegin("StartSensitiveContentProtectionManager"); mSystemServiceManager.startService(SensitiveContentProtectionManagerService.class); 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 558827631dfe..cb3ee7307e36 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 @@ -46,6 +46,7 @@ import com.android.server.pm.KnownPackages import com.android.server.pm.parsing.PackageInfoUtils import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState +import libcore.util.EmptyArray class AppIdPermissionPolicy : SchemePolicy() { private val persistence = AppIdPermissionPersistence() @@ -73,40 +74,42 @@ class AppIdPermissionPolicy : SchemePolicy() { } override fun MutateStateScope.onInitialized() { - newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) -> - val oldPermission = newState.systemState.permissions[permissionName] - val newPermission = - if (oldPermission != null) { - if (permissionEntry.gids != null) { - oldPermission.copy( - gids = permissionEntry.gids, - areGidsPerUser = permissionEntry.perUser - ) - } else { - return@forEach - } - } else { - @Suppress("DEPRECATION") - val permissionInfo = - PermissionInfo().apply { - name = permissionName - packageName = PLATFORM_PACKAGE_NAME - protectionLevel = PermissionInfo.PROTECTION_SIGNATURE + if (!Flags.newPermissionGidEnabled()) { + newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) -> + val oldPermission = newState.systemState.permissions[permissionName] + val newPermission = + if (oldPermission != null) { + if (permissionEntry.gids != null) { + oldPermission.copy( + gids = permissionEntry.gids, + areGidsPerUser = permissionEntry.perUser + ) + } else { + return@forEach } - if (permissionEntry.gids != null) { - Permission( - permissionInfo, - false, - Permission.TYPE_CONFIG, - 0, - permissionEntry.gids, - permissionEntry.perUser - ) } else { - Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + @Suppress("DEPRECATION") + val permissionInfo = + PermissionInfo().apply { + name = permissionName + packageName = PLATFORM_PACKAGE_NAME + protectionLevel = PermissionInfo.PROTECTION_SIGNATURE + } + if (permissionEntry.gids != null) { + Permission( + permissionInfo, + false, + Permission.TYPE_CONFIG, + 0, + permissionEntry.gids, + permissionEntry.perUser + ) + } else { + Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + } } - } - newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission + newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission + } } } @@ -459,7 +462,7 @@ class AppIdPermissionPolicy : SchemePolicy() { ) return@forEachIndexed } - val newPermission = + var newPermission = if (oldPermission != null && newPackageName != oldPermission.packageName) { val oldPackageName = oldPermission.packageName // Only allow system apps to redefine non-system permissions. @@ -582,6 +585,24 @@ class AppIdPermissionPolicy : SchemePolicy() { ) } } + if (Flags.newPermissionGidEnabled()) { + var gids = EmptyArray.INT + var areGidsPerUser = false + if (!parsedPermission.isTree && packageState.isSystem) { + newState.externalState.configPermissions[permissionName]?.let { + gids = it.gids + areGidsPerUser = it.perUser + } + } + newPermission = Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId, + gids, + areGidsPerUser + ) + } if (parsedPermission.isTree) { newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index 67f66de71d39..0704c8ffca25 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -466,7 +466,7 @@ class PermissionService(private val service: AccessCheckingService) : return size } - override fun checkUidPermission(uid: Int, permissionName: String, deviceId: Int): Int { + override fun checkUidPermission(uid: Int, permissionName: String, deviceId: String): Int { val userId = UserHandle.getUserId(uid) if (!userManagerInternal.exists(userId)) { return PackageManager.PERMISSION_DENIED @@ -489,15 +489,9 @@ class PermissionService(private val service: AccessCheckingService) : return PackageManager.PERMISSION_DENIED } - val persistentDeviceId = getPersistentDeviceId(deviceId) - if (persistentDeviceId == null) { - Slog.e(LOG_TAG, "Cannot find persistent device id for $deviceId.") - return PackageManager.PERMISSION_DENIED - } - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, persistentDeviceId) + isPermissionGranted(packageState, userId, permissionName, deviceId) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED @@ -531,7 +525,7 @@ class PermissionService(private val service: AccessCheckingService) : override fun checkPermission( packageName: String, permissionName: String, - persistentDeviceId: String, + deviceId: String, userId: Int ): Int { if (!userManagerInternal.exists(userId)) { @@ -545,9 +539,7 @@ class PermissionService(private val service: AccessCheckingService) : ?: return PackageManager.PERMISSION_DENIED val isPermissionGranted = - service.getState { - isPermissionGranted(packageState, userId, permissionName, persistentDeviceId) - } + service.getState { isPermissionGranted(packageState, userId, permissionName, deviceId) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -565,21 +557,13 @@ class PermissionService(private val service: AccessCheckingService) : packageState: PackageState, userId: Int, permissionName: String, - persistentDeviceId: String + deviceId: String ): Boolean { val appId = packageState.appId // Note that instant apps can't have shared UIDs, so we only need to check the current // package state. val isInstantApp = packageState.getUserStateOrDefault(userId).isInstantApp - if ( - isSinglePermissionGranted( - appId, - userId, - isInstantApp, - permissionName, - persistentDeviceId - ) - ) { + if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName, deviceId)) { return true } @@ -591,7 +575,7 @@ class PermissionService(private val service: AccessCheckingService) : userId, isInstantApp, fullerPermissionName, - persistentDeviceId + deviceId ) ) { return true @@ -606,9 +590,9 @@ class PermissionService(private val service: AccessCheckingService) : userId: Int, isInstantApp: Boolean, permissionName: String, - persistentDeviceId: String, + deviceId: String, ): Boolean { - val flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, persistentDeviceId) + val flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (!PermissionFlags.isPermissionGranted(flags)) { return false } @@ -689,22 +673,16 @@ class PermissionService(private val service: AccessCheckingService) : override fun grantRuntimePermission( packageName: String, permissionName: String, - persistentDeviceId: String, + deviceId: String, userId: Int ) { - setRuntimePermissionGranted( - packageName, - userId, - permissionName, - persistentDeviceId, - isGranted = true - ) + setRuntimePermissionGranted(packageName, userId, permissionName, deviceId, isGranted = true) } override fun revokeRuntimePermission( packageName: String, permissionName: String, - persistentDeviceId: String, + deviceId: String, userId: Int, reason: String? ) { @@ -712,7 +690,7 @@ class PermissionService(private val service: AccessCheckingService) : packageName, userId, permissionName, - persistentDeviceId, + deviceId, isGranted = false, revokeReason = reason ) @@ -740,7 +718,7 @@ class PermissionService(private val service: AccessCheckingService) : packageName: String, userId: Int, permissionName: String, - persistentDeviceId: String, + deviceId: String, isGranted: Boolean, skipKillUid: Boolean = false, revokeReason: String? = null @@ -765,7 +743,7 @@ class PermissionService(private val service: AccessCheckingService) : (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") + ", userId = $userId," + " callingUid = $callingUidName ($callingUid))," + - " persistentDeviceId = $persistentDeviceId", + " deviceId = $deviceId", RuntimeException() ) } @@ -835,7 +813,7 @@ class PermissionService(private val service: AccessCheckingService) : packageState, userId, permissionName, - persistentDeviceId, + deviceId, isGranted, canManageRolePermission, overridePolicyFixed, @@ -923,7 +901,7 @@ class PermissionService(private val service: AccessCheckingService) : packageState: PackageState, userId: Int, permissionName: String, - persistentDeviceId: String, + deviceId: String, isGranted: Boolean, canManageRolePermission: Boolean, overridePolicyFixed: Boolean, @@ -982,8 +960,7 @@ class PermissionService(private val service: AccessCheckingService) : } val appId = packageState.appId - val oldFlags = - getPermissionFlagsWithPolicy(appId, userId, permissionName, persistentDeviceId) + val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) { if (reportError) { @@ -1055,7 +1032,7 @@ class PermissionService(private val service: AccessCheckingService) : return } - setPermissionFlagsWithPolicy(appId, userId, permissionName, persistentDeviceId, newFlags) + setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) if (permission.isRuntime) { val action = @@ -1089,7 +1066,7 @@ class PermissionService(private val service: AccessCheckingService) : override fun getPermissionFlags( packageName: String, permissionName: String, - persistentDeviceId: String, + deviceId: String, userId: Int, ): Int { if (!userManagerInternal.exists(userId)) { @@ -1125,12 +1102,7 @@ class PermissionService(private val service: AccessCheckingService) : } val flags = - getPermissionFlagsWithPolicy( - packageState.appId, - userId, - permissionName, - persistentDeviceId - ) + getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId) return PermissionFlags.toApiFlags(flags) } @@ -1138,7 +1110,7 @@ class PermissionService(private val service: AccessCheckingService) : override fun getAllPermissionStates( packageName: String, - persistentDeviceId: String, + deviceId: String, userId: Int ): Map<String, PermissionState> { if (!userManagerInternal.exists(userId)) { @@ -1165,14 +1137,15 @@ class PermissionService(private val service: AccessCheckingService) : val permissionFlagsMap = service.getState { - if (persistentDeviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) { + if (deviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) { with(policy) { getAllPermissionFlags(packageState.appId, userId) } } else { with(devicePolicy) { - getAllPermissionFlags(packageState.appId, persistentDeviceId, userId) + getAllPermissionFlags(packageState.appId, deviceId, userId) } } - } ?: return emptyMap() + } + ?: return emptyMap() val permissionStates = ArrayMap<String, PermissionState>() permissionFlagsMap.forEachIndexed { _, permissionName, flags -> @@ -1186,7 +1159,7 @@ class PermissionService(private val service: AccessCheckingService) : override fun isPermissionRevokedByPolicy( packageName: String, permissionName: String, - deviceId: Int, + deviceId: String, userId: Int ): Boolean { if (!userManagerInternal.exists(userId)) { @@ -1207,24 +1180,13 @@ class PermissionService(private val service: AccessCheckingService) : } ?: return false - val persistentDeviceId = getPersistentDeviceId(deviceId) - if (persistentDeviceId == null) { - Slog.w(LOG_TAG, "Cannot find persistent device Id for $deviceId") - return false - } - service.getState { - if (isPermissionGranted(packageState, userId, permissionName, persistentDeviceId)) { + if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { return false } val flags = - getPermissionFlagsWithPolicy( - packageState.appId, - userId, - permissionName, - persistentDeviceId - ) + getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId) return flags.hasBits(PermissionFlags.POLICY_FIXED) } @@ -1248,7 +1210,7 @@ class PermissionService(private val service: AccessCheckingService) : override fun shouldShowRequestPermissionRationale( packageName: String, permissionName: String, - deviceId: Int, + deviceId: String, userId: Int, ): Boolean { if (!userManagerInternal.exists(userId)) { @@ -1274,19 +1236,13 @@ class PermissionService(private val service: AccessCheckingService) : return false } - val persistentDeviceId = getPersistentDeviceId(deviceId) - if (persistentDeviceId == null) { - Slog.w(LOG_TAG, "Cannot find persistent device Id for $deviceId") - return false - } - val flags: Int service.getState { - if (isPermissionGranted(packageState, userId, permissionName, persistentDeviceId)) { + if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { return false } - flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, persistentDeviceId) + flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) } if (flags.hasAnyBit(UNREQUESTABLE_MASK)) { return false @@ -1325,7 +1281,7 @@ class PermissionService(private val service: AccessCheckingService) : flagMask: Int, flagValues: Int, enforceAdjustPolicyPermission: Boolean, - persistentDeviceId: String, + deviceId: String, userId: Int ) { val callingUid = Binder.getCallingUid() @@ -1351,7 +1307,7 @@ class PermissionService(private val service: AccessCheckingService) : "updatePermissionFlags(packageName = $packageName," + " permissionName = $permissionName, flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " persistentDeviceId = $persistentDeviceId," + + " deviceId = $deviceId," + " callingUid = $callingUidName ($callingUid))", RuntimeException() ) @@ -1441,7 +1397,7 @@ class PermissionService(private val service: AccessCheckingService) : appId, userId, permissionName, - persistentDeviceId, + deviceId, flagMask, flagValues, canUpdateSystemFlags, @@ -1527,7 +1483,7 @@ class PermissionService(private val service: AccessCheckingService) : appId: Int, userId: Int, permissionName: String, - persistentDeviceId: String, + deviceId: String, flagMask: Int, flagValues: Int, canUpdateSystemFlags: Boolean, @@ -1561,8 +1517,7 @@ class PermissionService(private val service: AccessCheckingService) : return } - val oldFlags = - getPermissionFlagsWithPolicy(appId, userId, permissionName, persistentDeviceId) + val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (!isPermissionRequested && oldFlags == 0) { Slog.w( LOG_TAG, @@ -1573,7 +1528,7 @@ class PermissionService(private val service: AccessCheckingService) : } val newFlags = PermissionFlags.updateFlags(permission, oldFlags, flagMask, flagValues) - setPermissionFlagsWithPolicy(appId, userId, permissionName, persistentDeviceId, newFlags) + setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) } override fun getAllowlistedRestrictedPermissions( @@ -1648,11 +1603,11 @@ class PermissionService(private val service: AccessCheckingService) : appId: Int, userId: Int, permissionName: String, - persistentDeviceId: String, + deviceId: String, ): Int { return if ( !Flags.deviceAwarePermissionApisEnabled() || - persistentDeviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT + deviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT ) { with(policy) { getPermissionFlags(appId, userId, permissionName) } } else { @@ -1664,9 +1619,7 @@ class PermissionService(private val service: AccessCheckingService) : ) return with(policy) { getPermissionFlags(appId, userId, permissionName) } } - with(devicePolicy) { - getPermissionFlags(appId, persistentDeviceId, userId, permissionName) - } + with(devicePolicy) { getPermissionFlags(appId, deviceId, userId, permissionName) } } } @@ -1674,12 +1627,12 @@ class PermissionService(private val service: AccessCheckingService) : appId: Int, userId: Int, permissionName: String, - persistentDeviceId: String, + deviceId: String, flags: Int ): Boolean { return if ( !Flags.deviceAwarePermissionApisEnabled() || - persistentDeviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT + deviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT ) { with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } else { @@ -1693,23 +1646,11 @@ class PermissionService(private val service: AccessCheckingService) : } with(devicePolicy) { - setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags) + setPermissionFlags(appId, deviceId, userId, permissionName, flags) } } } - private fun getPersistentDeviceId(deviceId: Int): String? { - if (deviceId == Context.DEVICE_ID_DEFAULT) { - return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT - } - - if (virtualDeviceManagerInternal == null) { - virtualDeviceManagerInternal = - LocalServices.getService(VirtualDeviceManagerInternal::class.java) - } - return virtualDeviceManagerInternal?.getPersistentIdForDevice(deviceId) - } - /** * This method does not enforce checks on the caller, should only be called after required * checks. @@ -2270,9 +2211,9 @@ class PermissionService(private val service: AccessCheckingService) : userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { _, - persistentDeviceId, + deviceId, devicePermissionFlags -> - println("Permissions (Device $persistentDeviceId):") + println("Permissions (Device $deviceId):") withIndent { devicePermissionFlags.forEachIndexed { _, permissionName, flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) @@ -2415,9 +2356,8 @@ class PermissionService(private val service: AccessCheckingService) : with(devicePolicy) { trimDevicePermissionStates(persistentDeviceIds) } } } - virtualDeviceManagerInternal?.registerPersistentDeviceIdRemovedListener { persistentDeviceId - -> - service.mutateState { with(devicePolicy) { onDeviceIdRemoved(persistentDeviceId) } } + virtualDeviceManagerInternal?.registerPersistentDeviceIdRemovedListener { deviceId -> + service.mutateState { with(devicePolicy) { onDeviceIdRemoved(deviceId) } } } permissionControllerManager = @@ -2764,7 +2704,7 @@ class PermissionService(private val service: AccessCheckingService) : override fun onDevicePermissionFlagsChanged( appId: Int, userId: Int, - persistentDeviceId: String, + deviceId: String, permissionName: String, oldFlags: Int, newFlags: Int @@ -2787,8 +2727,7 @@ class PermissionService(private val service: AccessCheckingService) : permissionName in NOTIFICATIONS_PERMISSIONS && runtimePermissionRevokedUids.get(uid, true) } - runtimePermissionChangedUidDevices.getOrPut(uid) { mutableSetOf() } += - persistentDeviceId + runtimePermissionChangedUidDevices.getOrPut(uid) { mutableSetOf() } += deviceId } if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) { @@ -2803,8 +2742,8 @@ class PermissionService(private val service: AccessCheckingService) : } runtimePermissionChangedUidDevices.forEachIndexed { _, uid, persistentDeviceIds -> - persistentDeviceIds.forEach { persistentDeviceId -> - onPermissionsChangeListeners.onPermissionsChanged(uid, persistentDeviceId) + persistentDeviceIds.forEach { deviceId -> + onPermissionsChangeListeners.onPermissionsChanged(uid, deviceId) } } runtimePermissionChangedUidDevices.clear() @@ -2844,8 +2783,11 @@ class PermissionService(private val service: AccessCheckingService) : private fun isAppBackupAndRestoreRunning(uid: Int): Boolean { if ( - checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != - PackageManager.PERMISSION_GRANTED + checkUidPermission( + uid, + Manifest.permission.BACKUP, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT + ) != PackageManager.PERMISSION_GRANTED ) { return false } @@ -2879,16 +2821,16 @@ class PermissionService(private val service: AccessCheckingService) : when (msg.what) { MSG_ON_PERMISSIONS_CHANGED -> { val uid = msg.arg1 - val persistentDeviceId = msg.obj as String - handleOnPermissionsChanged(uid, persistentDeviceId) + val deviceId = msg.obj as String + handleOnPermissionsChanged(uid, deviceId) } } } - private fun handleOnPermissionsChanged(uid: Int, persistentDeviceId: String) { + private fun handleOnPermissionsChanged(uid: Int, deviceId: String) { listeners.broadcast { listener -> try { - listener.onPermissionsChanged(uid, persistentDeviceId) + listener.onPermissionsChanged(uid, deviceId) } catch (e: RemoteException) { Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e) } @@ -2903,9 +2845,9 @@ class PermissionService(private val service: AccessCheckingService) : listeners.unregister(listener) } - fun onPermissionsChanged(uid: Int, persistentDeviceId: String) { + fun onPermissionsChanged(uid: Int, deviceId: String) { if (listeners.registeredCallbackCount > 0) { - obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId).sendToTarget() + obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget() } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index cfe701f42065..d4b57f191ecd 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -273,7 +273,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag AndroidPackage::hasRequestForegroundServiceExemption, AndroidPackage::hasRequestRawExternalStorageAccess, AndroidPackage::isUpdatableSystem, - AndroidPackage::getEmergencyInstaller + AndroidPackage::getEmergencyInstaller, + AndroidPackage::isAllowCrossUidActivitySwitchFromBelow, ) override fun extraParams() = listOf( diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java index 9473e572259f..c298d516c89e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java @@ -18,7 +18,7 @@ package com.android.server; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.server.notification.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; +import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index d876dae29798..47928bcffb9a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -72,6 +72,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.answer; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; @@ -157,9 +159,11 @@ public class MockingOomAdjusterTests { private static final int MOCKAPP2_UID_OTHER = MOCKAPP2_UID + UserHandle.PER_USER_RANGE; private static final int MOCKAPP_ISOLATED_UID = Process.FIRST_ISOLATED_UID + 321; private static final String MOCKAPP_ISOLATED_PROCESSNAME = "isolated test #1"; + private static final int MOCKAPP_SDK_SANDBOX_UID = Process.FIRST_SDK_SANDBOX_UID + 654; + private static final String MOCKAPP_SDK_SANDBOX_PROCESSNAME = "sandbox test #1"; private static int sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ - + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; + + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; private static Context sContext; private static PackageManagerInternal sPackageManagerInternal; private static ActivityManagerService sService; @@ -271,7 +275,6 @@ public class MockingOomAdjusterTests { /** * Replace the process LRU with the given processes. - * @param apps */ @SuppressWarnings("GuardedBy") private void setProcessesToLru(ProcessRecord... apps) { @@ -660,7 +663,7 @@ public class MockingOomAdjusterTests { app.mState.setLastTopTime(nowUptime); // Simulate the system starting and binding to a service in the app. ServiceRecord s = bindService(app, system, - null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); + null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime; s.getConnections().clear(); app.mServices.updateHasTopStartedAlmostPerceptibleServices(); @@ -682,7 +685,7 @@ public class MockingOomAdjusterTests { app.mState.setLastTopTime(nowUptime); // Simulate the system starting and binding to a service in the app. ServiceRecord s = bindService(app, system, - null, Context.BIND_ALMOST_PERCEPTIBLE + 2, mock(IBinder.class)); + null, null, Context.BIND_ALMOST_PERCEPTIBLE + 2, mock(IBinder.class)); s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs; app.mServices.updateHasTopStartedAlmostPerceptibleServices(); @@ -704,7 +707,7 @@ public class MockingOomAdjusterTests { app.mState.setLastTopTime(nowUptime); // Simulate the system starting and binding to a service in the app. ServiceRecord s = bindService(app, system, - null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); + null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs; s.getConnections().clear(); @@ -729,7 +732,7 @@ public class MockingOomAdjusterTests { system.mState.setHasTopUi(true); // Simulate the system starting and binding to a service in the app. ServiceRecord s = bindService(app, system, - null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); + null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(system, app); @@ -901,7 +904,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - ServiceRecord s = bindService(app, client, null, Context.BIND_WAIVE_PRIORITY, + ServiceRecord s = bindService(app, client, null, null, Context.BIND_WAIVE_PRIORITY, mock(IBinder.class)); s.startRequested = true; sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -921,7 +924,7 @@ public class MockingOomAdjusterTests { ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); client.mServices.setTreatLikeActivity(true); - bindService(app, client, null, Context.BIND_WAIVE_PRIORITY + bindService(app, client, null, null, Context.BIND_WAIVE_PRIORITY | Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -937,7 +940,7 @@ public class MockingOomAdjusterTests { ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); IBinder binder = mock(IBinder.class); - ServiceRecord s = bindService(app, client, null, Context.BIND_WAIVE_PRIORITY + ServiceRecord s = bindService(app, client, null, null, Context.BIND_WAIVE_PRIORITY | Context.BIND_ADJUST_WITH_ACTIVITY | Context.BIND_IMPORTANT, binder); ConnectionRecord cr = s.getConnections().get(binder).get(0); setFieldValue(ConnectionRecord.class, cr, "activity", @@ -955,7 +958,7 @@ public class MockingOomAdjusterTests { public void testUpdateOomAdj_DoOne_Service_Self() { ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); - bindService(app, app, null, 0, mock(IBinder.class)); + bindService(app, app, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app); @@ -970,7 +973,7 @@ public class MockingOomAdjusterTests { ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); client.mServices.setTreatLikeActivity(true); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -988,7 +991,8 @@ public class MockingOomAdjusterTests { doReturn(true).when(wpc).hasActivities(); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_ALLOW_OOM_MANAGEMENT, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_ALLOW_OOM_MANAGEMENT, + mock(IBinder.class)); doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); doReturn(client).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -1005,7 +1009,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); client.mState.setHasTopUi(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -1023,7 +1027,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_IMPORTANT, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_IMPORTANT, mock(IBinder.class)); client.mServices.startExecutingService(mock(ServiceRecord.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1039,7 +1043,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); doReturn(client).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -1056,7 +1060,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1074,7 +1078,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1090,7 +1094,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1109,7 +1113,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); // In order to trick OomAdjuster to think it has a short-service, we need this logic. ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(sService); @@ -1172,8 +1176,8 @@ public class MockingOomAdjusterTests { ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app1, pers, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class)); - bindService(app2, app1, null, 0, mock(IBinder.class)); + bindService(app1, pers, null, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class)); + bindService(app2, app1, null, null, 0, mock(IBinder.class)); updateOomAdj(pers, app1, app2); @@ -1192,7 +1196,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0); backupTarget.app = client; doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt()); @@ -1218,7 +1222,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class)); client.mState.setRunningRemoteAnimation(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1233,7 +1237,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class)); client.mState.setRunningRemoteAnimation(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1248,7 +1252,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); client.mState.setHasOverlayUi(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1264,7 +1268,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, + bindService(app, client, null, null, Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); @@ -1283,7 +1287,7 @@ public class MockingOomAdjusterTests { MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); WindowProcessController wpc = client.getWindowProcessController(); doReturn(true).when(wpc).isHeavyWeightProcess(); - bindService(app, client, null, + bindService(app, client, null, null, Context.BIND_ALMOST_PERCEPTIBLE | Context.BIND_NOT_FOREGROUND, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); @@ -1301,7 +1305,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, + bindService(app, client, null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); @@ -1320,7 +1324,7 @@ public class MockingOomAdjusterTests { MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); WindowProcessController wpc = client.getWindowProcessController(); doReturn(true).when(wpc).isHeavyWeightProcess(); - bindService(app, client, null, + bindService(app, client, null, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); @@ -1341,7 +1345,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); client.mState.setRunningRemoteAnimation(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1356,7 +1360,8 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_IMPORTANT_BACKGROUND, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_IMPORTANT_BACKGROUND, + mock(IBinder.class)); client.mState.setHasOverlayUi(true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); @@ -1496,10 +1501,10 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); doReturn(client2).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -1517,10 +1522,10 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(app, client2, null, 0, mock(IBinder.class)); + bindService(app, client2, null, null, 0, mock(IBinder.class)); client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, client2, app); @@ -1537,10 +1542,10 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, client2, app); @@ -1557,12 +1562,12 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(client2, app, null, 0, mock(IBinder.class)); + bindService(client2, app, null, null, 0, mock(IBinder.class)); // Note: We add processes to LRU but still call updateOomAdjLocked() with a specific // processes. @@ -1599,11 +1604,11 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); - bindService(client, app, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); + bindService(client, app, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client2, client, null, 0, mock(IBinder.class)); + bindService(client2, client, null, null, 0, mock(IBinder.class)); client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2); @@ -1626,11 +1631,11 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); - bindService(client2, client, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); + bindService(client2, client, null, null, 0, mock(IBinder.class)); client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2); @@ -1653,18 +1658,18 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); - bindService(client2, client, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); + bindService(client2, client, null, null, 0, mock(IBinder.class)); ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); - bindService(client3, client, null, 0, mock(IBinder.class)); + bindService(client3, client, null, null, 0, mock(IBinder.class)); ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID, MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false)); - bindService(client3, client4, null, 0, mock(IBinder.class)); - bindService(client4, client3, null, 0, mock(IBinder.class)); + bindService(client3, client4, null, null, 0, mock(IBinder.class)); + bindService(client4, client3, null, null, 0, mock(IBinder.class)); client.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2, client3, client4); @@ -1693,16 +1698,16 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(client2, app, null, 0, mock(IBinder.class)); + bindService(client2, app, null, null, 0, mock(IBinder.class)); ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); client3.mState.setForcingToImportant(new Object()); - bindService(app, client3, null, 0, mock(IBinder.class)); + bindService(app, client3, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2, client3); @@ -1718,17 +1723,17 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); - bindService(client2, app, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); + bindService(client2, app, null, null, 0, mock(IBinder.class)); WindowProcessController wpc = client2.getWindowProcessController(); doReturn(true).when(wpc).isHomeProcess(); ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); client3.mState.setForcingToImportant(new Object()); - bindService(app, client3, null, 0, mock(IBinder.class)); + bindService(app, client3, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2, client3); @@ -1743,11 +1748,11 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); - bindService(client2, app, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); + bindService(client2, app, null, null, 0, mock(IBinder.class)); WindowProcessController wpc = client2.getWindowProcessController(); doReturn(true).when(wpc).isHomeProcess(); ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, @@ -1755,7 +1760,7 @@ public class MockingOomAdjusterTests { ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID, MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false)); client4.mState.setForcingToImportant(new Object()); - bindService(app, client4, null, 0, mock(IBinder.class)); + bindService(app, client4, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2, client3, client4); @@ -1770,21 +1775,21 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, 0, mock(IBinder.class)); - bindService(client2, app, null, 0, mock(IBinder.class)); + bindService(client, client2, null, null, 0, mock(IBinder.class)); + bindService(client2, app, null, null, 0, mock(IBinder.class)); WindowProcessController wpc = client2.getWindowProcessController(); doReturn(true).when(wpc).isHomeProcess(); ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); client3.mState.setForcingToImportant(new Object()); - bindService(app, client3, null, 0, mock(IBinder.class)); + bindService(app, client3, null, null, 0, mock(IBinder.class)); ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID, MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false)); client4.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(app, client4, null, 0, mock(IBinder.class)); + bindService(app, client4, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2, client3, client4); @@ -1802,15 +1807,15 @@ public class MockingOomAdjusterTests { MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); WindowProcessController wpc = client.getWindowProcessController(); doReturn(true).when(wpc).isHomeProcess(); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(app, client2, null, 0, mock(IBinder.class)); + bindService(app, client2, null, null, 0, mock(IBinder.class)); client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); client3.mState.setForcingToImportant(new Object()); - bindService(app, client3, null, 0, mock(IBinder.class)); + bindService(app, client3, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, client2, client3, app); @@ -1826,7 +1831,7 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); bindProvider(client, client2, null, null, false); @@ -1846,12 +1851,12 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, 0, mock(IBinder.class)); + bindService(app, client, null, null, 0, mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); bindProvider(client, client2, null, null, false); client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(client2, app, null, 0, mock(IBinder.class)); + bindService(client2, app, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2); @@ -1912,9 +1917,9 @@ public class MockingOomAdjusterTests { MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); final ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); - bindService(app1, client1, null, Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, + bindService(app1, client1, null, null, Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, mock(IBinder.class)); - bindService(app2, client2, null, Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, + bindService(app2, client2, null, null, Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, mock(IBinder.class)); client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ); client2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); @@ -1929,8 +1934,10 @@ public class MockingOomAdjusterTests { assertBfsl(app1); assertBfsl(app2); - bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class)); - bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class)); + bindService(app1, client1, null, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, + mock(IBinder.class)); + bindService(app2, client2, null, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, + mock(IBinder.class)); updateOomAdj(client1, client2, app1, app2); assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ, @@ -1946,8 +1953,8 @@ public class MockingOomAdjusterTests { SCHED_GROUP_DEFAULT); assertBfsl(app2); - bindService(client2, app1, null, 0, mock(IBinder.class)); - bindService(app1, client2, null, 0, mock(IBinder.class)); + bindService(client2, app1, null, null, 0, mock(IBinder.class)); + bindService(app1, client2, null, null, 0, mock(IBinder.class)); client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false); updateOomAdj(app1, client1, client2); assertProcStates(app1, PROCESS_STATE_IMPORTANT_FOREGROUND, VISIBLE_APP_ADJ, @@ -1968,9 +1975,9 @@ public class MockingOomAdjusterTests { client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ); client2.mState.setMaxAdj(PERSISTENT_PROC_ADJ); - final ServiceRecord s1 = bindService(app1, client1, null, + final ServiceRecord s1 = bindService(app1, client1, null, null, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class)); - final ServiceRecord s2 = bindService(app2, client2, null, + final ServiceRecord s2 = bindService(app2, client2, null, null, Context.BIND_IMPORTANT, mock(IBinder.class)); updateOomAdj(client1, client2, app1, app2); @@ -1980,7 +1987,7 @@ public class MockingOomAdjusterTests { assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ, SCHED_GROUP_DEFAULT); - bindService(app2, client1, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, + bindService(app2, client1, null, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class)); updateOomAdj(app2); assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ, @@ -1995,9 +2002,9 @@ public class MockingOomAdjusterTests { client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); client2.mState.setHasOverlayUi(true); - bindService(app1, client1, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, + bindService(app1, client1, null, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class)); - bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, + bindService(app2, client2, null, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class)); updateOomAdj(client1, client2, app1, app2); @@ -2030,7 +2037,7 @@ public class MockingOomAdjusterTests { app1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - bindService(app1, client1, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class)); + bindService(app1, client1, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class)); updateOomAdj(client1, app1); @@ -2051,7 +2058,8 @@ public class MockingOomAdjusterTests { app1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - bindService(app1, client1, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); + bindService(app1, client1, null, null, Context.BIND_ALMOST_PERCEPTIBLE, + mock(IBinder.class)); updateOomAdj(client1, app1); @@ -2121,19 +2129,19 @@ public class MockingOomAdjusterTests { final ComponentName cn1 = ComponentName.unflattenFromString( MOCKAPP_PACKAGENAME + "/.TestService"); - final ServiceRecord s1 = bindService(app1, client1, null, 0, mock(IBinder.class)); + final ServiceRecord s1 = bindService(app1, client1, null, null, 0, mock(IBinder.class)); setFieldValue(ServiceRecord.class, s1, "name", cn1); s1.startRequested = true; final ComponentName cn2 = ComponentName.unflattenFromString( MOCKAPP2_PACKAGENAME + "/.TestService"); - final ServiceRecord s2 = bindService(app2, client2, null, 0, mock(IBinder.class)); + final ServiceRecord s2 = bindService(app2, client2, null, null, 0, mock(IBinder.class)); setFieldValue(ServiceRecord.class, s2, "name", cn2); s2.startRequested = true; final ComponentName cn3 = ComponentName.unflattenFromString( MOCKAPP5_PACKAGENAME + "/.TestService"); - final ServiceRecord s3 = bindService(app3, client1, null, 0, mock(IBinder.class)); + final ServiceRecord s3 = bindService(app3, client1, null, null, 0, mock(IBinder.class)); setFieldValue(ServiceRecord.class, s3, "name", cn3); s3.startRequested = true; @@ -2177,7 +2185,7 @@ public class MockingOomAdjusterTests { clientUidRecord.setIdle(true); doReturn(ActivityManager.APP_START_MODE_DELAYED).when(sService) .getAppStartModeLOSP(anyInt(), any(String.class), anyInt(), - anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); + anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); doNothing().when(sService.mServices) .scheduleServiceTimeoutLocked(any(ProcessRecord.class)); updateOomAdj(client1, client2, app1, app2, app3); @@ -2188,7 +2196,7 @@ public class MockingOomAdjusterTests { } finally { doCallRealMethod().when(sService) .getAppStartModeLOSP(anyInt(), any(String.class), anyInt(), - anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); + anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); sService.mServices.mServiceMap.clear(); sService.mOomAdjuster.mActiveUids.clear(); } @@ -2223,7 +2231,7 @@ public class MockingOomAdjusterTests { ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); app2.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(app, app2, null, 0, mock(IBinder.class)); + bindService(app, app2, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, app2); @@ -2242,12 +2250,12 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, app2, null, 0, mock(IBinder.class)); + bindService(app, app2, null, null, 0, mock(IBinder.class)); ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(app2, app3, null, 0, mock(IBinder.class)); + bindService(app2, app3, null, null, 0, mock(IBinder.class)); app3.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(app3, app, null, 0, mock(IBinder.class)); + bindService(app3, app, null, null, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, app2, app3); @@ -2278,21 +2286,21 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - ServiceRecord s = bindService(app, app2, null, 0, mock(IBinder.class)); + ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class)); ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(app2, app3, null, 0, mock(IBinder.class)); - bindService(app3, app, null, 0, mock(IBinder.class)); + bindService(app2, app3, null, null, 0, mock(IBinder.class)); + bindService(app3, app, null, null, 0, mock(IBinder.class)); WindowProcessController wpc = app3.getWindowProcessController(); doReturn(true).when(wpc).isHomeProcess(); ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); app4.mState.setHasOverlayUi(true); - bindService(app, app4, s, 0, mock(IBinder.class)); + bindService(app, app4, null, s, 0, mock(IBinder.class)); ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID, MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false)); app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(app, app5, s, 0, mock(IBinder.class)); + bindService(app, app5, null, s, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, app2, app3, app4, app5); @@ -2320,21 +2328,21 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - ServiceRecord s = bindService(app, app2, null, 0, mock(IBinder.class)); + ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class)); ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(app2, app3, null, 0, mock(IBinder.class)); - bindService(app3, app, null, 0, mock(IBinder.class)); + bindService(app2, app3, null, null, 0, mock(IBinder.class)); + bindService(app3, app, null, null, 0, mock(IBinder.class)); WindowProcessController wpc = app3.getWindowProcessController(); doReturn(true).when(wpc).isHomeProcess(); ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); app4.mState.setHasOverlayUi(true); - bindService(app, app4, s, 0, mock(IBinder.class)); + bindService(app, app4, null, s, 0, mock(IBinder.class)); ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID, MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false)); app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(app, app5, s, 0, mock(IBinder.class)); + bindService(app, app5, null, s, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app5, app4, app3, app2, app); @@ -2362,21 +2370,21 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - ServiceRecord s = bindService(app, app2, null, 0, mock(IBinder.class)); + ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class)); ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(app2, app3, null, 0, mock(IBinder.class)); - bindService(app3, app, null, 0, mock(IBinder.class)); + bindService(app2, app3, null, null, 0, mock(IBinder.class)); + bindService(app3, app, null, null, 0, mock(IBinder.class)); WindowProcessController wpc = app3.getWindowProcessController(); doReturn(true).when(wpc).isHomeProcess(); ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); app4.mState.setHasOverlayUi(true); - bindService(app, app4, s, 0, mock(IBinder.class)); + bindService(app, app4, null, s, 0, mock(IBinder.class)); ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID, MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false)); app5.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true); - bindService(app, app5, s, 0, mock(IBinder.class)); + bindService(app, app5, null, s, 0, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app3, app4, app2, app, app5); @@ -2404,15 +2412,19 @@ public class MockingOomAdjusterTests { MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); - bindService(app, client, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + bindService(app, client, null, null, Context.BIND_INCLUDE_CAPABILITIES, + mock(IBinder.class)); ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); - bindService(client, client2, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); - bindService(client2, app, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + bindService(client, client2, null, null, Context.BIND_INCLUDE_CAPABILITIES, + mock(IBinder.class)); + bindService(client2, app, null, null, Context.BIND_INCLUDE_CAPABILITIES, + mock(IBinder.class)); ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ); - bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class)); + bindService(app, client3, null, null, Context.BIND_INCLUDE_CAPABILITIES, + mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(app, client, client2, client3); @@ -2472,10 +2484,10 @@ public class MockingOomAdjusterTests { ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); long now = SystemClock.uptimeMillis(); - ServiceRecord s = bindService(app, app2, null, 0, mock(IBinder.class)); + ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class)); s.startRequested = true; s.lastActivity = now; - s = bindService(app2, app, null, 0, mock(IBinder.class)); + s = bindService(app2, app, null, null, 0, mock(IBinder.class)); s.startRequested = true; s.lastActivity = now; ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, @@ -2507,11 +2519,11 @@ public class MockingOomAdjusterTests { final int userOwner = 0; final int userOther = 1; final int cachedAdj1 = sService.mConstants.USE_TIERED_CACHED_ADJ - ? CACHED_APP_MIN_ADJ + 10 - : CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; + ? CACHED_APP_MIN_ADJ + 10 + : CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS; final int cachedAdj2 = sService.mConstants.USE_TIERED_CACHED_ADJ - ? CACHED_APP_MIN_ADJ + 10 - : cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; + ? CACHED_APP_MIN_ADJ + 10 + : cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2; doReturn(userOwner).when(sService.mUserController).getCurrentUserId(); final ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP(); @@ -2626,7 +2638,7 @@ public class MockingOomAdjusterTests { // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and // verify that its OOM adjustment level is unaffected. - bindService(app, app, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); + bindService(app, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); app.mServices.updateHasAboveClientLocked(); assertFalse(app.mServices.hasAboveClient()); @@ -2644,12 +2656,12 @@ public class MockingOomAdjusterTests { final ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); long now = SystemClock.uptimeMillis(); - ServiceRecord s = bindService(app, app2, null, 0, mock(IBinder.class)); + ServiceRecord s = bindService(app, app2, null, null, 0, mock(IBinder.class)); s.startRequested = true; s.lastActivity = now; - s = bindService(app2, app3, null, 0, mock(IBinder.class)); + s = bindService(app2, app3, null, null, 0, mock(IBinder.class)); s.lastActivity = now; - s = bindService(app3, app2, null, 0, mock(IBinder.class)); + s = bindService(app3, app2, null, null, 0, mock(IBinder.class)); s.lastActivity = now; sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); @@ -2678,7 +2690,7 @@ public class MockingOomAdjusterTests { // Start binding to a service that isn't running yet. ServiceRecord sr = makeServiceRecord(app); sr.app = null; - bindService(null, app, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); + bindService(null, app, null, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); // Since sr.app is null, this service cannot be in the same process as the // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect. @@ -2772,91 +2784,37 @@ public class MockingOomAdjusterTests { ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true); } + @SuppressWarnings("GuardedBy") + @Test + public void testUpdateOomAdj_DoAll_SdkSandbox_attributedClient() { + ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + ProcessRecord attributedClient = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, + MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, true)); + ProcessRecord sandboxService = spy(new ProcessRecordBuilder(MOCKAPP_PID, + MOCKAPP_SDK_SANDBOX_UID, MOCKAPP_SDK_SANDBOX_PROCESSNAME, MOCKAPP_PACKAGENAME) + .setSdkSandboxClientAppPackage(MOCKAPP3_PACKAGENAME) + .build()); + + setProcessesToLru(sandboxService, client, attributedClient); + + client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + attributedClient.mServices.setHasForegroundServices(true, 0, true); + bindService(sandboxService, client, attributedClient, null, 0, mock(IBinder.class)); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + updateOomAdj(); + assertProcStates(client, PROCESS_STATE_PERSISTENT, PERSISTENT_PROC_ADJ, + SCHED_GROUP_DEFAULT); + assertProcStates(attributedClient, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT); + assertProcStates(sandboxService, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT); + } + private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName, String packageName, boolean hasShownUi) { - long now = SystemClock.uptimeMillis(); - return makeProcessRecord(sService, pid, uid, processName, - packageName, 12345, Build.VERSION_CODES.CUR_DEVELOPMENT, - now, now, now, 12345, UNKNOWN_ADJ, UNKNOWN_ADJ, - UNKNOWN_ADJ, CACHED_APP_MAX_ADJ, - SCHED_GROUP_DEFAULT, SCHED_GROUP_DEFAULT, - PROCESS_STATE_NONEXISTENT, PROCESS_STATE_NONEXISTENT, - PROCESS_STATE_NONEXISTENT, PROCESS_STATE_NONEXISTENT, - 0, 0, false, false, false, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE, - false, false, false, hasShownUi, false, false, false, false, false, false, null, - 0, Long.MIN_VALUE, Long.MIN_VALUE, true, 0, null, false); - } - - private ProcessRecord makeProcessRecord(ActivityManagerService service, int pid, int uid, - String processName, String packageName, long versionCode, int targetSdkVersion, - long lastActivityTime, long lastPssTime, long nextPssTime, long lastPss, int maxAdj, - int setRawAdj, int curAdj, int setAdj, int curSchedGroup, int setSchedGroup, - int curProcState, int repProcState, int curRawProcState, int setProcState, - int connectionGroup, int connectionImportance, boolean serviceb, - boolean hasClientActivities, boolean hasForegroundServices, int fgServiceTypes, - boolean hasForegroundActivities, boolean repForegroundActivities, boolean systemNoUi, - boolean hasShownUi, boolean hasTopUi, boolean hasOverlayUi, - boolean runningRemoteAnimation, boolean hasAboveClient, boolean treatLikeActivity, - boolean killedByAm, Object forcingToImportant, int numOfCurReceivers, - long lastProviderTime, long lastTopTime, boolean cached, int numOfExecutingServices, - String isolatedEntryPoint, boolean execServicesFg) { - ApplicationInfo ai = spy(new ApplicationInfo()); - ai.uid = uid; - ai.packageName = packageName; - ai.longVersionCode = versionCode; - ai.targetSdkVersion = targetSdkVersion; - ProcessRecord app = new ProcessRecord(service, ai, processName, uid); - final ProcessStateRecord state = app.mState; - final ProcessServiceRecord services = app.mServices; - final ProcessReceiverRecord receivers = app.mReceivers; - final ProcessProfileRecord profile = app.mProfile; - final ProcessProviderRecord providers = app.mProviders; - app.makeActive(mock(IApplicationThread.class), sService.mProcessStats); - app.setLastActivityTime(lastActivityTime); - app.setKilledByAm(killedByAm); - app.setIsolatedEntryPoint(isolatedEntryPoint); - setFieldValue(ProcessRecord.class, app, "mWindowProcessController", - mock(WindowProcessController.class)); - profile.setLastPssTime(lastPssTime); - profile.setNextPssTime(nextPssTime); - profile.setLastPss(lastPss); - state.setMaxAdj(maxAdj); - state.setSetRawAdj(setRawAdj); - state.setCurAdj(curAdj); - state.setSetAdj(setAdj); - state.setCurrentSchedulingGroup(curSchedGroup); - state.setSetSchedGroup(setSchedGroup); - state.setCurProcState(curProcState); - state.setReportedProcState(repProcState); - state.setCurRawProcState(curRawProcState); - state.setSetProcState(setProcState); - state.setServiceB(serviceb); - state.setRepForegroundActivities(repForegroundActivities); - state.setHasForegroundActivities(hasForegroundActivities); - state.setSystemNoUi(systemNoUi); - state.setHasShownUi(hasShownUi); - state.setHasTopUi(hasTopUi); - state.setRunningRemoteAnimation(runningRemoteAnimation); - state.setHasOverlayUi(hasOverlayUi); - state.setCached(cached); - state.setLastTopTime(lastTopTime); - state.setForcingToImportant(forcingToImportant); - services.setConnectionGroup(connectionGroup); - services.setConnectionImportance(connectionImportance); - services.setHasClientActivities(hasClientActivities); - services.setHasForegroundServices(hasForegroundServices, fgServiceTypes, - /* hasNoneType=*/false); - services.setHasAboveClient(hasAboveClient); - services.setTreatLikeActivity(treatLikeActivity); - services.setExecServicesFg(execServicesFg); - for (int i = 0; i < numOfExecutingServices; i++) { - services.startExecutingService(mock(ServiceRecord.class)); - } - for (int i = 0; i < numOfCurReceivers; i++) { - receivers.addCurReceiver(mock(BroadcastRecord.class)); - } - providers.setLastProviderTime(lastProviderTime); - return app; + return new ProcessRecordBuilder(pid, uid, processName, packageName).setHasShownUi( + hasShownUi).build(); } private ServiceRecord makeServiceRecord(ProcessRecord app) { @@ -2870,6 +2828,7 @@ public class MockingOomAdjusterTests { record.appInfo = app.info; setFieldValue(ServiceRecord.class, record, "bindings", new ArrayMap<>()); setFieldValue(ServiceRecord.class, record, "pendingStarts", new ArrayList<>()); + setFieldValue(ServiceRecord.class, record, "isSdkSandbox", app.isSdkSandbox); return record; } @@ -2892,11 +2851,11 @@ public class MockingOomAdjusterTests { } private ServiceRecord bindService(ProcessRecord service, ProcessRecord client, - ServiceRecord record, long bindFlags, IBinder binder) { + ProcessRecord attributedClient, ServiceRecord record, long bindFlags, IBinder binder) { if (record == null) { record = makeServiceRecord(service); } - AppBindRecord binding = new AppBindRecord(record, null, client, null); + AppBindRecord binding = new AppBindRecord(record, null, client, attributedClient); ConnectionRecord cr = spy(new ConnectionRecord(binding, mock(ActivityServiceConnectionsHolder.class), mock(IServiceConnection.class), bindFlags, @@ -2961,4 +2920,140 @@ public class MockingOomAdjusterTests { assertBfsl(app); } } + + private static class ProcessRecordBuilder { + @SuppressWarnings("UnusedVariable") + int mPid; + int mUid; + String mProcessName; + String mPackageName; + long mVersionCode = 12345; + int mTargetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; + long mLastActivityTime; + long mLastPssTime; + long mNextPssTime; + long mLastPss = 12345; + int mMaxAdj = UNKNOWN_ADJ; + int mSetRawAdj = UNKNOWN_ADJ; + int mCurAdj = UNKNOWN_ADJ; + int mSetAdj = CACHED_APP_MAX_ADJ; + int mCurSchedGroup = SCHED_GROUP_DEFAULT; + int mSetSchedGroup = SCHED_GROUP_DEFAULT; + int mCurProcState = PROCESS_STATE_NONEXISTENT; + int mRepProcState = PROCESS_STATE_NONEXISTENT; + int mCurRawProcState = PROCESS_STATE_NONEXISTENT; + int mSetProcState = PROCESS_STATE_NONEXISTENT; + int mConnectionGroup = 0; + int mConnectionImportance = 0; + boolean mServiceb = false; + boolean mHasClientActivities = false; + boolean mHasForegroundServices = false; + int mFgServiceTypes = ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE; + boolean mHasForegroundActivities = false; + boolean mRepForegroundActivities = false; + boolean mSystemNoUi = false; + boolean mHasShownUi = false; + boolean mHasTopUi = false; + boolean mHasOverlayUi = false; + boolean mRunningRemoteAnimation = false; + boolean mHasAboveClient = false; + boolean mTreatLikeActivity = false; + boolean mKilledByAm = false; + Object mForcingToImportant; + int mNumOfCurReceivers = 0; + long mLastProviderTime = Long.MIN_VALUE; + long mLastTopTime = Long.MIN_VALUE; + boolean mCached = true; + int mNumOfExecutingServices = 0; + String mIsolatedEntryPoint = null; + boolean mExecServicesFg = false; + String mSdkSandboxClientAppPackage = null; + + ProcessRecordBuilder(int pid, int uid, String processName, String packageName) { + mPid = pid; + mUid = uid; + mProcessName = processName; + mPackageName = packageName; + + long now = SystemClock.uptimeMillis(); + mLastActivityTime = now; + mLastPssTime = now; + mNextPssTime = now; + } + + ProcessRecordBuilder setHasShownUi(boolean hasShownUi) { + mHasShownUi = hasShownUi; + return this; + } + + ProcessRecordBuilder setSdkSandboxClientAppPackage(String sdkSandboxClientAppPackage) { + mSdkSandboxClientAppPackage = sdkSandboxClientAppPackage; + return this; + } + + @SuppressWarnings("GuardedBy") + public ProcessRecord build() { + ApplicationInfo ai = spy(new ApplicationInfo()); + ai.uid = mUid; + ai.packageName = mPackageName; + ai.longVersionCode = mVersionCode; + ai.targetSdkVersion = mTargetSdkVersion; + doCallRealMethod().when(sService).getPackageManagerInternal(); + doReturn(null).when(sPackageManagerInternal).getApplicationInfo( + eq(mSdkSandboxClientAppPackage), anyLong(), anyInt(), anyInt()); + ProcessRecord app = new ProcessRecord(sService, ai, mProcessName, mUid, + mSdkSandboxClientAppPackage, -1, null); + final ProcessStateRecord state = app.mState; + final ProcessServiceRecord services = app.mServices; + final ProcessReceiverRecord receivers = app.mReceivers; + final ProcessProfileRecord profile = app.mProfile; + final ProcessProviderRecord providers = app.mProviders; + app.makeActive(mock(IApplicationThread.class), sService.mProcessStats); + app.setLastActivityTime(mLastActivityTime); + app.setKilledByAm(mKilledByAm); + app.setIsolatedEntryPoint(mIsolatedEntryPoint); + setFieldValue(ProcessRecord.class, app, "mWindowProcessController", + mock(WindowProcessController.class)); + profile.setLastPssTime(mLastPssTime); + profile.setNextPssTime(mNextPssTime); + profile.setLastPss(mLastPss); + state.setMaxAdj(mMaxAdj); + state.setSetRawAdj(mSetRawAdj); + state.setCurAdj(mCurAdj); + state.setSetAdj(mSetAdj); + state.setCurrentSchedulingGroup(mCurSchedGroup); + state.setSetSchedGroup(mSetSchedGroup); + state.setCurProcState(mCurProcState); + state.setReportedProcState(mRepProcState); + state.setCurRawProcState(mCurRawProcState); + state.setSetProcState(mSetProcState); + state.setServiceB(mServiceb); + state.setRepForegroundActivities(mRepForegroundActivities); + state.setHasForegroundActivities(mHasForegroundActivities); + state.setSystemNoUi(mSystemNoUi); + state.setHasShownUi(mHasShownUi); + state.setHasTopUi(mHasTopUi); + state.setRunningRemoteAnimation(mRunningRemoteAnimation); + state.setHasOverlayUi(mHasOverlayUi); + state.setCached(mCached); + state.setLastTopTime(mLastTopTime); + state.setForcingToImportant(mForcingToImportant); + services.setConnectionGroup(mConnectionGroup); + services.setConnectionImportance(mConnectionImportance); + services.setHasClientActivities(mHasClientActivities); + services.setHasForegroundServices(mHasForegroundServices, mFgServiceTypes, + /* hasNoneType=*/false); + services.setHasAboveClient(mHasAboveClient); + services.setTreatLikeActivity(mTreatLikeActivity); + services.setExecServicesFg(mExecServicesFg); + for (int i = 0; i < mNumOfExecutingServices; i++) { + services.startExecutingService(mock(ServiceRecord.class)); + } + for (int i = 0; i < mNumOfCurReceivers; i++) { + receivers.addCurReceiver(mock(BroadcastRecord.class)); + } + providers.setLastProviderTime(mLastProviderTime); + return app; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java new file mode 100644 index 000000000000..08a65292cf20 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java @@ -0,0 +1,333 @@ +/* + * 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.adaptiveauth; + +import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH; +import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS; +import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST; +import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.KeyguardManager; +import android.content.Context; +import android.hardware.biometrics.AuthenticationStateListener; +import android.hardware.biometrics.BiometricManager; +import android.os.RemoteException; +import android.platform.test.flag.junit.SetFlagsRule; + +import androidx.test.InstrumentationRegistry; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockSettingsInternal; +import com.android.internal.widget.LockSettingsStateListener; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * atest FrameworksServicesTests:AdaptiveAuthServiceTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AdaptiveAuthServiceTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private static final int PRIMARY_USER_ID = 0; + private static final int MANAGED_PROFILE_USER_ID = 12; + private static final int DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS = 0; + private static final int REASON_UNKNOWN = 0; // BiometricRequestConstants.RequestReason + + private Context mContext; + private AdaptiveAuthService mAdaptiveAuthService; + + @Mock + LockPatternUtils mLockPatternUtils; + @Mock + private LockSettingsInternal mLockSettings; + @Mock + private BiometricManager mBiometricManager; + @Mock + private KeyguardManager mKeyguardManager; + @Mock + private WindowManagerInternal mWindowManager; + @Mock + private UserManagerInternal mUserManager; + + @Captor + ArgumentCaptor<LockSettingsStateListener> mLockSettingsStateListenerCaptor; + @Captor + ArgumentCaptor<AuthenticationStateListener> mAuthenticationStateListenerCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mSetFlagsRule.enableFlags(FLAG_ENABLE_ADAPTIVE_AUTH); + mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS); + mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS); + + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(BiometricManager.class)).thenReturn(mBiometricManager); + when(mContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager); + + LocalServices.removeServiceForTest(LockSettingsInternal.class); + LocalServices.addService(LockSettingsInternal.class, mLockSettings); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, mWindowManager); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mUserManager); + + mAdaptiveAuthService = new AdaptiveAuthService(mContext, mLockPatternUtils); + mAdaptiveAuthService.init(); + + verify(mLockSettings).registerLockSettingsStateListener( + mLockSettingsStateListenerCaptor.capture()); + verify(mBiometricManager).registerAuthenticationStateListener( + mAuthenticationStateListenerCaptor.capture()); + + // Set PRIMARY_USER_ID as the parent of MANAGED_PROFILE_USER_ID + when(mUserManager.getProfileParentId(eq(MANAGED_PROFILE_USER_ID))) + .thenReturn(PRIMARY_USER_ID); + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(LockSettingsInternal.class); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.removeServiceForTest(UserManagerInternal.class); + } + + @Test + public void testReportAuthAttempt_primaryAuthSucceeded() + throws RemoteException { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationSucceeded(PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthFailed_once() + throws RemoteException { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(1 /* expectedCntFailedAttempts */, PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthFailed_multiple_deviceCurrentlyLocked() + throws RemoteException { + // Device is currently locked and Keyguard is showing + when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(true); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); + + for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthFailed_multiple_deviceCurrentlyNotLocked() + throws RemoteException { + // Device is currently not locked and Keyguard is not showing + when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); + + for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyLockDevice(PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthSucceeded() + throws RemoteException { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationSucceeded(REASON_UNKNOWN, PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthFailed_once() + throws RemoteException { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(1 /* expectedCntFailedAttempts */, PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyLocked() + throws RemoteException { + // Device is currently locked and Keyguard is showing + when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(true); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); + + for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked() + throws RemoteException { + // Device is currently not locked and Keyguard is not showing + when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); + + for (int i = 0; i < MAX_ALLOWED_FAILED_AUTH_ATTEMPTS; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyLockDevice(PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_biometricAuthFailedThenPrimaryAuthSucceeded() + throws RemoteException { + // Three failed biometric auth attempts + for (int i = 0; i < 3; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + } + // One successful primary auth attempt + mLockSettingsStateListenerCaptor.getValue().onAuthenticationSucceeded(PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthFailedThenBiometricAuthSucceeded() + throws RemoteException { + // Three failed primary auth attempts + for (int i = 0; i < 3; i++) { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + } + // One successful biometric auth attempt + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationSucceeded(REASON_UNKNOWN, PRIMARY_USER_ID); + waitForAuthCompletion(); + + verifyNotLockDevice(DEFAULT_COUNT_FAILED_AUTH_ATTEMPTS /* expectedCntFailedAttempts */, + PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser() + throws RemoteException { + // Three failed primary auth attempts + for (int i = 0; i < 3; i++) { + mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID); + } + // Two failed biometric auth attempts + for (int i = 0; i < 2; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, PRIMARY_USER_ID); + } + waitForAuthCompletion(); + + verifyLockDevice(PRIMARY_USER_ID); + } + + @Test + public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_profileOfPrimaryUser() + throws RemoteException { + // Three failed primary auth attempts + for (int i = 0; i < 3; i++) { + mLockSettingsStateListenerCaptor.getValue() + .onAuthenticationFailed(MANAGED_PROFILE_USER_ID); + } + // Two failed biometric auth attempts + for (int i = 0; i < 2; i++) { + mAuthenticationStateListenerCaptor.getValue() + .onAuthenticationFailed(REASON_UNKNOWN, MANAGED_PROFILE_USER_ID); + } + waitForAuthCompletion(); + + verifyLockDevice(MANAGED_PROFILE_USER_ID); + } + + private void verifyNotLockDevice(int expectedCntFailedAttempts, int userId) { + assertEquals(expectedCntFailedAttempts, + mAdaptiveAuthService.mFailedAttemptsForUser.get(userId)); + verify(mWindowManager, never()).lockNow(); + } + + private void verifyLockDevice(int userId) { + assertEquals(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, + mAdaptiveAuthService.mFailedAttemptsForUser.get(userId)); + verify(mLockPatternUtils).requireStrongAuth( + eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(userId)); + // If userId is MANAGED_PROFILE_USER_ID, the StrongAuthFlag of its parent (PRIMARY_USER_ID) + // should also be verified + if (userId == MANAGED_PROFILE_USER_ID) { + verify(mLockPatternUtils).requireStrongAuth( + eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(PRIMARY_USER_ID)); + } + verify(mWindowManager).lockNow(); + } + + /** + * Wait for all auth events to complete before verification + */ + private static void waitForAuthCompletion() { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS new file mode 100644 index 000000000000..0218a7835586 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 6986cab72f56..e59b5ea027ed 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -270,6 +270,9 @@ public abstract class BaseLockSettingsServiceTests { } protected void setSecureFrpMode(boolean secure) { + if (android.security.Flags.frpEnforcement()) { + mStorage.setTestFactoryResetProtectionState(secure); + } Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.SECURE_FRP_MODE, secure ? 1 : 0, UserHandle.USER_SYSTEM); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index ee076c6bcf4b..296d2cba83dd 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -21,12 +21,14 @@ import static org.mockito.Mockito.mock; import android.app.IActivityManager; import android.app.admin.DeviceStateCache; import android.content.Context; +import android.content.Intent; import android.content.pm.UserInfo; import android.hardware.authsecret.IAuthSecret; import android.os.Handler; import android.os.Parcel; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.os.storage.IStorageManager; import android.security.KeyStore; import android.security.keystore.KeyPermanentlyInvalidatedException; @@ -41,6 +43,9 @@ import com.android.server.pm.UserManagerInternal; import java.io.FileNotFoundException; public class LockSettingsServiceTestable extends LockSettingsService { + private Intent mSavedFrpNotificationIntent = null; + private UserHandle mSavedFrpNotificationUserHandle = null; + private String mSavedFrpNotificationPermission = null; public static class MockInjector extends LockSettingsService.Injector { @@ -218,4 +223,29 @@ public class LockSettingsServiceTestable extends LockSettingsService { mAuthSecret = null; } } + + @Override + void sendBroadcast(Intent intent, UserHandle userHandle, String permission) { + mSavedFrpNotificationIntent = intent; + mSavedFrpNotificationUserHandle = userHandle; + mSavedFrpNotificationPermission = permission; + } + + String getSavedFrpNotificationPermission() { + return mSavedFrpNotificationPermission; + } + + UserHandle getSavedFrpNotificationUserHandle() { + return mSavedFrpNotificationUserHandle; + } + + Intent getSavedFrpNotificationIntent() { + return mSavedFrpNotificationIntent; + } + + void clearRecordedFrpNotificationData() { + mSavedFrpNotificationIntent = null; + mSavedFrpNotificationPermission = null; + mSavedFrpNotificationUserHandle = null; + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 705359708bc7..4b22652a3f21 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -16,6 +16,7 @@ package com.android.server.locksettings; +import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION; import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; @@ -39,7 +40,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; +import android.content.Intent; import android.os.RemoteException; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.service.gatekeeper.GateKeeperResponse; @@ -239,6 +242,12 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @Test + public void testSetLockCredential_forPrimaryUser_sendsFrpNotification() throws Exception { + setCredential(PRIMARY_USER_ID, newPassword("password")); + checkRecordedFrpNotificationIntent(); + } + + @Test public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception { setCredential(PRIMARY_USER_ID, newPassword("password")); verify(mRecoverableKeyStoreManager) @@ -323,6 +332,15 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @Test + public void testClearLockCredential_sendsFrpNotification() throws Exception { + setCredential(PRIMARY_USER_ID, newPassword("password")); + checkRecordedFrpNotificationIntent(); + mService.clearRecordedFrpNotificationData(); + clearCredential(PRIMARY_USER_ID, newPassword("password")); + checkRecordedFrpNotificationIntent(); + } + + @Test public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials() throws Exception { final LockscreenCredential parentPassword = newPassword("parentPassword"); @@ -519,6 +537,23 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { mService.setString(null, "value", 0); } + private void checkRecordedFrpNotificationIntent() { + if (android.security.Flags.frpEnforcement()) { + Intent savedNotificationIntent = mService.getSavedFrpNotificationIntent(); + assertNotNull(savedNotificationIntent); + UserHandle userHandle = mService.getSavedFrpNotificationUserHandle(); + assertEquals(userHandle, + UserHandle.of(mInjector.getUserManagerInternal().getMainUserId())); + + String permission = mService.getSavedFrpNotificationPermission(); + assertEquals(CONFIGURE_FACTORY_RESET_PROTECTION, permission); + } else { + assertNull(mService.getSavedFrpNotificationIntent()); + assertNull(mService.getSavedFrpNotificationUserHandle()); + assertNull(mService.getSavedFrpNotificationPermission()); + } + } + private void checkPasswordHistoryLength(int userId, int expectedLen) { String history = mService.getString(LockPatternUtils.PASSWORD_HISTORY_KEY, "", userId); String[] hashes = TextUtils.split(history, LockPatternUtils.PASSWORD_HISTORY_DELIMITER); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java index fa3c7a4c4769..c01d0f644983 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java @@ -35,6 +35,7 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { public final File mStorageDir; public PersistentDataBlockManagerInternal mPersistentDataBlockManager; private byte[] mPersistentData; + private boolean mIsFactoryResetProtectionActive = false; public LockSettingsStorageTestable(Context context, File storageDir) { super(context); @@ -63,6 +64,10 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { }).when(mPersistentDataBlockManager).getFrpCredentialHandle(); } + void setTestFactoryResetProtectionState(boolean active) { + mIsFactoryResetProtectionActive = active; + } + @Override File getChildProfileLockFile(int userId) { return remapToStorageDir(super.getChildProfileLockFile(userId)); @@ -101,4 +106,9 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { mappedPath.getParentFile().mkdirs(); return mappedPath; } + + @Override + public boolean isFactoryResetProtectionActive() { + return mIsFactoryResetProtectionActive; + } } 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 4451cae8db42..5f2abc3285c9 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.net; import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; import static android.Manifest.permission.NETWORK_STACK; +import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK; import static android.app.ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; @@ -58,9 +59,11 @@ import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM; import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP; import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; +import static android.net.NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; +import static android.net.NetworkPolicyManager.TOP_THRESHOLD_STATE; import static android.net.NetworkPolicyManager.allowedReasonsToString; import static android.net.NetworkPolicyManager.blockedReasonsToString; import static android.net.NetworkPolicyManager.uidPoliciesToString; @@ -88,6 +91,7 @@ import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT; import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED; import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID; import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING; +import static com.android.server.net.NetworkPolicyManagerService.UID_MSG_STATE_CHANGED; import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons; import static com.android.server.net.NetworkPolicyManagerService.normalizeTemplate; @@ -196,8 +200,6 @@ import com.android.server.LocalServices; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.usage.AppStandbyInternal; -import com.google.common.util.concurrent.AbstractFuture; - import libcore.io.Streams; import org.junit.After; @@ -241,10 +243,8 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -2247,6 +2247,123 @@ public class NetworkPolicyManagerServiceTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) + public void testUidObserverFiltersProcStateChanges() throws Exception { + int testProcStateSeq = 0; + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // First callback for uid. + callOnUidStatechanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Doesn't cross the background threshold. + callOnUidStatechanged(UID_A, BACKGROUND_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Crosses the background threshold. + callOnUidStatechanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Doesn't cross the foreground threshold. + callOnUidStatechanged(UID_A, FOREGROUND_THRESHOLD_STATE + 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Crosses the foreground threshold. + callOnUidStatechanged(UID_A, FOREGROUND_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Doesn't cross the top threshold. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE + 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Crosses the top threshold. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Doesn't cross any other threshold. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE - 1, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) + public void testUidObserverFiltersStaleChanges() throws Exception { + final int testProcStateSeq = 51; + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // First callback for uid. + callOnUidStatechanged(UID_B, BACKGROUND_THRESHOLD_STATE + 100, testProcStateSeq, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // Stale callback because the procStateSeq is smaller. + callOnUidStatechanged(UID_B, BACKGROUND_THRESHOLD_STATE - 100, testProcStateSeq - 10, + PROCESS_CAPABILITY_NONE); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) + public void testUidObserverFiltersCapabilityChanges() throws Exception { + int testProcStateSeq = 0; + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // First callback for uid. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_NONE); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // The same process-state with one network capability added. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // The same process-state with another network capability added. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK + | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // The same process-state with all capabilities, but no change in network capabilities. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_ALL); + assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); + } + + @Test public void testLowPowerStandbyAllowlist() throws Exception { // Chain background is also enabled but these procstates are important enough to be exempt. callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0); @@ -2559,17 +2676,6 @@ public class NetworkPolicyManagerServiceTest { verify(mStatsManager).setDefaultGlobalAlert(anyLong()); } - private static class TestAbstractFuture<T> extends AbstractFuture<T> { - @Override - public T get() throws InterruptedException, ExecutionException { - try { - return get(5, TimeUnit.SECONDS); - } catch (TimeoutException e) { - throw new RuntimeException(e); - } - } - } - private static void assertTimeEquals(long expected, long actual) { if (expected != actual) { fail("expected " + formatTime(expected) + " but was actually " + formatTime(actual)); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index f9ba33b526a9..6e5c180de347 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -649,6 +649,74 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testTotalSilence_consolidatedPolicyDisallowsAll() { + // Start with zen mode off just to make sure global/manual mode isn't doing anything. + mZenModeHelper.mZenMode = ZEN_MODE_OFF; + + // Create a zen rule that calls for total silence via zen mode, but does not specify any + // particular policy. This confirms that the application of the policy is based only on the + // actual zen mode setting. + AutomaticZenRule azr = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID) + .setInterruptionFilter(INTERRUPTION_FILTER_NONE) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + azr, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID); + + // Enable rule + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(azr.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + Process.SYSTEM_UID); + + // Confirm that the consolidated policy doesn't allow anything + NotificationManager.Policy policy = mZenModeHelper.getConsolidatedNotificationPolicy(); + assertThat(policy.allowAlarms()).isFalse(); + assertThat(policy.allowMedia()).isFalse(); + assertThat(policy.allowCalls()).isFalse(); + assertThat(policy.allowMessages()).isFalse(); + assertThat(policy.allowConversations()).isFalse(); + assertThat(policy.allowEvents()).isFalse(); + assertThat(policy.allowReminders()).isFalse(); + assertThat(policy.allowRepeatCallers()).isFalse(); + assertThat(policy.allowPriorityChannels()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testAlarmsOnly_consolidatedPolicyOnlyAllowsAlarmsAndMedia() { + // Start with zen mode off just to make sure global/manual mode isn't doing anything. + mZenModeHelper.mZenMode = ZEN_MODE_OFF; + + // Create a zen rule that calls for alarms only via zen mode, but does not specify any + // particular policy. This confirms that the application of the policy is based only on the + // actual zen mode setting. + AutomaticZenRule azr = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + azr, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID); + + // Enable rule + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(azr.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + Process.SYSTEM_UID); + + // Confirm that the consolidated policy allows only alarms and media and nothing else + NotificationManager.Policy policy = mZenModeHelper.getConsolidatedNotificationPolicy(); + assertThat(policy.allowAlarms()).isTrue(); + assertThat(policy.allowMedia()).isTrue(); + assertThat(policy.allowCalls()).isFalse(); + assertThat(policy.allowMessages()).isFalse(); + assertThat(policy.allowConversations()).isFalse(); + assertThat(policy.allowEvents()).isFalse(); + assertThat(policy.allowReminders()).isFalse(); + assertThat(policy.allowRepeatCallers()).isFalse(); + assertThat(policy.allowPriorityChannels()).isFalse(); + } + + @Test public void testZenUpgradeNotification() { /** * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 847c9d09b73a..d5eeaa745a69 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -1561,7 +1561,7 @@ public class ActivityStarterTests extends WindowTestsBase { .build(); final int result = starter.recycleTask(task, null, null, null, - BalVerdict.ALLOW_BY_DEFAULT); + BalVerdict.ALLOW_PRIVILEGED); assertThat(result == START_SUCCESS).isTrue(); assertThat(starter.mAddingToTask).isTrue(); } @@ -2075,7 +2075,7 @@ public class ActivityStarterTests extends WindowTestsBase { starter.startActivityInner(target, source, null /* voiceSession */, null /* voiceInteractor */, 0 /* startFlags */, options, inTask, inTaskFragment, - BalVerdict.ALLOW_BY_DEFAULT, + BalVerdict.ALLOW_PRIVILEGED, null /* intentGrants */, -1 /* realCallingUid */); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index 203475156491..0b466b2b8a2c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -242,7 +242,8 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { verify(activity).getFilteredReferrer(eq(activity.launchedFromPackage)); activity.deliverNewIntentLocked(ActivityBuilder.DEFAULT_FAKE_UID, - new Intent(), null /* intentGrants */, "other.package2"); + new Intent(), null /* intentGrants */, "other.package2", + /* isShareIdentityEnabled */ false); verify(activity).getFilteredReferrer(eq("other.package2")); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java index c404c77c8550..bb5887d12f4b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java @@ -187,7 +187,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase { /* options */null, /* inTask */null, /* inTaskFragment */ null, - BalVerdict.ALLOW_BY_DEFAULT, + BalVerdict.ALLOW_PRIVILEGED, /* intentGrants */null, /* realCaiingUid */ -1); @@ -217,7 +217,7 @@ public class DisplayWindowPolicyControllerTests extends WindowTestsBase { /* options= */null, /* inTask= */null, /* inTaskFragment= */ null, - BalVerdict.ALLOW_BY_DEFAULT, + BalVerdict.ALLOW_PRIVILEGED, /* intentGrants= */null, /* realCaiingUid */ -1); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java index 06d30fc98c6a..29f48b86a375 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java @@ -58,7 +58,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { public void setUp() { super.setUp(); MockitoAnnotations.initMocks(this); - mCache = new TaskSnapshotCache(mWm, mLoader); + mCache = new TaskSnapshotCache(mLoader); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java index df5f3d149e88..7432537902a0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java @@ -61,7 +61,7 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas public void setUp() { super.setUp(); MockitoAnnotations.initMocks(this); - mCache = new TaskSnapshotCache(mWm, mLoader); + mCache = new TaskSnapshotCache(mLoader); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index f02dd3f70feb..cd3ce9192509 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.ROTATION_0; @@ -55,7 +56,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.notification.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW; @@ -1424,6 +1424,25 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(window2.isSecureLocked()); } + @Test + @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) + public void testIsSecureLocked_sensitiveContentBlockOrClearScreenCaptureForApp() { + String testPackage = "test"; + int ownerId = 20; + final WindowState window = createWindow(null, TYPE_APPLICATION, "window", ownerId); + window.mAttrs.packageName = testPackage; + assertFalse(window.isSecureLocked()); + + PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId); + ArraySet<PackageInfo> blockedPackages = new ArraySet(); + blockedPackages.add(blockedPackage); + mWm.mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedPackages); + assertTrue(window.isSecureLocked()); + + mWm.mSensitiveContentPackages.removeBlockScreenCaptureForApps(blockedPackages); + assertFalse(window.isSecureLocked()); + } + private static class TestImeTargetChangeListener implements ImeTargetChangeListener { private IBinder mImeTargetToken; private boolean mIsRemoved; diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java index 8c6e101f2c03..afd34fc74e43 100644 --- a/telecomm/java/android/telecom/CallAttributes.java +++ b/telecomm/java/android/telecom/CallAttributes.java @@ -16,6 +16,7 @@ package android.telecom; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -24,6 +25,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.server.telecom.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -113,7 +116,8 @@ public final class CallAttributes implements Parcelable { public static final int VIDEO_CALL = 2; /** @hide */ - @IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER}, flag = true) + @IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER, + SUPPORTS_VIDEO_CALLING}, flag = true) @Retention(RetentionPolicy.SOURCE) public @interface CallCapability { } @@ -133,6 +137,12 @@ public final class CallAttributes implements Parcelable { * The call can be completely transferred from one endpoint to another. */ public static final int SUPPORTS_TRANSFER = 1 << 3; + /** + * The call supports video calling. This allows clients to gate video calling on a per call + * basis as opposed to re-registering the phone account. + */ + @FlaggedApi(Flags.FLAG_TRANSACTIONAL_VIDEO_STATE) + public static final int SUPPORTS_VIDEO_CALLING = 1 << 4; /** * Build an instance of {@link CallAttributes}. In order to build a valid instance, a diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java index a14078697c71..808a57589b47 100644 --- a/telecomm/java/android/telecom/CallControl.java +++ b/telecomm/java/android/telecom/CallControl.java @@ -293,12 +293,50 @@ public final class CallControl { try { mServerInterface.setMuteState(isMuted, new CallControlResultReceiver("requestMuteState", executor, callback)); - } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } + /** + * Request a new video state for the ongoing call. This can only be changed if the application + * has registered a {@link PhoneAccount} with the + * {@link PhoneAccount#CAPABILITY_SUPPORTS_VIDEO_CALLING} and set the + * {@link CallAttributes#SUPPORTS_VIDEO_CALLING} when adding the call via + * {@link TelecomManager#addCall(CallAttributes, Executor, OutcomeReceiver, + * CallControlCallback, CallEventCallback)} + * + * @param videoState to report to Telecom. To see the valid argument to pass, + * see {@link CallAttributes.CallType}. + * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback + * will be called on. + * @param callback that will be completed on the Telecom side that details success or failure + * of the requested operation. + * + * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully + * switched the video state. + * + * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set + * the new video state. A {@link CallException} will be passed + * that details why the operation failed. + * @throws IllegalArgumentException if the argument passed for videoState is invalid. To see a + * list of valid states, see {@link CallAttributes.CallType}. + */ + @FlaggedApi(Flags.FLAG_TRANSACTIONAL_VIDEO_STATE) + public void requestVideoState(@CallAttributes.CallType int videoState, + @CallbackExecutor @NonNull Executor executor, + @NonNull OutcomeReceiver<Void, CallException> callback) { + validateVideoState(videoState); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + try { + mServerInterface.requestVideoState(videoState, mCallId, + new CallControlResultReceiver("requestVideoState", executor, callback)); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + /** * Raises an event to the {@link android.telecom.InCallService} implementations tracking this * call via {@link android.telecom.Call.Callback#onConnectionEvent(Call, String, Bundle)}. diff --git a/telecomm/java/android/telecom/CallEventCallback.java b/telecomm/java/android/telecom/CallEventCallback.java index a41c0113e933..b0438bfa0289 100644 --- a/telecomm/java/android/telecom/CallEventCallback.java +++ b/telecomm/java/android/telecom/CallEventCallback.java @@ -16,9 +16,12 @@ package android.telecom; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.os.Bundle; +import com.android.server.telecom.flags.Flags; + import java.util.List; /** @@ -51,6 +54,14 @@ public interface CallEventCallback { void onMuteStateChanged(boolean isMuted); /** + * Called when the video state changes. + * + * @param videoState The current video state. + */ + @FlaggedApi(Flags.FLAG_TRANSACTIONAL_VIDEO_STATE) + default void onVideoStateChanged(@CallAttributes.CallType int videoState) {} + + /** * Telecom is informing the client user requested call streaming but the stream can't be * started. * diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java index 467e89c78810..a2c60862f559 100644 --- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java +++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java @@ -33,6 +33,8 @@ import android.telecom.PhoneAccountHandle; import android.text.TextUtils; import android.util.Log; +import com.android.server.telecom.flags.Flags; + import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -148,6 +150,7 @@ public class ClientTransactionalServiceWrapper { private static final String ON_REQ_ENDPOINT_CHANGE = "onRequestEndpointChange"; private static final String ON_AVAILABLE_CALL_ENDPOINTS = "onAvailableCallEndpointsChanged"; private static final String ON_MUTE_STATE_CHANGED = "onMuteStateChanged"; + private static final String ON_VIDEO_STATE_CHANGED = "onVideoStateChanged"; private static final String ON_CALL_STREAMING_FAILED = "onCallStreamingFailed"; private static final String ON_EVENT = "onEvent"; @@ -261,6 +264,11 @@ public class ClientTransactionalServiceWrapper { handleEventCallback(callId, ON_MUTE_STATE_CHANGED, isMuted); } + @Override + public void onVideoStateChanged(String callId, int videoState) { + handleEventCallback(callId, ON_VIDEO_STATE_CHANGED, videoState); + } + public void handleEventCallback(String callId, String action, Object arg) { Log.d(TAG, TextUtils.formatSimple("hEC: [%s], callId=[%s]", action, callId)); // lookup the callEventCallback associated with the particular call @@ -281,6 +289,11 @@ public class ClientTransactionalServiceWrapper { case ON_MUTE_STATE_CHANGED: callback.onMuteStateChanged((boolean) arg); break; + case ON_VIDEO_STATE_CHANGED: + if (Flags.transactionalVideoState()) { + callback.onVideoStateChanged((int) arg); + } + break; case ON_CALL_STREAMING_FAILED: callback.onCallStreamingFailed((int) arg /* reason */); break; diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl index 372e4a12ff70..ac496607abe6 100644 --- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl +++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl @@ -34,4 +34,5 @@ oneway interface ICallControl { void requestCallEndpointChange(in CallEndpoint callEndpoint, in ResultReceiver callback); void setMuteState(boolean isMuted, in ResultReceiver callback); void sendEvent(String callId, String event, in Bundle extras); + void requestVideoState(int videoState, String callId, in ResultReceiver callback); }
\ No newline at end of file diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl index 213cafbbf188..e4d6b0c79c49 100644 --- a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl +++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl @@ -45,6 +45,8 @@ oneway interface ICallEventCallback { void onCallEndpointChanged(String callId, in CallEndpoint endpoint); void onAvailableCallEndpointsChanged(String callId, in List<CallEndpoint> endpoint); void onMuteStateChanged(String callId, boolean isMuted); + // -- Video Related + void onVideoStateChanged(String callId, int videoState); // -- Events void onEvent(String callId, String event, in Bundle extras); // hidden methods that help with cleanup diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java index 4ff9712f0907..633694ac97da 100644 --- a/telephony/java/android/telephony/DomainSelectionService.java +++ b/telephony/java/android/telephony/DomainSelectionService.java @@ -20,6 +20,7 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; @@ -855,7 +856,8 @@ public abstract class DomainSelectionService extends Service { * * @return an {@link Executor} used to execute methods called remotely by the framework. */ - public @NonNull Executor onCreateExecutor() { + @SuppressLint("OnNameExpected") + public @NonNull Executor getCreateExecutor() { return Runnable::run; } @@ -869,7 +871,7 @@ public abstract class DomainSelectionService extends Service { public final @NonNull Executor getCachedExecutor() { synchronized (mExecutorLock) { if (mExecutor == null) { - Executor e = onCreateExecutor(); + Executor e = getCreateExecutor(); mExecutor = (e != null) ? e : Runnable::run; } return mExecutor; diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index cd641b8be0b6..c5f2d42389e5 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -4690,7 +4690,6 @@ public class SubscriptionManager { * @param subscriptionId the subId of the subscription * @param userHandle user handle of the user * @return {@code true} if subscription is associated with user - * {code true} if there are no subscriptions on device * else {@code false} if subscription is not associated with user. * * @throws IllegalArgumentException if subscription doesn't exist. @@ -4721,6 +4720,37 @@ public class SubscriptionManager { } /** + * Returns whether the given subscription is associated with the calling user. + * + * @param subscriptionId the subscription ID of the subscription + * @return {@code true} if the subscription is associated with the user that the current process + * is running in; {@code false} otherwise. + * + * @throws IllegalArgumentException if subscription doesn't exist. + * @throws SecurityException if the caller doesn't have permissions required. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @FlaggedApi(Flags.FLAG_SUBSCRIPTION_USER_ASSOCIATION_QUERY) + public boolean isSubscriptionAssociatedWithUser(int subscriptionId) { + if (!isValidSubscriptionId(subscriptionId)) { + throw new IllegalArgumentException("[isSubscriptionAssociatedWithCallingUser]: " + + "Invalid subscriptionId: " + subscriptionId); + } + + try { + ISub iSub = TelephonyManager.getSubscriptionService(); + if (iSub != null) { + return iSub.isSubscriptionAssociatedWithCallingUser(subscriptionId); + } else { + throw new IllegalStateException("subscription service unavailable."); + } + } catch (RemoteException ex) { + ex.rethrowAsRuntimeException(); + } + return false; + } + + /** * Get list of subscriptions associated with user. * * @param userHandle user handle of the user diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 041822bf4ee9..fd9aae9ff835 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15011,6 +15011,27 @@ public class TelephonyManager { } /** + * Get the emergency assistance package name. + * + * @return the package name of the emergency assistance app. + * @throws IllegalStateException if emergency assistance is not enabled. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @FlaggedApi(android.permission.flags.Flags.FLAG_GET_EMERGENCY_ROLE_HOLDER_API_ENABLED) + @NonNull + @SystemApi + public String getEmergencyAssistancePackage() { + if (!isEmergencyAssistanceEnabled()) { + throw new IllegalStateException("isEmergencyAssistanceEnabled() is false."); + } + String emergencyRole = mContext.getSystemService(RoleManager.class) + .getEmergencyRoleHolder(mContext.getUserId()); + return Objects.requireNonNull(emergencyRole, "Emergency role holder must not be null"); + } + + /** * Get the emergency number list based on current locale, sim, default, modem and network. * * <p>In each returned list, the emergency number {@link EmergencyNumber} coming from higher diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index cc770aa65888..6678f408e720 100644 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -332,12 +332,23 @@ interface ISub { UserHandle getSubscriptionUserHandle(int subId); /** + * Returns whether the given subscription is associated with the calling user. + * + * @param subscriptionId the subscription ID of the subscription + * @return {@code true} if the subscription is associated with the user that the current process + * is running in; {@code false} otherwise. + * + * @throws IllegalArgumentException if subscription doesn't exist. + * @throws SecurityException if the caller doesn't have permissions required. + */ + boolean isSubscriptionAssociatedWithCallingUser(int subscriptionId); + + /** * Check if subscription and user are associated with each other. * * @param subscriptionId the subId of the subscription * @param userHandle user handle of the user * @return {@code true} if subscription is associated with user - * {code true} if there are no subscriptions on device * else {@code false} if subscription is not associated with user. * * @throws IllegalArgumentException if subscription is invalid. diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml index c62db1ea5ca9..f602c5124e77 100644 --- a/tests/Input/AndroidTest.xml +++ b/tests/Input/AndroidTest.xml @@ -4,6 +4,7 @@ --> <configuration description="Runs Input Tests"> <option name="test-tag" value="InputTests" /> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on" /> @@ -22,4 +23,9 @@ <option name="test-timeout" value="600s" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> </test> + <object class="com.android.tradefed.testtype.suite.module.TestFailureModuleController" + type="module_controller"> + <!-- Take screenshot upon test failure --> + <option name="screenshot-on-failure" value="true" /> + </object> </configuration> diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index c6dd29ce7cc6..30333da5e86c 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -54,6 +54,7 @@ java_library { // This library is _not_ specific to Android APIs. java_library_host { name: "hoststubgen-helper-runtime", + defaults: ["ravenwood-internal-only-visibility-java"], srcs: [ "helper-runtime-src/**/*.java", ], @@ -64,11 +65,11 @@ java_library_host { "guava", ], jarjar_rules: "jarjar-rules.txt", - visibility: ["//visibility:public"], } java_library { name: "hoststubgen-helper-runtime.ravenwood", + defaults: ["ravenwood-internal-only-visibility-java"], srcs: [ "helper-runtime-src/**/*.java", ], @@ -79,7 +80,6 @@ java_library { "guava", ], jarjar_rules: "jarjar-rules.txt", - visibility: ["//visibility:public"], } // Host-side stub generator tool. @@ -152,34 +152,3 @@ genrule_defaults { "hoststubgen_dump.txt", ], } - -java_library_host { - name: "hoststubgen-helper-libcore-runtime", - srcs: [ - "helper-framework-runtime-src/libcore-fake/**/*.java", - ], - visibility: ["//visibility:private"], -} - -java_host_for_device { - name: "hoststubgen-helper-libcore-runtime.ravenwood", - libs: [ - "hoststubgen-helper-libcore-runtime", - ], - visibility: ["//visibility:private"], -} - -java_library { - name: "hoststubgen-helper-framework-runtime.ravenwood", - defaults: ["ravenwood-internal-only-visibility-java"], - srcs: [ - "helper-framework-runtime-src/framework/**/*.java", - ], - libs: [ - "hoststubgen-helper-runtime.ravenwood", - "framework-minus-apex.ravenwood", - ], - static_libs: [ - "hoststubgen-helper-libcore-runtime.ravenwood", - ], -} diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh deleted file mode 100755 index a6847ae97bae..000000000000 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -# 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. - -source "${0%/*}"/../common.sh - -# Move to the top directory of hoststubgen -cd .. - -ATEST_ARGS="--host" - -# These tests are known to pass. -READY_TEST_MODULES=( - hoststubgen-test-tiny-test - CtsUtilTestCasesRavenwood - CtsOsTestCasesRavenwood # This one uses native sustitution, so let's run it too. -) - -MUST_BUILD_MODULES=( - "${NOT_READY_TEST_MODULES[*]}" -) - -# First, build all the test / etc modules. This shouldn't fail. -run m "${MUST_BUILD_MODULES[@]}" - -# Run the hoststubgen unittests / etc -run atest $ATEST_ARGS hoststubgentest hoststubgen-invoke-test - -# Next, run the golden check. This should always pass too. -# The following scripts _should_ pass too, but they depend on the internal paths to soong generated -# files, and they may fail when something changes in the build system. -run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh - -run ./hoststubgen/test-tiny-framework/run-test-manually.sh -run atest $ATEST_ARGS tiny-framework-dump-test - -# This script is already broken on goog/master -# run ./scripts/build-framework-hostside-jars-without-genrules.sh - -# These tests should all pass. -run atest $ATEST_ARGS ${READY_TEST_MODULES[*]} - -echo ""${0##*/}" finished, with no failures. Ready to submit!" diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index 3c55237ce443..ce856cd49614 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -25,6 +25,7 @@ import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.OutputStream +import java.time.LocalDateTime import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.jar.JarOutputStream @@ -42,6 +43,13 @@ object ProtoLogTool { return source.contains(protoLogSimpleClassName) } + private fun zipEntry(path: String): ZipEntry { + val entry = ZipEntry(path) + // Use a constant time to improve the cachability of build actions. + entry.timeLocal = LocalDateTime.of(2008, 1, 1, 0, 0, 0) + return entry + } + private fun processClasses(command: CommandOptions) { val groups = injector.readLogGroups( command.protoLogGroupsJarArg, @@ -77,7 +85,7 @@ object ProtoLogTool { } }.map { future -> val (path, outSrc) = future.get() - outJar.putNextEntry(ZipEntry(path)) + outJar.putNextEntry(zipEntry(path)) outJar.write(outSrc.toByteArray()) outJar.closeEntry() } @@ -90,7 +98,7 @@ object ProtoLogTool { val cachePackage = cacheSplit.dropLast(1).joinToString(".") val cachePath = "gen/${cacheSplit.joinToString("/")}.java" - outJar.putNextEntry(ZipEntry(cachePath)) + outJar.putNextEntry(zipEntry(cachePath)) outJar.write(generateLogGroupCache(cachePackage, cacheName, groups, command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray()) |