Merge "Add compat change permissions to privapp list for intentresolver" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7e6c30f..2846221 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -75,6 +75,7 @@
":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 @@
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 e12f74f..870df5a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -508,6 +508,8 @@
lint: {
baseline_filename: "lint-baseline.xml",
},
+ // For jarjar repackaging
+ jarjar_prefix: "com.android.internal.hidden_from_bootclasspath",
}
java_library {
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 f819f15..bd00c03 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 @@
// 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 @@
// 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 6ed42ec..3219f7e 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_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 @@
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 26dc700..adcc3df 100644
--- a/boot/hiddenapi/hiddenapi-unsupported.txt
+++ b/boot/hiddenapi/hiddenapi-unsupported.txt
@@ -133,8 +133,6 @@
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 f1e4d0ee..fd571c9 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 2d03be7..0c11811 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -144,10 +144,12 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 b36b963f..1b0da05 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -245,6 +245,14 @@
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 @@
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 @@
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 159410d..15de42a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -100,6 +100,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 a6505c8..ca9fab8 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -535,6 +535,10 @@
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 3838b94..f6366a2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1519,6 +1519,7 @@
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 39e46bb..de4e607 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 23fe731..fae9f83 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -844,7 +844,28 @@
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 @@
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 @@
* 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 @@
}
/**
+ * 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 @@
if (android.security.Flags.contentUriPermissionApis()) {
mInitialCaller = new ComponentCaller(getActivityToken(), initialCallerInfoAccessToken);
+ mCaller = mInitialCaller;
}
}
@@ -8815,6 +8938,16 @@
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 a59f04b..10dc3c6 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -299,6 +299,26 @@
}
}
+ /** 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 a8d183a..237d31c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -20,6 +20,7 @@
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 @@
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 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 @@
| 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 @@
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 @@
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 e14bf68..2a2c5f0 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -20,6 +20,7 @@
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 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 b25d5eb..00ccd04 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4203,7 +4203,12 @@
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 2100425..16c7753 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.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 @@
private static Boolean sFullLog = null;
final Context mContext;
+ private PermissionUsageHelper mUsageHelper;
@UnsupportedAppUsage
final IAppOpsService mService;
@@ -3229,6 +3234,10 @@
* @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 @@
}
/**
+ * 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 @@
@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 @@
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 c6712c0..3715c6e 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -22,6 +22,7 @@
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 @@
private @StartType int mStartType;
/**
- * @see #getStartIntent
+ * @see #getIntent
*/
private Intent mStartIntent;
@@ -259,6 +260,11 @@
private @LaunchMode int mLaunchMode;
/**
+ * @see #wasForceStopped()
+ */
+ private boolean mWasForceStopped;
+
+ /**
* @hide *
*/
@IntDef(
@@ -427,6 +433,15 @@
}
/**
+ * @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 @@
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 @@
dest.writeInt(mStartType);
dest.writeParcelable(mStartIntent, flags);
dest.writeInt(mLaunchMode);
+ dest.writeBoolean(mWasForceStopped);
}
/** @hide */
@@ -622,6 +652,7 @@
mStartType = other.mStartType;
mStartIntent = other.mStartIntent;
mLaunchMode = other.mLaunchMode;
+ mWasForceStopped = other.mWasForceStopped;
}
private ApplicationStartInfo(@NonNull Parcel in) {
@@ -643,6 +674,7 @@
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 @@
intentOut.close();
}
proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
+ proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
proto.end(token);
}
@@ -799,6 +832,10 @@
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 @@
.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 @@
&& 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 f727ee5..1b5b0fc 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 @@
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 @@
"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 @@
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 @@
}
/**
+ * 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 @@
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 44e8a0a..14bc003 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.os.IBinder;
import android.os.Process;
-import androidx.annotation.NonNull;
-
import java.util.Objects;
/**
@@ -45,7 +44,7 @@
/**
* @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 @@
* @see Activity#getLaunchedFromUid()
*/
public int getUid() {
- return ActivityClient.getInstance().getLaunchedFromUid(mActivityToken);
+ return ActivityClient.getInstance().getActivityCallerUid(mActivityToken, mCallerToken);
}
/**
@@ -121,7 +120,7 @@
*/
@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 05fee72..9c8fea1 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -90,7 +90,9 @@
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 454d605..be7199b 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,7 +1625,51 @@
* @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
+ */
+ @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;
+ }
+ Intent newIntent = intent != null ? new Intent(intent) : null;
+ callActivityOnNewIntent(activity, newIntent, caller);
+ } finally {
+ activity.mReferrer = oldReferrer;
+ }
}
/**
@@ -1632,14 +1677,19 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent) {
- final String oldReferrer = activity.mReferrer;
- try {
- if (intent != null) {
- activity.mReferrer = intent.mReferrer;
+ 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;
}
- callActivityOnNewIntent(activity, intent != null ? new Intent(intent) : null);
- } finally {
- activity.mReferrer = oldReferrer;
}
}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index f92ff83..da0cc01 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -31,6 +31,7 @@
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 ff23f09..350b1ed 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -34,3 +34,10 @@
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 a884ab0..3c56aaf 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -20,6 +20,7 @@
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 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 c53b54c..a6fda9d 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_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_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_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.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 @@
private final boolean mParentInstance;
private final DevicePolicyResourcesManager mResourcesManager;
-
/** @hide */
public DevicePolicyManager(Context context, IDevicePolicyManager service) {
this(context, service, false);
@@ -4116,6 +4120,19 @@
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 @@
}
/**
- * 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 @@
}
/**
+ * 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 @@
* 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 @@
* 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 @@
}
/**
+ *
+ * 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 07ee8de..1aee9fe 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -338,4 +338,9 @@
* 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/location/java/android/location/IGeocodeListener.aidl b/core/java/android/app/admin/IAuditLogEventsCallback.aidl
similarity index 66%
copy from location/java/android/location/IGeocodeListener.aidl
copy to core/java/android/app/admin/IAuditLogEventsCallback.aidl
index 8e10411..ab87117 100644
--- a/location/java/android/location/IGeocodeListener.aidl
+++ b/core/java/android/app/admin/IAuditLogEventsCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,11 @@
* limitations under the License.
*/
-package android.location;
+package android.app.admin;
-import android.location.Address;
+import android.app.admin.SecurityLog;
-/**
- * An interface for returning geocode results.
- *
- * {@hide}
- */
-interface IGeocodeListener {
-
- oneway void onResults(String error, in List<Address> results);
-}
+/** @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 c4cbdd6..3a7a891c 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.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 @@
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 @@
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 @@
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/location/java/android/location/GeocoderParams.aidl b/core/java/android/app/admin/SecurityLog.aidl
similarity index 73%
copy from location/java/android/location/GeocoderParams.aidl
copy to core/java/android/app/admin/SecurityLog.aidl
index 2484e20..e5ae2df 100644
--- a/location/java/android/location/GeocoderParams.aidl
+++ b/core/java/android/app/admin/SecurityLog.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2010, The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 +14,7 @@
* limitations under the License.
*/
-package android.location;
+package android.app.admin;
-parcelable GeocoderParams;
+/** @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 30cd1b7..cbd8e5b 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -92,6 +92,13 @@
}
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 @@
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 0000000..6932ba2
--- /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 e8031a3..0bcbb8e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -19,6 +19,7 @@
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 @@
"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 a8dba51..cae4fab 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1565,6 +1565,14 @@
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 @@
+ 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 @@
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 @@
nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
requestRawExternalStorageAccess = orig.requestRawExternalStorageAccess;
localeConfigRes = orig.localeConfigRes;
+ allowCrossUidActivitySwitchFromBelow = orig.allowCrossUidActivitySwitchFromBelow;
createTimestamp = SystemClock.uptimeMillis();
}
@@ -2106,6 +2120,8 @@
}
}
dest.writeInt(localeConfigRes);
+ dest.writeInt(allowCrossUidActivitySwitchFromBelow ? 1 : 0);
+
sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
}
@@ -2204,6 +2220,8 @@
}
}
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 529363f..8220313 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -19,7 +19,9 @@
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 @@
}
}
+
+ /**
+ * 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 @@
}
}
+ /**
+ * 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 7c264f6..9c859c4 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 @@
/**
* 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 @@
* </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 @@
/**
* 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 214c3e4..8426f54 100644
--- a/core/java/android/content/pm/LauncherUserInfo.java
+++ b/core/java/android/content/pm/LauncherUserInfo.java
@@ -27,8 +27,6 @@
/**
* 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 @@
/**
* 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 @@
* {@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 49c8a7c..2a67353 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2554,6 +2554,15 @@
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 @@
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/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 317fa58..6696ba0 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -139,7 +139,7 @@
flag {
name: "enable_launcher_apps_hidden_profile_checks"
namespace: "profile_experiences"
- description: "Enable extra check to limit access to hidden prfiles data in Launcher apps APIs."
+ description: "Enable extra check to limit access to hidden profiles data in Launcher apps APIs."
bug: "321988638"
}
@@ -149,3 +149,10 @@
description: "Reorder loading home and lock screen wallpapers during a user switch."
bug: "324911115"
}
+
+flag {
+ name: "set_power_mode_during_user_switch"
+ namespace: "multiuser"
+ description: "Set power mode during a user switch."
+ bug: "325249845"
+}
diff --git a/location/java/android/location/GeocoderParams.aidl b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
similarity index 74%
copy from location/java/android/location/GeocoderParams.aidl
copy to core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
index 2484e20..838e41e 100644
--- a/location/java/android/location/GeocoderParams.aidl
+++ b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
@@ -1,5 +1,5 @@
-/*
- * Copyright (C) 2010, The Android Open Source Project
+/**
+ * Copyright (c) 2024, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,11 @@
* limitations under the License.
*/
-package android.location;
+package android.hardware;
-parcelable GeocoderParams;
+/** @hide */
+parcelable CameraPrivacyAllowlistEntry {
+ String packageName;
+ boolean isMandatory;
+}
+
diff --git a/core/java/android/hardware/ISensorPrivacyListener.aidl b/core/java/android/hardware/ISensorPrivacyListener.aidl
index 2ac21d2..19ae302 100644
--- a/core/java/android/hardware/ISensorPrivacyListener.aidl
+++ b/core/java/android/hardware/ISensorPrivacyListener.aidl
@@ -25,5 +25,6 @@
// 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 9cf329c..851ce2a 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 @@
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 @@
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 18c95bfb..6294a8d 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.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 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 @@
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 boolean isEnabled() {
return mEnabled;
}
+
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+ public @StateTypes.StateType int getState() {
+ return mState;
+ }
+
}
}
@@ -319,6 +369,9 @@
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 @@
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)));
+ 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, state)));
+ }
+ }
+ }
+
};
/** Whether the singleton ISensorPrivacyListener has been registered */
@@ -649,6 +723,73 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
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 451d6fb..2add77e 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -21,6 +21,7 @@
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 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 3b10e0d..f6b22ed 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -26,6 +26,7 @@
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.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 @@
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 7cf10d8..e24c98e 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -21,6 +21,7 @@
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 @@
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 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 ded96a2..66efccd1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -21,6 +21,7 @@
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 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 7cf5a7f..a01c23d 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -22,6 +22,7 @@
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 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 0000000..32039c6
--- /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 0000000..5c99909
--- /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 b4fe7fe..53f56bc 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -18,8 +18,11 @@
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 @@
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 @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 8415379..02e160c 100644
--- a/core/java/android/hardware/camera2/extension/OutputSurface.aidl
+++ b/core/java/android/hardware/camera2/extension/OutputSurface.aidl
@@ -24,4 +24,6 @@
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 0000000..15e8982
--- /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 0e6c1b3..69a6e9b 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 @@
private OutputConfiguration mPostviewOutput = null;
private Executor mExecutor = null;
private CameraExtensionSession.StateCallback mCallback = null;
+ private int mColorSpace;
/**
* Create a new ExtensionSessionConfiguration
@@ -118,4 +124,55 @@
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/UserManager.java b/core/java/android/os/UserManager.java
index 8b31f8b..2b30a2b 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -163,19 +163,16 @@
* 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 @@
* 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";
/**
@@ -3263,7 +3258,11 @@
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 49b7463..6895d3c 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 @@
/**
* 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 @@
private final @Nullable CharSequence mAttributionTag;
private final @Nullable CharSequence mAttributionLabel;
private final @Nullable CharSequence mProxyLabel;
+ private final @NonNull String mPersistentDeviceId;
@@ -79,7 +81,8 @@
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 @@
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 @@
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 @@
"phoneCall = " + mPhoneCall + ", " +
"attributionTag = " + mAttributionTag + ", " +
"attributionLabel = " + mAttributionLabel + ", " +
- "proxyLabel = " + mProxyLabel +
+ "proxyLabel = " + mProxyLabel + ", " +
+ "persistentDeviceId = " + mPersistentDeviceId +
" }";
}
@@ -210,7 +223,8 @@
&& 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 @@
_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 @@
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 @@
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 @@
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 @@
};
@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 e6b8102..fd52c76 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1329,7 +1329,9 @@
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 1f798ba..460b4dd 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -41,6 +41,8 @@
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.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 @@
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 @@
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 @@
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 @@
}
/**
- * @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 @@
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 @@
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 @@
* 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 9d7fb70..9218cb8 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -96,9 +96,37 @@
}
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 786d768..aa47d3a 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -673,6 +673,10 @@
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 0000000..09774f7
--- /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 2028c40..9db8aa1 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -273,7 +273,7 @@
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 @@
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 @@
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 @@
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 @@
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 9286049..cce4f7b 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -25,6 +25,7 @@
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 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
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 @@
.setAddLastLineLineSpacing(!islast)
.setIncludePad(false)
.setUseBoundsForWidth(mUseBoundsForWidth)
+ .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
.setMinimumFontMetrics(mMinimumFontMetrics)
.setCalculateBounds(true);
@@ -1392,6 +1427,7 @@
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 e5d199a..8e52af3 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -44,6 +44,7 @@
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 @@
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 @@
int justificationMode,
LineBreakConfig lineBreakConfig,
boolean useBoundsForWidth,
+ boolean shiftDrawingOffsetForStartOverhang,
Paint.FontMetrics minimumFontMetrics
) {
@@ -384,6 +386,7 @@
mJustificationMode = justificationMode;
mLineBreakConfig = lineBreakConfig;
mUseBoundsForWidth = useBoundsForWidth;
+ mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
mMinimumFontMetrics = minimumFontMetrics;
}
@@ -465,7 +468,7 @@
@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 @@
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 @@
}
/**
+ * 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 @@
.setJustificationMode(mJustificationMode)
.setLineBreakConfig(mLineBreakConfig)
.setUseBoundsForWidth(mUseBoundsForWidth)
+ .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
.build();
} else {
return new BoringLayout(
@@ -3955,7 +3989,7 @@
mIncludePad, mFallbackLineSpacing, mEllipsizedWidth, mEllipsize, mMaxLines,
mBreakStrategy, mHyphenationFrequency, mLeftIndents, mRightIndents,
mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth,
- mMinimumFontMetrics);
+ mShiftDrawingOffsetForStartOverhang, mMinimumFontMetrics);
}
}
@@ -3980,6 +4014,7 @@
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 @@
}
/**
+ * 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 5986238..3dd3a9e 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -24,6 +24,7 @@
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 @@
}
/**
+ * 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 @@
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 @@
JUSTIFICATION_MODE_NONE,
null, // lineBreakConfig,
false, // useBoundsForWidth
+ false, // shiftDrawingOffsetForStartOverhang
null // minimumFontMetrics
);
@@ -677,7 +709,7 @@
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/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 9413f5c..5ec41591 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -202,38 +202,4 @@
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 eb28920..676b903 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -23,6 +23,9 @@
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 @@
}
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 @@
// 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 @@
}
}
+ 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 @@
&& 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 @@
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 29cc859..c475f6b 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -1098,4 +1098,7 @@
* (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 55e49f8..d68a47c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -370,11 +370,6 @@
*/
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 1dd9cbb..06a923a 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -293,12 +293,12 @@
/**
* 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 @@
}
/**
- * 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 @@
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 25ade62..73b6ed6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5638,7 +5638,7 @@
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 @@
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 657c8e6..9474a69 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -27,6 +27,8 @@
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 @@
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 @@
// 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 @@
}
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 @@
*/
@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 @@
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 @@
*/
@VisibleForTesting
public float getPreferredFrameRate() {
- return mPreferredFrameRate;
+ return mPreferredFrameRate >= 0 ? mPreferredFrameRate : mLastPreferredFrameRate;
}
/**
@@ -12431,19 +12462,6 @@
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 0302a0d..e4be016 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1548,6 +1548,48 @@
"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 @@
* 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 @@
* @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 @@
}
/**
+ * 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 1428963..584219a 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -839,14 +839,15 @@
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 @@
}
}));
}
+ 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 @@
}
}));
}
+ return inputTransferToken;
}
void unregisterSurfaceControlInputReceiver(SurfaceControl surfaceControl) {
@@ -930,6 +934,17 @@
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 1e3d062..8972228 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -533,22 +533,24 @@
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 @@
}
@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 3f1ae51..2b2c507 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -651,21 +651,6 @@
}
@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 fa0052c..749f977 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 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 @@
* @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 a11ac7c..5b99c71f 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 {
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 57e4e6a..9847cb1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -181,6 +181,7 @@
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 @@
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 @@
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 @@
* @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 @@
* 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 @@
}
/**
+ * 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 @@
null,
boring,
mUseBoundsForWidth,
+ mShiftDrawingOffsetForStartOverhang,
getResolvedMinimumFontMetrics());
}
@@ -11028,6 +11086,7 @@
effectiveEllipsize,
boring,
mUseBoundsForWidth,
+ mShiftDrawingOffsetForStartOverhang,
getResolvedMinimumFontMetrics());
}
}
diff --git a/core/java/android/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java
index bed0e0e..e572853 100644
--- a/core/java/android/window/InputTransferToken.java
+++ b/core/java/android/window/InputTransferToken.java
@@ -20,8 +20,12 @@
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 @@
/**
* 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 3ffa274..8b3bd97 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -20,13 +20,6 @@
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 bc63881..ce74848 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -69,4 +69,12 @@
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 3a321e5..3ec7064 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -162,4 +162,5 @@
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 6af03dd..2c68203 100644
--- a/core/java/com/android/internal/content/ReferrerIntent.java
+++ b/core/java/com/android/internal/content/ReferrerIntent.java
@@ -18,6 +18,7 @@
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 @@
@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 @@
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 @@
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/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index d433ca3..73df5e8 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 @@
// Derived fields
private long mLongVersionCode;
private int mLocaleConfigRes;
+ private boolean mAllowCrossUidActivitySwitchFromBelow;
private List<AndroidPackageSplit> mSplits;
@@ -1542,6 +1543,11 @@
}
@Override
+ public boolean isAllowCrossUidActivitySwitchFromBelow() {
+ return mAllowCrossUidActivitySwitchFromBelow;
+ }
+
+ @Override
public boolean hasPreserveLegacyExternalStorage() {
return getBoolean(Booleans.PRESERVE_LEGACY_EXTERNAL_STORAGE);
}
@@ -2199,6 +2205,12 @@
}
@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 @@
if (!mKnownActivityEmbeddingCerts.isEmpty()) {
appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts);
}
+ appInfo.allowCrossUidActivitySwitchFromBelow = mAllowCrossUidActivitySwitchFromBelow;
return appInfo;
}
@@ -3250,6 +3263,7 @@
dest.writeInt(this.uid);
dest.writeLong(this.mBooleans);
dest.writeLong(this.mBooleans2);
+ dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
}
public PackageImpl(Parcel in) {
@@ -3411,6 +3425,7 @@
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 ef106e0..5d185af 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 @@
ParsingPackage setZygotePreloadName(String zygotePreloadName);
+ ParsingPackage setAllowCrossUidActivitySwitchFromBelow(
+ boolean allowCrossUidActivitySwitchFromBelow);
+
ParsingPackage sortActivities();
ParsingPackage sortReceivers();
@@ -518,6 +521,8 @@
@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 e0fdbc6..2e6053d 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 @@
.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 b5b3a48..e46b8d7 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1615,7 +1615,8 @@
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 @@
/**
* 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 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 096f246..d430fe3 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 @@
* @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 8c33041..d9ed911 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -39,4 +39,5 @@
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 b9905e8..b7408a4 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -113,6 +113,7 @@
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 9359528..e368c6a 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -91,6 +91,9 @@
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 @@
// 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 e4e8f7e..71f06f1 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 4ee03de..cc6460e 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 647bccf..b2e0be7c 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 dcb6bb0..8af8cb8 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 b085a27..5637063 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 2327b20..48ef7e6 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.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 @@
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 @@
* 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 @@
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 @@
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 661b210..402d08e 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.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 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 aa89e04..cc342cf 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.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 @@
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 0657e4b..433d353 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -660,10 +660,22 @@
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 @@
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 @@
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 @@
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,18 +919,6 @@
ViewRootImpl viewRootImpl = view.getViewRootImpl();
- // Frequent update
- 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);
- });
-
// In transistion from frequent update to infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
@@ -901,9 +926,28 @@
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_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 c8cbb98..42e3387 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 b33a5d2..f3bb217 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1128,6 +1128,30 @@
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 @@
@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 dffcc6d..b5ea1b1 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_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 @@
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 @@
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 @@
}
}
+ 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 @@
/** 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 @@
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 14b8d8d..0b739c3 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -70,6 +70,8 @@
: 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 @@
// Ensure path clips are anti-aliased
canvas->clipPath(mPath.value(), mOp, true);
break;
+ case Type::Shader:
+ canvas->clipShader(mShader, mOp);
}
}
@@ -94,6 +98,7 @@
Rect,
RRect,
Path,
+ Shader,
};
Type mType;
@@ -103,6 +108,7 @@
// 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 @@
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 5e3553b..4a012bc 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -97,6 +97,7 @@
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 20e3ad2..14b4f58 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -188,6 +188,7 @@
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 e5bdeee..1fc34d6 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -261,6 +261,23 @@
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 @@
{"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 5ed8c3c..85e9f65 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -88,12 +88,12 @@
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 b1cf96d..2e7a541 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -591,6 +591,36 @@
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 @@
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 a158344..cdde7c6 100644
--- a/location/java/android/location/Geocoder.java
+++ b/location/java/android/location/Geocoder.java
@@ -21,11 +21,13 @@
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.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 @@
* 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 @@
*/
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 @@
* <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 @@
* <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 @@
* <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 @@
* <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 @@
* 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 @@
* 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 @@
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 e661ca6..0000000
--- 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 72761ef..c96c118 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -19,13 +19,11 @@
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.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 @@
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 0fa641b..156be38 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/GeocoderParams.aidl b/location/java/android/location/provider/ForwardGeocodeRequest.aidl
similarity index 74%
copy from location/java/android/location/GeocoderParams.aidl
copy to location/java/android/location/provider/ForwardGeocodeRequest.aidl
index 2484e20..acd6190 100644
--- a/location/java/android/location/GeocoderParams.aidl
+++ b/location/java/android/location/provider/ForwardGeocodeRequest.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2010, The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 +14,6 @@
* limitations under the License.
*/
-package android.location;
+package android.location.provider;
-parcelable GeocoderParams;
+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 0000000..8f227b1
--- /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 0000000..e2c48b9
--- /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
similarity index 76%
rename from location/java/android/location/IGeocodeListener.aidl
rename to location/java/android/location/provider/IGeocodeCallback.aidl
index 8e10411..cf52713 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 0000000..9217438
--- /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
similarity index 75%
rename from location/java/android/location/GeocoderParams.aidl
rename to location/java/android/location/provider/ReverseGeocodeRequest.aidl
index 2484e20..015757a 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 0000000..57c9047
--- /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 c9be579..b10019a 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -30,6 +30,7 @@
"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 400a7dd..ae51872 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.
+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.
---- 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.
+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 7046abd..75e6bb4 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 @@
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
similarity index 65%
rename from core/java/android/location/GeocoderParams.java
rename to location/lib/java/android/location/GeocoderParams.java
index 3ea6364..780ccf4 100644
--- a/core/java/android/location/GeocoderParams.java
+++ b/location/lib/java/android/location/GeocoderParams.java
@@ -18,7 +18,7 @@
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.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 @@
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 @@
/**
* Returns the client UID.
*/
- @UnsupportedAppUsage
public int getClientUid() {
return mUid;
}
@@ -70,7 +70,6 @@
/**
* Returns the client package name.
*/
- @UnsupportedAppUsage
public @NonNull String getClientPackage() {
return mPackageName;
}
@@ -78,7 +77,6 @@
/**
* Returns the client attribution tag.
*/
- @UnsupportedAppUsage
public @Nullable String getClientAttributionTag() {
return mAttributionTag;
}
@@ -86,34 +84,38 @@
/**
* 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();
+ 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));
- }
+ return new GeocoderParams(
+ uid,
+ packageName,
+ attributionTag,
+ new Locale(language, country, variant));
+ }
- public GeocoderParams[] newArray(int size) {
- return new GeocoderParams[size];
- }
- };
+ 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 05d7935..45077ba 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.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 ac94a6f..9548525 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3557,6 +3557,36 @@
}
/**
+ * 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 @@
}
/**
- * 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 3174c37..1e7bc47 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -20,10 +20,12 @@
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.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 @@
}
}
+ /** @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 5e40eee..7b83842 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 @@
@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 c4b38c7..b165809 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 fa9afa8..a8e9423 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -232,52 +232,8 @@
+ " 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 @@
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 @@
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 @@
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 @@
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 @@
* children changed.
*/
public void notifyChildrenChanged(@NonNull String parentId) {
- notifyChildrenChangedInternal(parentId, null);
+ notifyChildrenChanged(parentId, Bundle.EMPTY);
}
/**
@@ -617,30 +523,10 @@
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 @@
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 @@
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 @@
iter.remove();
}
}
- if (callbackList.size() == 0) {
+ if (callbackList.isEmpty()) {
connection.subscriptions.remove(id);
}
}
@@ -709,44 +682,53 @@
/**
* 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 @@
}
}
- 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 3254a39..f264b16 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -44,6 +44,8 @@
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 @@
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 @@
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 @@
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 @@
}
/**
+ * 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 0000000..cda6b8b
--- /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 0000000..1f037c0
--- /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 ab8c6b8..6925c71 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 @@
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 @@
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 430d733..7bd43d2 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 @@
)
)
var mediaController: MediaController? = null
- underTest.activeMediaController
+ underTest.activeLocalMediaController
.onEach { mediaController = it }
.launchIn(backgroundScope)
runCurrent()
@@ -141,7 +141,7 @@
)
)
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 5669276..8edda1a 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 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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 4305d91..53f2caf 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 0000000..aad593e
--- /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/core/src/com/android/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
index 1d6f813..b28655b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/AndroidColorScheme.kt
@@ -69,6 +69,7 @@
val onTertiary = getColor(context, R.attr.materialColorOnTertiary)
val surfaceDim = getColor(context, R.attr.materialColorSurfaceDim)
val surfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
+ val error = getColor(context, R.attr.materialColorError)
val onError = getColor(context, R.attr.materialColorOnError)
val surface = getColor(context, R.attr.materialColorSurface)
val surfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
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 374a97d..4398b25 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 @@
override fun setVolumePanelActivityContent(
activity: ComponentActivity,
viewModel: VolumePanelViewModel,
- onDismissAnimationFinished: () -> Unit,
+ onDismiss: () -> Unit,
) {
throwComposeUnavailableError()
}
diff --git a/location/java/android/location/GeocoderParams.aidl b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
similarity index 68%
copy from location/java/android/location/GeocoderParams.aidl
copy to packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
index 2484e20..8ad0a08 100644
--- a/location/java/android/location/GeocoderParams.aidl
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2010, The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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 +14,8 @@
* limitations under the License.
*/
-package android.location;
+package com.android.systemui.volume.panel.component.mediaoutput
-parcelable GeocoderParams;
+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 a1bbc7d..aa56736 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 @@
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 f387021..ed2cbb8 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 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 @@
) {
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 43d5453..236aee2 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 @@
@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 03c07f7..0cf4367 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.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 @@
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 0000000..c73656e
--- /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 0000000..8ad6fdf
--- /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 0000000..98ef067
--- /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 e8d5966..86eb849 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 @@
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 @@
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 c70c6b1..10731c7 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 60d03fc..dd63420 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.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 @@
@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 @@
}
@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 3dfe65a..51c008a 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.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 @@
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 @@
@OpenForTesting
open fun querySettings() {
- assertNotMainThread()
+ assert.isNotMainThread()
val result =
try {
val json =
@@ -313,7 +314,7 @@
@OpenForTesting
open fun applySettings(value: ClockSettings?) {
- assertNotMainThread()
+ assert.isNotMainThread()
try {
value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis())
@@ -339,16 +340,6 @@
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 @@
}
scope.launch(mainDispatcher) {
- assertMainThread()
+ assert.isMainThread()
isClockChanged.set(false)
clockChangeListeners.forEach { it.onCurrentClockChanged() }
}
@@ -371,7 +362,7 @@
}
scope.launch(mainDispatcher) {
- assertMainThread()
+ assert.isMainThread()
isClockListChanged.set(false)
clockChangeListeners.forEach { it.onAvailableClocksChanged() }
}
@@ -585,7 +576,7 @@
* 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 @@
* 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
similarity index 88%
rename from packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioModeInteractorTest.kt
index 4dbf865..fe34361 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 @@
@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 0000000..ec37925
--- /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 0000000..243aab2
--- /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 7c99360..71866b3 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 @@
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 @@
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 5db9eee..109e63c 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 7537a00..a681da3 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 e401c71..a7d93e7 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 ab3cacb..f1d4d71 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 c053b33..2f2b10f 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 8bf7560d..0140d52 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 ee260e1..8b2a0ec 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.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 @@
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 @@
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 2db3795..e621ffe 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.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 @@
}
int getNotificationIconAreaHeight() {
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
return 0;
} else if (NotificationIconContainerRefactor.isEnabled()) {
return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
@@ -597,7 +596,7 @@
}
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 3585feb..84c8ea7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -19,6 +19,7 @@
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 @@
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 db7ff88..bf8900d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -331,6 +331,9 @@
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 fcff0db..bcab6f0 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 @@
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 83b1a2c..3e87c1b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -67,6 +67,11 @@
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 1758831..9421f15 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.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 @@
}
mDumpManager.registerDumpable(getInstanceName(), this);
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
startCoroutines(EmptyCoroutineContext.INSTANCE);
}
}
@@ -511,7 +510,7 @@
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 536f3af..38c2829e 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.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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
@VisibleForTesting
void resetBiometricListeningState() {
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ mFingerprintDetectRunning = false;
}
@VisibleForTesting
@@ -2537,8 +2560,11 @@
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 @@
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 @@
&& biometricEnabledForUser
&& !isUserInLockdown(user);
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
- final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
final boolean shouldListenBouncerState =
!strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
@@ -2872,7 +2908,7 @@
}
}
- 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 @@
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 9ebae90..2000028 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.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 @@
animProps.setDelay(0).setDuration(160);
log("goingToFullShade && !keyguardFadingAway");
}
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
log("Using LockscreenToGoneTransition 1");
} else {
PropertyAnimator.setProperty(
@@ -167,7 +167,7 @@
animProps,
true /* animate */);
} else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
log("Using GoneToAodTransition");
mKeyguardViewVisibilityAnimating = false;
} else {
@@ -183,7 +183,7 @@
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 878a5d8..a0f15ef 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.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 @@
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 3397906..0bd44f0 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 @@
@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 @@
}
}
+
+ 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 @@
() -> 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 31aadf5..b0cc3bd 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.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 @@
(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 @@
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 2417fe9..a37d916 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.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 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 @@
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 @@
// 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 @@
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 @@
// 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 7b4be02..9c28f1c 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 @@
}
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 8197145..c25e748 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_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 @@
} else {
faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
}
+ } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
+ authRequiredAfterAdaptiveAuthRequest(
+ currentSecurityMode,
+ isFingerprintAuthCurrentlyAllowed.value
+ )
} else if (
trustOrBiometricsAvailable &&
flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
@@ -464,6 +470,34 @@
}.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 8178ade..a0aaa90 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 @@
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 41ce3fd..7a24d76 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.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 @@
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 e23ec89..00ec1a1 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 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 @@
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 @@
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 86b99ec..e35c5a6 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.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 @@
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 @@
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 63fd608..641b967 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.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;
@@ -920,15 +921,17 @@
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 405d1d4..78749ea 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 @@
/**
* 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 23642a7..0000000
--- 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 cf5b88f..08904b6 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 @@
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 6e70368..66fc995 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.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 @@
constraintLayout: ConstraintLayout,
viewModel: KeyguardBlueprintViewModel,
clockViewModel: KeyguardClockViewModel,
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
) {
constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -108,10 +110,18 @@
) {
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 @@
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 5604ef2..c58a03c 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.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 @@
}
}
- if (KeyguardShadeMigrationNssl.isEnabled) {
+ if (migrateClocksToBlueprint()) {
launch {
viewModel.burnInLayerVisibility.collect { visibility ->
childViews[burnInLayerId]?.visibility = visibility
@@ -316,7 +315,7 @@
}
}
- if (KeyguardShadeMigrationNssl.isEnabled) {
+ if (migrateClocksToBlueprint()) {
burnInParams.update { current ->
current.copy(translationY = { childViews[burnInLayerId]?.translationY })
}
@@ -415,7 +414,9 @@
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 @@
}
when {
!isVisible.isAnimating -> {
- if (!KeyguardShadeMigrationNssl.isEnabled) {
+ if (!migrateClocksToBlueprint()) {
translationY = 0f
}
visibility =
@@ -494,7 +495,7 @@
animatorListener: Animator.AnimatorListener,
) {
if (animate) {
- if (!KeyguardShadeMigrationNssl.isEnabled) {
+ if (!migrateClocksToBlueprint()) {
translationY = -iconAppearTranslation.toFloat()
}
alpha = 0f
@@ -502,19 +503,19 @@
.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 bc9671e..77f7ac8 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.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 @@
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
clockSection: ClockSection,
smartspaceSection: SmartspaceSection,
+ keyguardSliceViewSection: KeyguardSliceViewSection,
udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = DEFAULT
@@ -83,6 +85,7 @@
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 d118d4d..55b2381 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.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 @@
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
clockSection: ClockSection,
smartspaceSection: SmartspaceSection,
+ keyguardSliceViewSection: KeyguardSliceViewSection,
udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection,
) : KeyguardBlueprint {
override val id: String = SHORTCUTS_BESIDE_UDFPS
@@ -78,6 +80,7 @@
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 a7075d9..3adeb2a 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 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 @@
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 9a1fcc1..282c495 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 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 @@
) : KeyguardSection() {
private lateinit var burnInLayer: AodBurnInLayer
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!KeyguardShadeMigrationNssl.isEnabled) {
+ if (!migrateClocksToBlueprint()) {
return
}
@@ -58,16 +57,14 @@
}
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 ad589df..3d9c04e 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.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 @@
private lateinit var nic: NotificationIconContainer
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!KeyguardShadeMigrationNssl.isEnabled) {
+ if (!migrateClocksToBlueprint()) {
return
}
nic =
@@ -78,7 +77,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!KeyguardShadeMigrationNssl.isEnabled) {
+ if (!migrateClocksToBlueprint()) {
return
}
@@ -99,7 +98,7 @@
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!KeyguardShadeMigrationNssl.isEnabled) {
+ if (!migrateClocksToBlueprint()) {
return
}
val bottomMargin =
@@ -114,12 +113,9 @@
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 75132a5..218af29 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 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 @@
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 851a45f..390b39f 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.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 @@
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 @@
}
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 0000000..d572c51
--- /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 52d94a0..d0f57c7 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.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 @@
}
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 @@
}
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 8255bcc..b0f7a25 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 @@
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 @@
override fun bindData(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) return
+ if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
KeyguardSmartspaceViewBinder.bind(
constraintLayout,
keyguardClockViewModel,
@@ -93,6 +95,7 @@
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 @@
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 3e35ae4..2545302 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.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 @@
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 64cbb32..ab0d489 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.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 @@
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 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 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 a3888c3..9fa1423 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 @@
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 85885b0..9fc759b 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 @@
return transitionAnimation.sharedFlowWithState(
startTime = 600.milliseconds,
duration = 500.milliseconds,
- onStart = { translatePx },
onStep = { translatePx + it * -translatePx },
onFinish = { 0f },
onCancel = { 0f },
@@ -66,7 +65,6 @@
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 c61b1f5..d7ba46b 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 @@
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 19c11a9..3a19780 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 @@
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 1b14f75..898eacf 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.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 @@
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 @@
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 c87fd14..952f9b8 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.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 @@
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 @@
mediaViewHolder.albumView.backgroundTintList = colorList
mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor)
}
-
val accentPrimary =
animatingColorTransitionFactory(
loadDefaultColor(R.attr.textColorPrimary),
@@ -139,6 +140,7 @@
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 aa92814..e97c9d3d 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.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.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.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 @@
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 @@
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 @@
}
}
- // Turbulence noise
if (shouldPlayTurbulenceNoise()) {
+ // Need to create the config here to get the correct view size and color.
if (mTurbulenceNoiseAnimationConfig == null) {
mTurbulenceNoiseAnimationConfig =
- createTurbulenceNoiseAnimation();
+ createTurbulenceNoiseConfig();
}
- // Color will be correctly updated in ColorSchemeTransition.
- mTurbulenceNoiseController.play(
- Type.SIMPLEX_NOISE,
- mTurbulenceNoiseAnimationConfig
- );
- mMainExecutor.executeDelayed(
- mTurbulenceNoiseController::finish,
- TURBULENCE_NOISE_PLAY_DURATION
- );
+
+ 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
+ );
+ }
}
+
mButtonClicked = false;
mWasPlaying = isPlaying();
@@ -1232,7 +1281,13 @@
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 @@
/* 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 25d89fa..02be0c1 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.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 @@
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 092f1ed..152f193 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 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 @@
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 @@
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 @@
/** @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 @@
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/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index a755805..4ccb18f 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 @@
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 @@
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 2f0fc51..ee602e5 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_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.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 @@
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 @@
}
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 @@
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 fe45df8..9a03393 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -136,7 +136,6 @@
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;
@@ -1021,7 +1020,7 @@
instantCollapse();
} else {
mView.animate().cancel();
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mView.animate()
.alpha(0f)
.setStartDelay(0)
@@ -1157,7 +1156,7 @@
// Occluded->Lockscreen
collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
mOccludedToLockscreenTransition, mMainDispatcher);
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
collectFlow(mView,
@@ -1168,7 +1167,7 @@
// Lockscreen->Dreaming
collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition, mMainDispatcher);
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
mMainDispatcher);
@@ -1180,7 +1179,7 @@
// Gone->Dreaming
collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(),
mGoneToDreamingTransition, mMainDispatcher);
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
}
@@ -1191,7 +1190,7 @@
// Lockscreen->Occluded
collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(),
mLockscreenToOccludedTransition, mMainDispatcher);
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY(),
@@ -1199,7 +1198,7 @@
}
// Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth)
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
}
@@ -1277,7 +1276,7 @@
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
@@ -1333,7 +1332,7 @@
// 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);
}
@@ -1354,7 +1353,7 @@
}
updateClockAppearance();
mQsController.updateQsState();
- if (!KeyguardShadeMigrationNssl.isEnabled() && !FooterViewRefactor.isEnabled()) {
+ if (!migrateClocksToBlueprint() && !FooterViewRefactor.isEnabled()) {
mNotificationStackScrollLayoutController.updateFooter();
}
}
@@ -1386,7 +1385,7 @@
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);
@@ -1506,7 +1505,7 @@
}
private void updateMaxDisplayedNotifications(boolean recompute) {
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
return;
}
@@ -1663,7 +1662,7 @@
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mKeyguardStatusViewController.setLockscreenClockY(
mClockPositionAlgorithm.getExpandedPreferredClockY());
}
@@ -1677,7 +1676,7 @@
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX, mClockPositionResult.clockY,
mClockPositionResult.clockScale, animateClock);
@@ -1753,7 +1752,7 @@
private void updateKeyguardStatusViewAlignment(boolean animate) {
boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
ConstraintLayout layout;
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
layout = mKeyguardViewConfigurator.getKeyguardRootView();
} else {
layout = mNotificationContainerParent;
@@ -1929,7 +1928,7 @@
}
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
@@ -2522,7 +2521,7 @@
void requestScrollerTopPaddingUpdate(boolean animate) {
float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
getKeyguardNotificationStaticPadding(), mExpandedFraction);
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
mSharedNotificationContainerInteractor.setTopPosition(padding);
} else {
mNotificationStackScrollLayoutController.updateTopPadding(padding, animate);
@@ -2704,7 +2703,7 @@
return;
}
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
float alpha = 1f;
if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
&& !mHeadsUpManager.hasPinnedHeadsUp()) {
@@ -2739,7 +2738,7 @@
}
private void updateKeyguardBottomAreaAlpha() {
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
return;
}
if (mIsOcclusionTransitionRunning) {
@@ -2980,7 +2979,7 @@
@Override
public void onScreenTurningOn() {
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mKeyguardStatusViewController.dozeTimeTick();
}
}
@@ -3232,7 +3231,7 @@
public void dozeTimeTick() {
mLockIconViewController.dozeTimeTick();
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mKeyguardStatusViewController.dozeTimeTick();
}
if (mInterpolatedDarkAmount > 0) {
@@ -4448,7 +4447,7 @@
&& 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,
@@ -4568,7 +4567,7 @@
setDozing(true /* dozing */, false /* animate */);
mStatusBarStateController.setUpcomingState(KEYGUARD);
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
mStatusBarStateController.setState(KEYGUARD);
} else {
mStatusBarStateListener.onStateChanged(KEYGUARD);
@@ -4629,7 +4628,7 @@
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
// Update Clock Pivot (used by anti-burnin transformations)
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
}
@@ -4739,7 +4738,7 @@
private Consumer<Float> setTransitionY(
NotificationStackScrollLayoutController stackScroller) {
return (Float translationY) -> {
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mKeyguardStatusViewController.setTranslationY(translationY,
/* excludeMedia= */false);
stackScroller.setTranslationY(translationY);
@@ -4781,7 +4780,7 @@
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) {
+ if (migrateClocksToBlueprint() && !mUseExternalTouch) {
return false;
}
@@ -4852,7 +4851,7 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mCentralSurfaces.userActivity();
}
mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
@@ -4953,7 +4952,7 @@
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) {
+ if (migrateClocksToBlueprint() && !mUseExternalTouch) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index aa2d606..99e91c1 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.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 @@
mTouchActive = true;
mTouchCancelled = false;
mDownEvent = ev;
- if (KeyguardShadeMigrationNssl.isEnabled()) {
+ if (migrateClocksToBlueprint()) {
mService.userActivity();
}
} else if (ev.getActionMasked() == MotionEvent.ACTION_UP
@@ -475,7 +475,7 @@
&& !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 @@
// 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 @@
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 @@
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 @@
}
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 c0afa32..457b3d7 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.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 @@
}
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 25e558e..e82f2d3 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 @@
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.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 @@
@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 f3e9c75..f86c71b 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 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.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 @@
// 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 @@
&& 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 7cb3be7..6a2a6a4 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 @@
}
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 14230ba..19fe60a 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.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 @@
updateLockScreenAlignmentMsg();
updateLockScreenLogoutView();
updateLockScreenPersistentUnlockMsg();
+ if (enableAdaptiveAuth()) {
+ updateLockScreenAdaptiveAuthMsg(userId);
+ }
}
private void updateOrganizedOwnedDevice() {
@@ -740,6 +745,22 @@
}
}
+ 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 ef50265..2e71103 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 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.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 @@
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 2a4753d..9916ef6 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 @@
|| isNotifUserRedacted;
boolean notificationRequestsRedaction =
- ent.getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE;
+ ent.isNotificationVisibilityPrivate();
boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey());
if (keyguardPrivateNotifications()) {
@@ -767,9 +767,7 @@
}
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 8678f0a..e111525 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 @@
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 54b6ad7..fb67f7c 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 @@
/**
* 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 @@
/**
* 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 @@
.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 @@
// 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 @@
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 @@
// 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 @@
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 @@
/**
* 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 @@
}
} 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 @@
wasUpdated = false,
shouldHeadsUpEver = shouldHeadsUpEver,
shouldHeadsUpAgain = true,
- isAlerting = false,
+ isHeadsUpEntry = false,
isBinding = false,
)
@@ -418,21 +422,21 @@
/**
* 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 @@
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 @@
}
/**
- * 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 @@
/**
* 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 @@
*/
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 @@
// 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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 @@
/**
* 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 20fae88..c90acee 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 @@
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 boolean hasPulsingNotifications() {
- return mPulsing && mHasAlertEntries;
+ return mPulsing && mHasHeadsUpEntries;
}
public void setPulsing(boolean hasPulsing) {
@@ -716,8 +716,8 @@
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 aa9d3b2..933a780 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 @@
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 830b8c1..78e6a79 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.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.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 @@
}
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 a135802..b0fefdd 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 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.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 @@
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 @@
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 e79f3ff..94f62e0 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.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 @@
return;
}
if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ if (!migrateClocksToBlueprint()) {
mAodIcons.setTranslationY(-mAodIconAppearTranslation);
}
mAodIcons.setAlpha(0);
@@ -557,14 +557,14 @@
.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 @@
}
} 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 8ac3b4a..d10ca3d 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 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 @@
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 @@
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/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 665a571..223eaf7 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_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 @@
// 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 a7352be..420701f 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 @@
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 eb08f37..eba3162 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 @@
*/
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 87dfc99..58b82f1 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 @@
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 androidx.annotation.NonNull;
+import com.android.internal.camera.flags.Flags;
+
import java.util.Set;
public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {
@@ -102,6 +107,13 @@
}
@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 2b0a92c..6956a7d 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 @@
// 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 ab76d45..9f99e97 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.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 @@
@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 0a1ee24..1f52260 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 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 @@
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 0000000..020ec64
--- /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 0000000..170b32c
--- /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 6c456f9..24cc29d 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 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 @@
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 f250308..71df8e5 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 @@
val appLabel: CharSequence,
val packageName: String,
val sessionToken: MediaSession.Token,
+ val playbackState: PlaybackState?,
) : MediaDeviceSession
/** Media is not playing. */
diff --git a/location/java/android/location/IGeocodeListener.aidl b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
similarity index 62%
copy from location/java/android/location/IGeocodeListener.aidl
copy to packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
index 8e10411..8ba672d 100644
--- a/location/java/android/location/IGeocodeListener.aidl
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,13 @@
* limitations under the License.
*/
-package android.location;
-
-import android.location.Address;
+package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
/**
- * An interface for returning geocode results.
- *
- * {@hide}
+ * Models part of the Media Session Volume Panel component that displays connected device
+ * information.
*/
-interface IGeocodeListener {
-
- oneway void onResults(String error, in List<Address> results);
-}
+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 0000000..e0718ac
--- /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 0000000..d148992
--- /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 842c323..6c742ba 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 @@
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 841daf8..afd3f61 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 @@
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 @@
// 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 defa92d..55d8de5 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 @@
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 a3f052d..867df4a 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 @@
@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 1b2265b..53e1b8b 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 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 @@
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 @@
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 f67db96..7f33a6b 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 @@
/**
* 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 d87a79e..3c5b75c 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 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 @@
.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 @@
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 cd19259..3026966 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 @@
@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 e8d86dd..9d81b96 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 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 @@
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 @@
@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 @@
@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 @@
@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 @@
@Test
public void testSmartspaceEnabledRemovesKeyguardStatusArea() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+
when(mSmartspaceController.isEnabled()).thenReturn(true);
mController.init();
@@ -113,6 +126,8 @@
@Test
public void onLocaleListChangedRebuildsSmartspaceView() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+
when(mSmartspaceController.isEnabled()).thenReturn(true);
mController.init();
@@ -123,6 +138,8 @@
@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 @@
@Test
public void testSmartspaceDisabledShowsKeyguardStatusArea() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
@@ -144,6 +163,8 @@
@Test
public void testRefresh() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+
mController.refresh();
verify(mSmartspaceController).requestSmartspaceUpdate();
@@ -151,6 +172,8 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 4508aea..b4a9d40 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.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 @@
@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 be06cc5..538daee 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 @@
verify(mHandler).postDelayed(mKeyguardUpdateMonitor.mFpCancelNotReceived,
DEFAULT_CANCEL_SIGNAL_TIMEOUT);
-
mKeyguardUpdateMonitor.onFingerprintAuthenticated(0, true);
mTestableLooper.processAllMessages();
@@ -2016,6 +2015,34 @@
}
@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 43f7c60..2c1a87d 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.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.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.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 @@
@Before
fun setup() {
+ mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
displayRepository = FakeDisplayRepository()
displayStateInteractor =
@@ -394,6 +399,19 @@
}
@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 2732047..0957748 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.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 @@
}
@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 ad86ee9..9c7f254 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.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 @@
@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 @@
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 deb3a83..8eccde7 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 @@
whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
.thenReturn(hasCustomWeatherDataDisplay)
whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
+ whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
+
constraintSet = ConstraintSet()
}
@@ -103,7 +105,6 @@
@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 @@
@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 @@
@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 5996502..c896486 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.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 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.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 @@
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 @@
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 @@
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 @@
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 @@
)
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 @@
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 bfb18c8..52859cd 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 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 @@
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 cc27cbd..44b8974 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.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 @@
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 2e8d46a..059053c 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 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 @@
@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 @@
@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 @@
@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 248ed24..c226790 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.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 @@
@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 @@
@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 @@
// 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 @@
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 @@
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 c326350..d7eada8 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.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 @@
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 64fd80d..74d0173 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.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 @@
@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 @@
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 ccc9dc0..8a48fe1 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 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 @@
}
@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/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 269b70f..5e8b62e 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 @@
}
@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 1dac642..a2af38f 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 @@
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 @@
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 @@
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 97f84c6..43897c9 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 @@
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 @@
@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/location/java/android/location/IGeocodeListener.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
similarity index 62%
copy from location/java/android/location/IGeocodeListener.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
index 8e10411..e1b1966 100644
--- a/location/java/android/location/IGeocodeListener.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,10 @@
* limitations under the License.
*/
-package android.location;
+package com.android.systemui.media
-import android.location.Address;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.util.mockito.mock
-/**
- * An interface for returning geocode results.
- *
- * {@hide}
- */
-interface IGeocodeListener {
-
- oneway void onResults(String error, in List<Address> results);
-}
+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 0000000..3f20df3
--- /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 0000000..5e1f85c
--- /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
similarity index 96%
rename from packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index dddf8e82..fed3e17 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
similarity index 71%
rename from packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
index 642b72c..7835fc8 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 0000000..6d52e52
--- /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 0000000..ad8ccb0
--- /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 0000000..1b3480c
--- /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/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh
index 3f4b8a7..259aa70 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/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 6ce471e..fff283d 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 a341b4a..2e14abb 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -263,6 +263,10 @@
// 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 @@
return mAllowedAssociations;
}
+ public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() {
+ return mAllowlistCameraPrivacy;
+ }
+
public ArraySet<String> getBugreportWhitelistedPackages() {
return mBugreportWhitelistedPackages;
}
@@ -1062,6 +1070,22 @@
}
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 0000000..3312be2
--- /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 c857235..1dc384d 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 @@
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 7d82f0c..df46e5d 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 @@
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.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 @@
(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 f85b03e..1bf779a 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 @@
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 @@
&& 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 d23d9fb..9883f09 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 @@
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 @@
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 57d233e..562beaf 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 @@
}
/**
- * 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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 55ac4cf..34ba7f0 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -97,6 +97,7 @@
import android.os.IRemoteCallback;
import android.os.IUserManager;
import android.os.Message;
+import android.os.PowerManagerInternal;
import android.os.PowerWhitelistManager;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -1934,9 +1935,12 @@
}
/**
- * Start user, if its not already running, and bring it to foreground.
+ * Start user, if it's not already running, and bring it to foreground.
*/
void startUserInForeground(@UserIdInt int targetUserId) {
+ if (android.multiuser.Flags.setPowerModeDuringUserSwitch()) {
+ mInjector.setPerformancePowerMode(true);
+ }
boolean success = startUser(targetUserId, USER_START_MODE_FOREGROUND);
if (!success) {
mInjector.getWindowManager().setSwitchingUser(false);
@@ -2146,6 +2150,9 @@
}
private void endUserSwitch() {
+ if (android.multiuser.Flags.setPowerModeDuringUserSwitch()) {
+ mInjector.setPerformancePowerMode(false);
+ }
final int nextUserId;
synchronized (mLock) {
nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
@@ -3535,6 +3542,7 @@
private final ActivityManagerService mService;
private UserManagerService mUserManager;
private UserManagerInternal mUserManagerInternal;
+ private PowerManagerInternal mPowerManagerInternal;
private Handler mHandler;
private final Object mUserSwitchingDialogLock = new Object();
@GuardedBy("mUserSwitchingDialogLock")
@@ -3636,6 +3644,13 @@
return mUserManagerInternal;
}
+ PowerManagerInternal getPowerManagerInternal() {
+ if (mPowerManagerInternal == null) {
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ }
+ return mPowerManagerInternal;
+ }
+
KeyguardManager getKeyguardManager() {
return mService.mContext.getSystemService(KeyguardManager.class);
}
@@ -3829,6 +3844,12 @@
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
}
+ void setPerformancePowerMode(boolean enabled) {
+ Slogf.i(TAG, "Setting power mode MODE_FIXED_PERFORMANCE to " + enabled);
+ getPowerManagerInternal().setPowerMode(
+ PowerManagerInternal.MODE_FIXED_PERFORMANCE, enabled);
+ }
+
void onSystemUserVisibilityChanged(boolean visible) {
getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5c95d43..fca1199 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.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.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.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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
});
+ mSensorPrivacyManager = SensorPrivacyManager.getInstance(mContext);
}
@VisibleForTesting
@@ -1253,20 +1161,27 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
if (resOps == null) {
resOps = new ArrayList<>();
}
- resOps.add(getOpEntryForResult(curOp));
+ resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
}
}
}
@@ -1588,16 +1610,23 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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 23a384f..bc6ef20 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_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 @@
}
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 @@
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 c59f4f7..559a1d6 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.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 @@
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 @@
}
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 @@
+ 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 @@
.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 @@
}
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 @@
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 4ceb83b2..409ed17 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 @@
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 @@
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 @@
* 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 @@
}
// 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 @@
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 @@
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 1376bde..35d38e2 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 @@
* 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 @@
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 4bb8e19..bc169ca 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 @@
@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 @@
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 @@
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 @@
// 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 @@
}
// 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 ac42646..0000000
--- 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 323cdc5..c5f3855 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.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.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.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 @@
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 @@
}
// 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 @@
}
@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 @@
}
@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 0000000..ac945f1
--- /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 7fb3e00..9a76ebd 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 @@
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_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 @@
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 @@
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 @@
setCurrentLskfBasedProtectorId(newProtectorId, userId);
LockPatternUtils.invalidateCredentialTypeCache();
synchronizeUnifiedChallengeForProfiles(userId, profilePasswords);
+ sendMainUserCredentialChangedNotificationIfNeeded(userId);
setUserPasswordMetrics(credential, userId);
mUnifiedProfilePasswordCache.removePassword(userId);
@@ -3071,6 +3080,24 @@
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 6d123cc..158d444 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.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 @@
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 @@
}
}
+ 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 f9ffb1c..b5c51af 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.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_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_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 @@
*/
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 @@
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 @@
}
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 @@
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 722654a..53244f9 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 {
- 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 d987622..8729522 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 @@
};
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 @@
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 @@
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 @@
}
@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 @@
}
}
+ @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 @@
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 @@
@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 0000000..09774f7
--- /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 295528e..f9d8112 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 @@
}
@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 85d2df3..bbce26c 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.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 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 @@
}
}
- 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 @@
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 @@
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 @@
}
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 @@
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/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index f44fcf0..21e2bf2 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.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.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 @@
@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 @@
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 @@
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 @@
@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 @@
*
* @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 @@
}
@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 c5b637d..70913c3 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 @@
}
@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 @@
@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 @@
}
@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 @@
}
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 @@
@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 @@
}
@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 @@
}
@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 @@
@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 @@
}
@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 7c10425..47032ea 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 @@
*
* @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 @@
* @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 @@
*
* @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 @@
*
* @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 @@
* @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 @@
* @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 @@
*
* @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 @@
*
* @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 91a778d..c18f856 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 @@
}
@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 @@
}
@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 @@
@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 @@
}
@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 0a4ff07..40139ba 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 @@
}
@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 @@
@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 @@
}
@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 @@
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
@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 bc29e67..981d3d9 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 @@
}
@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 @@
@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 @@
}
@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 @@
@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 @@
}
@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 @@
@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 59766ec..f8c678a 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.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 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_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.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 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
* @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 @@
== 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 @@
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 @@
* 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 @@
// 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 @@
}
@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 @@
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 @@
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 @@
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 9694958..14b13e0 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 @@
}
}
+ @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 @@
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 3dcb4cf..4b491ce 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 @@
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 d2f6701..4af8c61 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.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 @@
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 @@
}
}
+ 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 4772bbf..2e1049b 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.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 @@
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 @@
/**
* 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 @@
/**
* 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 @@
}
/**
+ * 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 92b327d..c64941b 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 4416605..e797290 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 @@
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 @@
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 @@
}
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 @@
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 efaa467..ed5df5f 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 @@
@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 @@
* 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 09c329b..b3a8b78 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 @@
}
}
- void computeInitialCallerInfo() {
- computeCallerInfo(initialCallerInfoAccessToken, intent, launchedFromUid);
+ boolean hasCaller(IBinder callerToken) {
+ return mCallerState.hasCaller(callerToken);
}
- void computeCallerInfo(IBinder callerToken, Intent intent, int callerUid) {
- mCallerState.computeCallerInfo(callerToken, intent, callerUid);
+ 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,
+ launchedFromPackage, mShareIdentity);
+ }
+
+ 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 @@
* 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 db27f60..01d077a 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 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 @@
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 a54dd82..3609837 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 @@
*/
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 1f013b9..f83003d 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 @@
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 07afa5f..65d01efc 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 @@
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 0e6c06d..445295a 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 @@
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 b65b2b2..3bc5319 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.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 @@
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 @@
// 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 @@
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 @@
boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
.shouldRestrictActivitySwitch(callingUid)
- && bas.mBlockActivityStartIfFlagEnabled;
+ && bas.mBlockActivityStartIfFlagEnabled;
PackageManager pm = mService.mContext.getPackageManager();
String callingPackage = pm.getNameForUid(callingUid);
@@ -1371,19 +1378,19 @@
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 @@
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 @@
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 @@
}
}
- 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 e6ef90b..68bff43 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.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 @@
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 @@
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 609ad1e..bf45804 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 @@
}
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 a7d6903b..5fe48d1 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 @@
}
/**
+ * 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 3c8c55e..975208f 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 @@
}
@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 401b260..8680436 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.util.ArrayMap;
import android.window.TaskSnapshot;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
/**
@@ -26,25 +28,31 @@
* @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 @@
/** 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 @@
}
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 @@
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 33486cc..b69ac1b 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 @@
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 d8e18e4..8b622d2 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 @@
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 ae4c3b9..669c61c 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 @@
/**
* 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 631ebcd..3d6bd4f 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 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 @@
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 @@
}
@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 @@
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 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);
+ }
}
-
- 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;
- }
- 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 f56e50e..6e993b3 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 @@
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 9bf41f9..58e198e 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_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_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_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_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_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.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.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 @@
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 @@
}
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 @@
@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 @@
}
}
- 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 @@
@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 @@
}
// 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 @@
}
@Override
- public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin) {
- Objects.requireNonNull(admin, "ComponentName is null");
+ 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));
-
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ }
return mOwners.getSystemUpdateInfo();
}
@@ -17873,6 +17930,82 @@
}
@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 @@
}
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 @@
}
@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 @@
});
}
- // 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 @@
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 @@
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 3474db3..1247f9002 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 @@
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 @@
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 4aaefa6..54242ab 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 @@
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 7a4454b..02f3918 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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
*/
@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 @@
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 @@
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 @@
}
}
// 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 @@
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 @@
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 1d89d17..ee758db 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.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 @@
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 @@
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 5588276..cb3ee73 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.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 @@
}
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 @@
)
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 @@
)
}
}
+ 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 67f66de..0704c8f 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 @@
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 @@
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 @@
override fun checkPermission(
packageName: String,
permissionName: String,
- persistentDeviceId: String,
+ deviceId: String,
userId: Int
): Int {
if (!userManagerInternal.exists(userId)) {
@@ -545,9 +539,7 @@
?: 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 @@
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 @@
userId,
isInstantApp,
fullerPermissionName,
- persistentDeviceId
+ deviceId
)
) {
return true
@@ -606,9 +590,9 @@
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 @@
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 @@
packageName,
userId,
permissionName,
- persistentDeviceId,
+ deviceId,
isGranted = false,
revokeReason = reason
)
@@ -740,7 +718,7 @@
packageName: String,
userId: Int,
permissionName: String,
- persistentDeviceId: String,
+ deviceId: String,
isGranted: Boolean,
skipKillUid: Boolean = false,
revokeReason: String? = null
@@ -765,7 +743,7 @@
(if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") +
", userId = $userId," +
" callingUid = $callingUidName ($callingUid))," +
- " persistentDeviceId = $persistentDeviceId",
+ " deviceId = $deviceId",
RuntimeException()
)
}
@@ -835,7 +813,7 @@
packageState,
userId,
permissionName,
- persistentDeviceId,
+ deviceId,
isGranted,
canManageRolePermission,
overridePolicyFixed,
@@ -923,7 +901,7 @@
packageState: PackageState,
userId: Int,
permissionName: String,
- persistentDeviceId: String,
+ deviceId: String,
isGranted: Boolean,
canManageRolePermission: Boolean,
overridePolicyFixed: Boolean,
@@ -982,8 +960,7 @@
}
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 @@
return
}
- setPermissionFlagsWithPolicy(appId, userId, permissionName, persistentDeviceId, newFlags)
+ setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
if (permission.isRuntime) {
val action =
@@ -1089,7 +1066,7 @@
override fun getPermissionFlags(
packageName: String,
permissionName: String,
- persistentDeviceId: String,
+ deviceId: String,
userId: Int,
): Int {
if (!userManagerInternal.exists(userId)) {
@@ -1125,12 +1102,7 @@
}
val flags =
- getPermissionFlagsWithPolicy(
- packageState.appId,
- userId,
- permissionName,
- persistentDeviceId
- )
+ getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId)
return PermissionFlags.toApiFlags(flags)
}
@@ -1138,7 +1110,7 @@
override fun getAllPermissionStates(
packageName: String,
- persistentDeviceId: String,
+ deviceId: String,
userId: Int
): Map<String, PermissionState> {
if (!userManagerInternal.exists(userId)) {
@@ -1165,14 +1137,15 @@
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 @@
override fun isPermissionRevokedByPolicy(
packageName: String,
permissionName: String,
- deviceId: Int,
+ deviceId: String,
userId: Int
): Boolean {
if (!userManagerInternal.exists(userId)) {
@@ -1207,24 +1180,13 @@
}
?: 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 @@
override fun shouldShowRequestPermissionRationale(
packageName: String,
permissionName: String,
- deviceId: Int,
+ deviceId: String,
userId: Int,
): Boolean {
if (!userManagerInternal.exists(userId)) {
@@ -1274,19 +1236,13 @@
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 @@
flagMask: Int,
flagValues: Int,
enforceAdjustPolicyPermission: Boolean,
- persistentDeviceId: String,
+ deviceId: String,
userId: Int
) {
val callingUid = Binder.getCallingUid()
@@ -1351,7 +1307,7 @@
"updatePermissionFlags(packageName = $packageName," +
" permissionName = $permissionName, flagMask = $flagMaskString," +
" flagValues = $flagValuesString, userId = $userId," +
- " persistentDeviceId = $persistentDeviceId," +
+ " deviceId = $deviceId," +
" callingUid = $callingUidName ($callingUid))",
RuntimeException()
)
@@ -1441,7 +1397,7 @@
appId,
userId,
permissionName,
- persistentDeviceId,
+ deviceId,
flagMask,
flagValues,
canUpdateSystemFlags,
@@ -1527,7 +1483,7 @@
appId: Int,
userId: Int,
permissionName: String,
- persistentDeviceId: String,
+ deviceId: String,
flagMask: Int,
flagValues: Int,
canUpdateSystemFlags: Boolean,
@@ -1561,8 +1517,7 @@
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 @@
}
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 @@
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 @@
)
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 @@
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 @@
}
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 @@
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 @@
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 @@
override fun onDevicePermissionFlagsChanged(
appId: Int,
userId: Int,
- persistentDeviceId: String,
+ deviceId: String,
permissionName: String,
oldFlags: Int,
newFlags: Int
@@ -2787,8 +2727,7 @@
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 @@
}
runtimePermissionChangedUidDevices.forEachIndexed { _, uid, persistentDeviceIds ->
- persistentDeviceIds.forEach { persistentDeviceId ->
- onPermissionsChangeListeners.onPermissionsChanged(uid, persistentDeviceId)
+ persistentDeviceIds.forEach { deviceId ->
+ onPermissionsChangeListeners.onPermissionsChanged(uid, deviceId)
}
}
runtimePermissionChangedUidDevices.clear()
@@ -2844,8 +2783,11 @@
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 @@
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 @@
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 cfe701f..d4b57f1 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 @@
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 9473e57..c298d51 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 @@
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 d876dae..47928bc 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.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 @@
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 @@
/**
* Replace the process LRU with the given processes.
- * @param apps
*/
@SuppressWarnings("GuardedBy")
private void setProcessesToLru(ProcessRecord... apps) {
@@ -660,7 +663,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
}
- 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);
+ @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 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;
+ private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
+ String packageName, boolean hasShownUi) {
+ return new ProcessRecordBuilder(pid, uid, processName, packageName).setHasShownUi(
+ hasShownUi).build();
}
private ServiceRecord makeServiceRecord(ProcessRecord app) {
@@ -2870,6 +2828,7 @@
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 @@
}
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 @@
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 0000000..08a6529
--- /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 0000000..0218a78
--- /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 6986cab..e59b5ea 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 @@
}
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 ee076c6..296d2cb 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 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 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 @@
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 7053597..4b22652 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.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 @@
}
@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 @@
}
@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 @@
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 fa3c7a4c..c01d0f6 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 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 @@
}).when(mPersistentDataBlockManager).getFrpCredentialHandle();
}
+ void setTestFactoryResetProtectionState(boolean active) {
+ mIsFactoryResetProtectionActive = active;
+ }
+
@Override
File getChildProfileLockFile(int userId) {
return remapToStorageDir(super.getChildProfileLockFile(userId));
@@ -101,4 +106,9 @@
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 4451cae..5f2abc3 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 @@
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_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_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.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.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 @@
}
@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 @@
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 f9ba33b..6e5c180 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 @@
}
@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 847c9d0..d5eeaa7 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 @@
.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 @@
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 2034751..0b466b2 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 @@
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 c404c77..bb5887d 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 @@
/* options */null,
/* inTask */null,
/* inTaskFragment */ null,
- BalVerdict.ALLOW_BY_DEFAULT,
+ BalVerdict.ALLOW_PRIVILEGED,
/* intentGrants */null,
/* realCaiingUid */ -1);
@@ -217,7 +217,7 @@
/* 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 06d30fc..29f48b8 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 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 df5f3d1..7432537 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 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 f02dd3f..cd3ce91 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.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.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 @@
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 8c6e101..afd34fc 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.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 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 @@
* 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 a1407869..808a575 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -293,12 +293,50 @@
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 a41c011..b0438bf 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 @@
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 467e89c..a2c6086 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.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 @@
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 @@
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 @@
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 372e4a12..ac49660 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -34,4 +34,5 @@
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 213cafb..e4d6b0c 100644
--- a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -45,6 +45,8 @@
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 4ff9712..633694a 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -20,6 +20,7 @@
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 @@
*
* @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 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 cd641b8..c5f2d42 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4690,7 +4690,6 @@
* @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 @@
}
/**
+ * 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 041822b..fd9aae9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -15011,6 +15011,27 @@
}
/**
+ * 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 cc770aa..6678f40 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -332,12 +332,23 @@
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 13b5f0d..f602c512 100644
--- a/tests/Input/AndroidTest.xml
+++ b/tests/Input/AndroidTest.xml
@@ -23,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/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
deleted file mode 100755
index a6847ae..0000000
--- 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!"