diff options
398 files changed, 9849 insertions, 2863 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 9ee74e377f90..1c6df75a4f02 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -56,6 +56,7 @@ aconfig_srcjars = [ ":android.service.notification.flags-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", ":android.speech.flags-aconfig-java{.generated_srcjars}", + ":android.systemserver.flags-aconfig-java{.generated_srcjars}", ":android.tracing.flags-aconfig-java{.generated_srcjars}", ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}", ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}", @@ -1159,3 +1160,16 @@ java_aconfig_library { host_supported: true, defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// System Server +aconfig_declarations { + name: "android.systemserver.flags-aconfig", + package: "android.server", + srcs: ["services/java/com/android/server/flags.aconfig"], +} + +java_aconfig_library { + name: "android.systemserver.flags-aconfig-java", + aconfig_declarations: "android.systemserver.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 324d8cafaefc..7284f479df35 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -23,7 +23,6 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.util.TimeUtils.formatDuration; import android.annotation.BytesLong; @@ -50,9 +49,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; -import android.os.Process; import android.os.Trace; -import android.os.UserHandle; import android.util.ArraySet; import android.util.Log; @@ -206,6 +203,8 @@ public class JobInfo implements Parcelable { /* Minimum flex for a periodic job, in milliseconds. */ private static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes + private static final long MIN_ALLOWED_TIME_WINDOW_MILLIS = MIN_PERIOD_MILLIS; + /** * Minimum backoff interval for a job, in milliseconds * @hide @@ -1881,11 +1880,12 @@ public class JobInfo implements Parcelable { } /** - * Set deadline which is the maximum scheduling latency. The job will be run by this - * deadline even if other requirements (including a delay set through - * {@link #setMinimumLatency(long)}) are not met. + * Set a deadline after which all other functional requested constraints will be ignored. + * After the deadline has passed, the job can run even if other requirements (including + * a delay set through {@link #setMinimumLatency(long)}) are not met. * {@link JobParameters#isOverrideDeadlineExpired()} will return {@code true} if the job's - * deadline has passed. + * deadline has passed. The job's execution may be delayed beyond the set deadline by + * other factors such as Doze mode and system health signals. * * <p> * Because it doesn't make sense setting this property on a periodic job, doing so will @@ -1894,30 +1894,23 @@ public class JobInfo implements Parcelable { * * <p class="note"> * Since a job will run once the deadline has passed regardless of the status of other - * constraints, setting a deadline of 0 with other constraints makes those constraints - * meaningless when it comes to execution decisions. Avoid doing this. - * </p> - * - * <p> - * Short deadlines hinder the system's ability to optimize scheduling behavior and may - * result in running jobs at inopportune times. Therefore, starting in Android version - * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, minimum time windows will be - * enforced to help make it easier to better optimize job execution. Time windows are + * constraints, setting a deadline of 0 (or a {@link #setMinimumLatency(long) delay} equal + * to the deadline) with other constraints makes those constraints + * meaningless when it comes to execution decisions. Since doing so is indicative of an + * error in the logic, starting in Android version + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, jobs with extremely short + * time windows will fail to build. Time windows are * defined as the time between a job's {@link #setMinimumLatency(long) minimum latency} * and its deadline. If the minimum latency is not set, it is assumed to be 0. - * The following minimums will be enforced: - * <ul> - * <li> - * Jobs with {@link #PRIORITY_DEFAULT} or higher priorities have a minimum time - * window of one hour. - * </li> - * <li>Jobs with {@link #PRIORITY_LOW} have a minimum time window of 6 hours.</li> - * <li>Jobs with {@link #PRIORITY_MIN} have a minimum time window of 12 hours.</li> - * </ul> * * Work that must happen immediately should use {@link #setExpedited(boolean)} or * {@link #setUserInitiated(boolean)} in the appropriate manner. * + * <p> + * This API aimed to guarantee execution of the job by the deadline only on Android version + * {@link android.os.Build.VERSION_CODES#LOLLIPOP}. That aim and guarantee has not existed + * since {@link android.os.Build.VERSION_CODES#M}. + * * @see JobInfo#getMaxExecutionDelayMillis() */ public Builder setOverrideDeadline(long maxExecutionDelayMillis) { @@ -2347,35 +2340,36 @@ public class JobInfo implements Parcelable { throw new IllegalArgumentException("Invalid priority level provided: " + mPriority); } - if (enforceMinimumTimeWindows - && Flags.enforceMinimumTimeWindows() - // TODO(312197030): remove exemption for the system - && !UserHandle.isCore(Process.myUid()) - && hasLateConstraint && !isPeriodic) { - final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0; - if (mPriority >= PRIORITY_DEFAULT) { - if (maxExecutionDelayMillis - windowStart < HOUR_IN_MILLIS) { - throw new IllegalArgumentException( - getPriorityString(mPriority) - + " cannot have a time window less than 1 hour." - + " Delay=" + windowStart - + ", deadline=" + maxExecutionDelayMillis); - } - } else if (mPriority >= PRIORITY_LOW) { - if (maxExecutionDelayMillis - windowStart < 6 * HOUR_IN_MILLIS) { - throw new IllegalArgumentException( - getPriorityString(mPriority) - + " cannot have a time window less than 6 hours." - + " Delay=" + windowStart - + ", deadline=" + maxExecutionDelayMillis); - } + final boolean hasFunctionalConstraint = networkRequest != null + || constraintFlags != 0 + || (triggerContentUris != null && triggerContentUris.length > 0); + if (hasLateConstraint && !isPeriodic) { + if (!hasFunctionalConstraint) { + Log.w(TAG, "Job '" + service.flattenToShortString() + "#" + jobId + "'" + + " has a deadline with no functional constraints." + + " The deadline won't improve job execution latency." + + " Consider removing the deadline."); } else { - if (maxExecutionDelayMillis - windowStart < 12 * HOUR_IN_MILLIS) { - throw new IllegalArgumentException( - getPriorityString(mPriority) - + " cannot have a time window less than 12 hours." - + " Delay=" + windowStart - + ", deadline=" + maxExecutionDelayMillis); + final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0; + if (maxExecutionDelayMillis - windowStart < MIN_ALLOWED_TIME_WINDOW_MILLIS) { + if (enforceMinimumTimeWindows + && Flags.enforceMinimumTimeWindows()) { + throw new IllegalArgumentException("Jobs with a deadline and" + + " functional constraints cannot have a time window less than " + + MIN_ALLOWED_TIME_WINDOW_MILLIS + " ms." + + " Job '" + service.flattenToShortString() + "#" + jobId + "'" + + " has delay=" + windowStart + + ", deadline=" + maxExecutionDelayMillis); + } else { + Log.w(TAG, "Job '" + service.flattenToShortString() + "#" + jobId + "'" + + " has a deadline with functional constraints and an extremely" + + " short time window of " + + (maxExecutionDelayMillis - windowStart) + " ms" + + " (delay=" + windowStart + + ", deadline=" + maxExecutionDelayMillis + ")." + + " The functional constraints are not likely to be satisfied when" + + " the job runs."); + } } } } diff --git a/api/Android.bp b/api/Android.bp index a148cbd05632..9d2147c32760 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -361,7 +361,10 @@ stubs_defaults { previous_api: ":android.api.public.latest", merge_annotations_dirs: ["metalava-manual"], defaults_visibility: ["//frameworks/base/api"], - visibility: ["//frameworks/base/api"], + visibility: [ + "//frameworks/base/api", + "//frameworks/base/core/api", + ], } // We resolve dependencies on APIs in modules by depending on a prebuilt of the whole diff --git a/api/api.go b/api/api.go index fa2be21db09f..c733f5b5bffd 100644 --- a/api/api.go +++ b/api/api.go @@ -130,7 +130,7 @@ type MergedTxtDefinition struct { Scope string } -func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) { +func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition, stubsTypeSuffix string, doDist bool) { metalavaCmd := "$(location metalava)" // Silence reflection warnings. See b/168689341 metalavaCmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED " @@ -140,7 +140,7 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) { if txt.Scope != "public" { filename = txt.Scope + "-" + filename } - moduleName := ctx.ModuleName() + "-" + filename + moduleName := ctx.ModuleName() + stubsTypeSuffix + filename props := genruleProps{} props.Name = proptools.StringPtr(moduleName) @@ -148,17 +148,19 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) { props.Out = []string{filename} props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --out $(out)") props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...) - props.Dists = []android.Dist{ - { - Targets: []string{"droidcore"}, - Dir: proptools.StringPtr("api"), - Dest: proptools.StringPtr(filename), - }, - { - Targets: []string{"api_txt", "sdk"}, - Dir: proptools.StringPtr("apistubs/android/" + txt.Scope + "/api"), - Dest: proptools.StringPtr(txt.DistFilename), - }, + if doDist { + props.Dists = []android.Dist{ + { + Targets: []string{"droidcore"}, + Dir: proptools.StringPtr("api"), + Dest: proptools.StringPtr(filename), + }, + { + Targets: []string{"api_txt", "sdk"}, + Dir: proptools.StringPtr("apistubs/android/" + txt.Scope + "/api"), + Dest: proptools.StringPtr(txt.DistFilename), + }, + } } props.Visibility = []string{"//visibility:public"} ctx.CreateModule(genrule.GenRuleFactory, &props) @@ -343,7 +345,7 @@ func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []str ctx.CreateModule(android.FileGroupFactory, &props) } -func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) { +func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string, baseTxtModulePrefix, stubsTypeSuffix string, doDist bool) { var textFiles []MergedTxtDefinition tagSuffix := []string{".api.txt}", ".removed-api.txt}"} @@ -352,7 +354,7 @@ func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_ textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, DistFilename: distFilename[i], - BaseTxt: ":non-updatable-" + f, + BaseTxt: ":" + baseTxtModulePrefix + f, Modules: bootclasspath, ModuleTag: "{.public" + tagSuffix[i], Scope: "public", @@ -360,7 +362,7 @@ func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_ textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, DistFilename: distFilename[i], - BaseTxt: ":non-updatable-system-" + f, + BaseTxt: ":" + baseTxtModulePrefix + "system-" + f, Modules: bootclasspath, ModuleTag: "{.system" + tagSuffix[i], Scope: "system", @@ -368,7 +370,7 @@ func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_ textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, DistFilename: distFilename[i], - BaseTxt: ":non-updatable-module-lib-" + f, + BaseTxt: ":" + baseTxtModulePrefix + "module-lib-" + f, Modules: bootclasspath, ModuleTag: "{.module-lib" + tagSuffix[i], Scope: "module-lib", @@ -376,14 +378,14 @@ func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_ textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, DistFilename: distFilename[i], - BaseTxt: ":non-updatable-system-server-" + f, + BaseTxt: ":" + baseTxtModulePrefix + "system-server-" + f, Modules: system_server_classpath, ModuleTag: "{.system-server" + tagSuffix[i], Scope: "system-server", }) } for _, txt := range textFiles { - createMergedTxt(ctx, txt) + createMergedTxt(ctx, txt, stubsTypeSuffix, doDist) } } @@ -465,7 +467,8 @@ func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) { bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...) sort.Strings(bootclasspath) } - createMergedTxts(ctx, bootclasspath, system_server_classpath) + createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-", "-", false) + createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-exportable-", "-exportable-", true) createMergedPublicStubs(ctx, bootclasspath) createMergedSystemStubs(ctx, bootclasspath) diff --git a/core/api/Android.bp b/core/api/Android.bp index 8d8a82b69b55..77594b758d19 100644 --- a/core/api/Android.bp +++ b/core/api/Android.bp @@ -96,3 +96,54 @@ filegroup { name: "non-updatable-test-lint-baseline.txt", srcs: ["test-lint-baseline.txt"], } + +// Exportable stub artifacts +filegroup { + name: "non-updatable-exportable-current.txt", + srcs: [":api-stubs-docs-non-updatable{.exportable.api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-removed.txt", + srcs: [":api-stubs-docs-non-updatable{.exportable.removed-api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-system-current.txt", + srcs: [":system-api-stubs-docs-non-updatable{.exportable.api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-system-removed.txt", + srcs: [":system-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-module-lib-current.txt", + srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable.api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-module-lib-removed.txt", + srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-test-current.txt", + srcs: [":test-api-stubs-docs-non-updatable{.exportable.api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-test-removed.txt", + srcs: [":test-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-system-server-current.txt", + srcs: [":services-non-updatable-stubs{.exportable.api.txt}"], +} + +filegroup { + name: "non-updatable-exportable-system-server-removed.txt", + srcs: [":services-non-updatable-stubs{.exportable.removed-api.txt}"], +} diff --git a/core/api/current.txt b/core/api/current.txt index f17d4a2958f9..f41982ff75bf 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1375,6 +1375,7 @@ package android { field public static final int reqTouchScreen = 16843303; // 0x1010227 field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603 field public static final int requestRawExternalStorageAccess = 16844357; // 0x1010645 + field @FlaggedApi("android.security.content_uri_permission_apis") public static final int requireContentUriPermissionFromCaller; field public static final int requireDeviceScreenOn = 16844317; // 0x101061d field public static final int requireDeviceUnlock = 16843756; // 0x10103ec field public static final int required = 16843406; // 0x101028e @@ -7895,6 +7896,7 @@ package android.app.admin { field public static final String AUTO_TIME_POLICY = "autoTime"; field public static final String BACKUP_SERVICE_POLICY = "backupService"; field public static final String CAMERA_DISABLED_POLICY = "cameraDisabled"; + field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String CONTENT_PROTECTION_POLICY = "contentProtection"; field public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures"; field public static final String LOCK_TASK_POLICY = "lockTask"; field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended"; @@ -7945,6 +7947,7 @@ package android.app.admin { method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public boolean getCameraDisabled(@Nullable android.content.ComponentName); method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException; + method @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional=true) public int getContentProtectionPolicy(@Nullable android.content.ComponentName); method @Nullable public android.app.admin.PackagePolicy getCredentialManagerPolicy(); method @Deprecated @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName); method @Deprecated public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName); @@ -8068,8 +8071,8 @@ package android.app.admin { method public boolean isUsbDataSignalingEnabled(); method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName); method @NonNull public java.util.List<android.os.UserHandle> listForegroundAffiliatedUsers(); - method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK, conditional=true) public void lockNow(); - method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK, conditional=true) public void lockNow(int); + method @RequiresPermission(value="android.permission.LOCK_DEVICE", conditional=true) public void lockNow(); + method @RequiresPermission(value="android.permission.LOCK_DEVICE", conditional=true) public void lockNow(int); method public int logoutUser(@NonNull android.content.ComponentName); method public void reboot(@NonNull android.content.ComponentName); method public void removeActiveAdmin(@NonNull android.content.ComponentName); @@ -8101,6 +8104,7 @@ package android.app.admin { method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException; method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, conditional=true) public void setCommonCriteriaModeEnabled(@Nullable android.content.ComponentName, boolean); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI, conditional=true) public void setConfiguredNetworksLockdownState(@Nullable android.content.ComponentName, boolean); + method @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional=true) public void setContentProtectionPolicy(@Nullable android.content.ComponentName, int); method public void setCredentialManagerPolicy(@Nullable android.app.admin.PackagePolicy); method @Deprecated public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>); method @Deprecated public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean); @@ -40957,6 +40961,14 @@ package android.service.notification { } +package android.service.persistentdata { + + @FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager { + method @FlaggedApi("android.security.frp_enforcement") public boolean isFactoryResetProtectionActive(); + } + +} + package android.service.quickaccesswallet { public interface GetWalletCardsCallback { @@ -46301,6 +46313,7 @@ package android.telephony.euicc { method @NonNull public android.telephony.euicc.EuiccManager createForCardId(int); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void deleteSubscription(int, android.app.PendingIntent); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent); + method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public long getAvailableMemoryInBytes(); method @Nullable public String getEid(); method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo(); method public boolean isEnabled(); @@ -46333,6 +46346,7 @@ package android.telephony.euicc { field public static final int ERROR_SIM_MISSING = 10008; // 0x2718 field public static final int ERROR_TIME_OUT = 10005; // 0x2715 field public static final int ERROR_UNSUPPORTED_VERSION = 10007; // 0x2717 + field @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") public static final long EUICC_MEMORY_FIELD_UNAVAILABLE = -1L; // 0xffffffffffffffffL field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE"; field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION"; field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_ERROR_CODE"; diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt index 162f54cc6d5a..e901f00d5f5f 100644 --- a/core/api/lint-baseline.txt +++ b/core/api/lint-baseline.txt @@ -389,6 +389,12 @@ InvalidNullabilityOverride: android.media.midi.MidiUmpDeviceService#onBind(andro Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. +KotlinOperator: android.graphics.Matrix44#get(int, int): + Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object) +KotlinOperator: android.graphics.Matrix44#set(int, int, float): + Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object) + + RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler): Method 'getAccountsByTypeAndFeatures' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler): @@ -477,6 +483,8 @@ RequiresPermission: android.app.admin.DevicePolicyManager#clearResetPasswordToke Method 'clearResetPasswordToken' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#generateKeyPair(android.content.ComponentName, String, android.security.keystore.KeyGenParameterSpec, int): Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName): + Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName): Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName): @@ -513,6 +521,8 @@ RequiresPermission: android.app.admin.DevicePolicyManager#removeCrossProfileWidg Method 'removeCrossProfileWidgetProvider' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage(android.content.ComponentName, String, boolean): Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int): + Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int): Method 'setLockTaskFeatures' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskPackages(android.content.ComponentName, String[]): diff --git a/core/api/module-lib-lint-baseline.txt b/core/api/module-lib-lint-baseline.txt index a2179bc59707..42c4efc139ca 100644 --- a/core/api/module-lib-lint-baseline.txt +++ b/core/api/module-lib-lint-baseline.txt @@ -611,6 +611,8 @@ RequiresPermission: android.app.admin.DevicePolicyManager#generateKeyPair(androi Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String): Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName): + Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName): Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName): @@ -657,6 +659,8 @@ RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage( Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>): Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int): + Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied(): Method 'setDeviceProvisioningConfigApplied' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int): diff --git a/core/api/system-current.txt b/core/api/system-current.txt index e998c2bc56bf..45d577855d3b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11,6 +11,7 @@ package android { field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES"; field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO"; field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER"; + field @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES_FULL = "android.permission.ACCESS_HIDDEN_PROFILES_FULL"; field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS"; field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACCESS_LAST_KNOWN_CELL_ID = "android.permission.ACCESS_LAST_KNOWN_CELL_ID"; field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS"; @@ -112,6 +113,7 @@ package android { field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; + field @FlaggedApi("android.security.frp_enforcement") public static final String CONFIGURE_FACTORY_RESET_PROTECTION = "android.permission.CONFIGURE_FACTORY_RESET_PROTECTION"; field public static final String CONFIGURE_INTERACT_ACROSS_PROFILES = "android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"; field @Deprecated public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL"; field public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"; @@ -3150,14 +3152,38 @@ package android.app.wallpapereffectsgeneration { package android.app.wearable { + @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public final class WearableSensingDataRequest implements android.os.Parcelable { + method public int describeContents(); + method public int getDataSize(); + method public int getDataType(); + method public static int getMaxRequestSize(); + method public static int getRateLimit(); + method @NonNull public static java.time.Duration getRateLimitWindowSize(); + method @NonNull public android.os.PersistableBundle getRequestDetails(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.wearable.WearableSensingDataRequest> CREATOR; + } + + @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final class WearableSensingDataRequest.Builder { + ctor public WearableSensingDataRequest.Builder(int); + method @NonNull public android.app.wearable.WearableSensingDataRequest build(); + method @NonNull public android.app.wearable.WearableSensingDataRequest.Builder setRequestDetails(@NonNull android.os.PersistableBundle); + } + public class WearableSensingManager { + method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent); method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideWearableConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void unregisterDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); field public static final int STATUS_ACCESS_DENIED = 5; // 0x5 + field @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") public static final int STATUS_CHANNEL_ERROR = 7; // 0x7 field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3 field public static final int STATUS_SUCCESS = 1; // 0x1 field public static final int STATUS_UNKNOWN = 0; // 0x0 field public static final int STATUS_UNSUPPORTED = 2; // 0x2 + field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8 field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6 field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4 } @@ -4411,8 +4437,9 @@ package android.credentials.selection { } @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable { + ctor public CancelSelectionRequest(@NonNull android.credentials.selection.RequestToken, boolean, @NonNull String); method public int describeContents(); - method @NonNull public String getAppPackageName(); + method @NonNull public String getPackageName(); method @NonNull public android.credentials.selection.RequestToken getRequestToken(); method public boolean shouldShowCancellationExplanation(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -4494,10 +4521,10 @@ package android.credentials.selection { @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable { method public int describeContents(); - method @NonNull public String getAppPackageName(); method @Nullable public android.credentials.CreateCredentialRequest getCreateCredentialRequest(); method @NonNull public java.util.List<java.lang.String> getDefaultProviderIds(); method @Nullable public android.credentials.GetCredentialRequest getGetCredentialRequest(); + method @NonNull public String getPackageName(); method @NonNull public java.util.List<java.lang.String> getRegistryProviderIds(); method @NonNull public android.credentials.selection.RequestToken getRequestToken(); method @NonNull public String getType(); @@ -6950,6 +6977,8 @@ package android.media { @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") public final class FadeManagerConfiguration implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributesWithVolumeShaperConfigs(); + method public static long getDefaultFadeInDurationMillis(); + method public static long getDefaultFadeOutDurationMillis(); method public long getFadeInDelayForOffenders(); method public long getFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes); method public long getFadeInDurationForUsage(int); @@ -6975,7 +7004,6 @@ package android.media { field @NonNull public static final android.os.Parcelable.Creator<android.media.FadeManagerConfiguration> CREATOR; field public static final long DURATION_NOT_SET = 0L; // 0x0L field public static final int FADE_STATE_DISABLED = 0; // 0x0 - field public static final int FADE_STATE_ENABLED_AUTO = 2; // 0x2 field public static final int FADE_STATE_ENABLED_DEFAULT = 1; // 0x1 field public static final String TAG = "FadeManagerConfiguration"; field public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2; // 0x2 @@ -6990,10 +7018,10 @@ package android.media { method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableContentType(int); method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableUid(int); method @NonNull public android.media.FadeManagerConfiguration build(); - method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsage(int); - method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes); - method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentType(int); - method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUid(int); + method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsages(); + method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(); + method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentTypes(); + method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUids(); method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDelayForOffenders(long); method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long); method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForUsage(int, long); @@ -12400,6 +12428,7 @@ package android.service.euicc { method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean); method @Deprecated public abstract int onEraseSubscriptions(int); method public int onEraseSubscriptions(int, int); + method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") public long onGetAvailableMemoryInBytes(int); method public abstract android.service.euicc.GetDefaultDownloadableSubscriptionListResult onGetDefaultDownloadableSubscriptionList(int, boolean); method public abstract android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, android.telephony.euicc.DownloadableSubscription, boolean); method @NonNull public android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean); @@ -12670,13 +12699,15 @@ package android.service.oemlock { package android.service.persistentdata { - public class PersistentDataBlockManager { + @FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager { + method @FlaggedApi("android.security.frp_enforcement") @RequiresPermission(android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION) public boolean deactivateFactoryResetProtection(@NonNull byte[]); method @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public int getDataBlockSize(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState(); method public long getMaximumDataBlockSize(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled(); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName(); method public byte[] read(); + method @FlaggedApi("android.security.frp_enforcement") public boolean setFactoryResetProtectionSecret(@NonNull byte[]); method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean); method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe(); method public int write(byte[]); @@ -13423,12 +13454,24 @@ package android.service.watchdog { package android.service.wearable { + @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public interface WearableSensingDataRequester { + method public void requestData(@NonNull android.app.wearable.WearableSensingDataRequest, @NonNull java.util.function.Consumer<java.lang.Integer>); + field public static final int STATUS_OBSERVER_CANCELLED = 2; // 0x2 + field public static final int STATUS_SUCCESS = 1; // 0x1 + field public static final int STATUS_TOO_FREQUENT = 4; // 0x4 + field public static final int STATUS_TOO_LARGE = 3; // 0x3 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + public abstract class WearableSensingService extends android.app.Service { ctor public WearableSensingService(); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverRegistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverUnregistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>); method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>); + method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureWearableConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>); method public abstract void onStopDetection(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService"; @@ -13604,6 +13647,13 @@ package android.telecom { method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference); } + public final class DisconnectCause implements android.os.Parcelable { + ctor @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public DisconnectCause(int, @NonNull CharSequence, @NonNull CharSequence, @NonNull String, int, int, int, @Nullable android.telephony.ims.ImsReasonInfo); + method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable public android.telephony.ims.ImsReasonInfo getImsReasonInfo(); + method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public int getTelephonyDisconnectCause(); + method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public int getTelephonyPreciseDisconnectCause(); + } + public abstract class InCallService extends android.app.Service { method @Deprecated public android.telecom.Phone getPhone(); method @Deprecated public void onPhoneCreated(android.telecom.Phone); @@ -14176,12 +14226,12 @@ package android.telephony { method @NonNull public android.telephony.DataThrottlingRequest.Builder setDataThrottlingAction(int); } - @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public class DomainSelectionService extends android.app.Service { + @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public abstract class DomainSelectionService extends android.app.Service { ctor public DomainSelectionService(); method public void onBarringInfoUpdated(int, int, @NonNull android.telephony.BarringInfo); - method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); + method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); method @NonNull public java.util.concurrent.Executor onCreateExecutor(); - method public void onDomainSelection(@NonNull android.telephony.DomainSelectionService.SelectionAttributes, @NonNull android.telephony.TransportSelectorCallback); + 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 field public static final int SCAN_TYPE_LIMITED_SERVICE = 1; // 0x1 @@ -14195,7 +14245,7 @@ package android.telephony { method @Nullable public android.net.Uri getAddress(); method @Nullable public String getCallId(); method public int getCsDisconnectCause(); - method @Nullable public android.telephony.EmergencyRegResult getEmergencyRegResult(); + method @Nullable public android.telephony.EmergencyRegistrationResult getEmergencyRegistrationResult(); method @Nullable public android.telephony.ims.ImsReasonInfo getPsDisconnectCause(); method public int getSelectorType(); method public int getSlotIndex(); @@ -14211,13 +14261,13 @@ package android.telephony { @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final class DomainSelectionService.SelectionAttributes.Builder { ctor public DomainSelectionService.SelectionAttributes.Builder(int, int, int); method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes build(); - method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setAddress(@NonNull android.net.Uri); - method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCallId(@NonNull String); + method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setAddress(@Nullable android.net.Uri); + method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCallId(@Nullable String); method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCsDisconnectCause(int); method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergency(boolean); - method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergencyRegResult(@NonNull android.telephony.EmergencyRegResult); + method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergencyRegistrationResult(@Nullable android.telephony.EmergencyRegistrationResult); method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setExitedFromAirplaneMode(boolean); - method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setPsDisconnectCause(@NonNull android.telephony.ims.ImsReasonInfo); + method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setPsDisconnectCause(@Nullable android.telephony.ims.ImsReasonInfo); method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setTestEmergencyNumber(boolean); method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setVideoCall(boolean); } @@ -14227,7 +14277,7 @@ package android.telephony { method public void reselectDomain(@NonNull android.telephony.DomainSelectionService.SelectionAttributes); } - @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public final class EmergencyRegResult implements android.os.Parcelable { + @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public final class EmergencyRegistrationResult implements android.os.Parcelable { method public int describeContents(); method public int getAccessNetwork(); method @NonNull public String getCountryIso(); @@ -14240,7 +14290,7 @@ package android.telephony { method public boolean isEmcBearerSupported(); method public boolean isVopsSupported(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.EmergencyRegResult> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.EmergencyRegistrationResult> CREATOR; } public final class ImsiEncryptionInfo implements android.os.Parcelable { @@ -15312,7 +15362,7 @@ package android.telephony { @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface WwanSelectorCallback { method public void onDomainSelected(int, boolean); - method public void onRequestEmergencyNetworkScan(@NonNull java.util.List<java.lang.Integer>, int, boolean, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.telephony.EmergencyRegResult>); + method public void onRequestEmergencyNetworkScan(@NonNull java.util.List<java.lang.Integer>, int, boolean, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.telephony.EmergencyRegistrationResult>); } } @@ -17380,6 +17430,19 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR; } + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public class EnableRequestAttributes { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isDemoMode(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isEmergencyMode(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isEnabled(); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final class EnableRequestAttributes.Builder { + ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public EnableRequestAttributes.Builder(boolean); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes build(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes.Builder setDemoMode(boolean); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes.Builder setEmergencyMode(boolean); + } + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable { ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); @@ -17445,10 +17508,11 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestEnabled(@NonNull android.telephony.satellite.EnableRequestAttributes, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsEmergencyModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); @@ -17510,6 +17574,7 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; // 0x18 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13 diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 6c83fd04b76b..8a485d2a8449 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -525,6 +525,10 @@ KotlinKeyword: android.app.Notification#when: Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords +KotlinOperator: android.hardware.camera2.extension.CharacteristicsMap#get(String): + Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object) + + ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>) parameter #1: Listeners should always be at end of argument list (method `stopSatelliteTransmissionUpdates`) ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>) parameter #2: @@ -685,6 +689,8 @@ RequiresPermission: android.app.admin.DevicePolicyManager#generateKeyPair(androi Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String): Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName): + Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName): Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName): @@ -731,6 +737,8 @@ RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage( Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>): Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int): + Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied(): Method 'setDeviceProvisioningConfigApplied' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int): @@ -2305,14 +2313,14 @@ UnflaggedApi: android.telephony.satellite.SatelliteManager.SatelliteException#Sa New API must be flagged with @FlaggedApi: constructor android.telephony.satellite.SatelliteManager.SatelliteException(int) UnflaggedApi: android.telephony.satellite.SatelliteManager.SatelliteException#getErrorCode(): New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.SatelliteException.getErrorCode() -UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback: - New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteProvisionStateCallback -UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean): - New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteProvisionStateCallback.onSatelliteProvisionStateChanged(boolean) UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback: New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteModemStateCallback UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback#onSatelliteModemStateChanged(int): New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteModemStateCallback.onSatelliteModemStateChanged(int) +UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback: + New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteProvisionStateCallback +UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean): + New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteProvisionStateCallback.onSatelliteProvisionStateChanged(boolean) UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback: New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteTransmissionUpdateCallback UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback#onReceiveDatagramStateChanged(int, int, int): diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 4a048bd315a0..fc095d482450 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -624,6 +624,7 @@ package android.app.admin { field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10 field public static final int OPERATION_SET_CAMERA_DISABLED = 31; // 0x1f + field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41; // 0x29 field public static final int OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY = 32; // 0x20 field public static final int OPERATION_SET_GLOBAL_PRIVATE_DNS = 33; // 0x21 field public static final int OPERATION_SET_KEEP_UNINSTALLED_PACKAGES = 17; // 0x11 @@ -1282,10 +1283,6 @@ package android.credentials.selection { field public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0; // 0x0 } - @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable { - ctor @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public CancelSelectionRequest(@NonNull android.os.IBinder, boolean, @NonNull String); - } - @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CreateCredentialProviderData extends android.credentials.selection.ProviderData implements android.os.Parcelable { ctor public CreateCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.selection.Entry>, @Nullable android.credentials.selection.Entry); method @Nullable public android.credentials.selection.Entry getRemoteEntry(); @@ -1882,6 +1879,7 @@ package android.media { method @FlaggedApi("android.media.audio.focus_freeze_test_api") @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusUnmuteDelayAfterFadeOutForTest(); method @Nullable public static android.media.AudioHalVersionInfo getHalVersion(); method public static final int[] getPublicStreamTypes(); + method @FlaggedApi("android.media.audiopolicy.audio_mix_test_api") @NonNull public java.util.List<android.media.audiopolicy.AudioMix> getRegisteredPolicyMixes(); method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public float getRs2Value(); method public int getStreamMinVolumeInt(int); @@ -2055,6 +2053,10 @@ package android.media.audiofx { package android.media.audiopolicy { + public class AudioPolicy { + method @FlaggedApi("android.media.audiopolicy.audio_mix_test_api") @NonNull public java.util.List<android.media.audiopolicy.AudioMix> getMixes(); + } + public static class AudioPolicy.Builder { method @NonNull public android.media.audiopolicy.AudioPolicy.Builder setIsTestFocusPolicy(boolean); } diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 5e904ef947c8..b938f0ffef76 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -673,6 +673,8 @@ RequiresPermission: android.app.admin.DevicePolicyManager#generateKeyPair(androi Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String): Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName): + Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName): Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName): @@ -721,6 +723,8 @@ RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage( Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>): Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int): + Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceOwner(android.content.ComponentName, int): Method 'setDeviceOwner' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied(): diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 084c71f47603..a8d183a1d6dd 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -5954,6 +5954,20 @@ public class ActivityManager { } /** + * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify of color + * palette readiness. + * @hide + */ + @RequiresPermission(Manifest.permission.SET_THEME_OVERLAY_CONTROLLER_READY) + public void setThemeOverlayReady(boolean readiness) { + try { + getService().setThemeOverlayReady(readiness); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Resets the state of the {@link com.android.server.am.AppErrors} instance. * This is intended for use with CTS only. * @hide diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 232fc926a802..0ae2e01a45fa 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -1258,4 +1258,11 @@ public abstract class ActivityManagerInternal { */ public abstract boolean clearApplicationUserData(String packageName, boolean keepState, boolean isRestore, IPackageDataObserver observer, int userId); + + /** + * Returns current state of {@link com.android.systemui.theme.ThemeOverlayController} color + * palette readiness. + * @hide + */ + public abstract boolean getThemeOverlayReadiness(); } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index b063d04655f5..ceeaf5dea7d3 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -17,7 +17,6 @@ package android.app; import android.app.ActivityManager; -import android.app.ActivityManager.PendingIntentInfo; import android.app.ActivityTaskManager; import android.app.ApplicationStartInfo; import android.app.ApplicationErrorReport; @@ -553,6 +552,14 @@ interface IActivityManager { @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) boolean isTopOfTask(in IBinder token); void bootAnimationComplete(); + + /** + * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify of color + * palette readiness. + * @throws RemoteException + */ + void setThemeOverlayReady(boolean readiness); + @UnsupportedAppUsage void registerTaskStackListener(in ITaskStackListener listener); void unregisterTaskStackListener(in ITaskStackListener listener); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b73431571760..aa9de814b4c5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICO import static android.app.admin.DevicePolicyResources.UNDEFINED; import static android.graphics.drawable.Icon.TYPE_URI; import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; +import static android.app.Flags.evenlyDividedCallStyleActionLayout; import static java.util.Objects.requireNonNull; @@ -5954,7 +5955,7 @@ public class Notification implements Parcelable // there is enough space to do so (and fall back to the left edge if not). big.setInt(R.id.actions, "setCollapsibleIndentDimen", R.dimen.call_notification_collapsible_indent); - if (CallStyle.USE_NEW_ACTION_LAYOUT) { + if (evenlyDividedCallStyleActionLayout()) { if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) { Log.d(TAG, "setting evenly divided mode on action list"); } @@ -6436,7 +6437,7 @@ public class Notification implements Parcelable title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor); } final CharSequence label = ensureColorSpanContrast(title, p); - if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) { + if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) { if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) { Log.d(TAG, "new action layout enabled, gluing instead of setting text"); } @@ -6460,7 +6461,7 @@ public class Notification implements Parcelable button.setColorStateList(R.id.action0, "setButtonBackground", ColorStateList.valueOf(buttonFillColor)); if (p.mCallStyleActions) { - if (CallStyle.USE_NEW_ACTION_LAYOUT) { + if (evenlyDividedCallStyleActionLayout()) { if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) { Log.d(TAG, "new action layout enabled, gluing instead of setting icon"); } @@ -9597,11 +9598,6 @@ public class Notification implements Parcelable /** * @hide */ - public static final boolean USE_NEW_ACTION_LAYOUT = false; - - /** - * @hide - */ public static final boolean DEBUG_NEW_ACTION_LAYOUT = true; /** diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index b0bec783555b..d7aafa010e1d 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -22,7 +22,6 @@ import android.annotation.TestApi; import android.app.admin.flags.Flags; import android.os.UserManager; - import java.util.Objects; /** @@ -163,6 +162,12 @@ public final class DevicePolicyIdentifiers { public static final String CROSS_PROFILE_WIDGET_PROVIDER_POLICY = "crossProfileWidgetProvider"; /** + * String identifier for {@link DevicePolicyManager#setContentProtectionPolicy}. + */ + @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) + public static final String CONTENT_PROTECTION_POLICY = "contentProtection"; + + /** * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}. */ @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED) diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 86d0125fd7a2..c8762c69eeca 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -18,12 +18,14 @@ package android.app.admin; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.LOCK_DEVICE; import static android.Manifest.permission.MANAGE_DEVICE_ADMINS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA; 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; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS; @@ -53,7 +55,6 @@ import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; 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; -import static android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -3825,6 +3826,10 @@ public class DevicePolicyManager { /** @hide */ @TestApi public static final int OPERATION_UNINSTALL_CA_CERT = 40; + /** @hide */ + @TestApi + @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) + public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41; private static final String PREFIX_OPERATION = "OPERATION_"; @@ -3869,7 +3874,8 @@ public class DevicePolicyManager { OPERATION_SET_PERMISSION_GRANT_STATE, OPERATION_SET_PERMISSION_POLICY, OPERATION_SET_RESTRICTIONS_PROVIDER, - OPERATION_UNINSTALL_CA_CERT + OPERATION_UNINSTALL_CA_CERT, + OPERATION_SET_CONTENT_PROTECTION_POLICY }) @Retention(RetentionPolicy.SOURCE) public static @interface DevicePolicyOperation { @@ -4095,15 +4101,15 @@ public class DevicePolicyManager { } /** Indicates that content protection is not controlled by policy, allowing user to choose. */ - @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED) + @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0; - /** Indicates that content protection is controlled and disabled by a policy. */ - @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED) + /** Indicates that content protection is controlled and disabled by a policy (default). */ + @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) public static final int CONTENT_PROTECTION_DISABLED = 1; /** Indicates that content protection is controlled and enabled by a policy. */ - @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED) + @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) public static final int CONTENT_PROTECTION_ENABLED = 2; /** @hide */ @@ -4118,6 +4124,86 @@ public class DevicePolicyManager { public @interface ContentProtectionPolicy {} /** + * Sets the content protection policy which controls scanning for deceptive apps. + * <p> + * This function can only be called by the device owner, a profile owner of an affiliated user + * or profile, or the profile owner when no device owner is set or holders of the permission + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}. See + * {@link #isAffiliatedUser}. + * Any policy set via this method will be cleared if the user becomes unaffiliated. + * <p> + * After the content protection policy has been set, + * {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, Bundle, TargetUser, + * PolicyUpdateResult)} will notify the admin on whether the policy was successfully set or not. + * This callback will contain: + * <ul> + * <li> The policy identifier {@link DevicePolicyIdentifiers#CONTENT_PROTECTION_POLICY} + * <li> The {@link TargetUser} that this policy relates to + * <li> The {@link PolicyUpdateResult}, which will be + * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the + * reason the policy failed to be set + * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY}) + * </ul> + * If there has been a change to the policy, + * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser, + * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the + * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult} + * will contain the reason why the policy changed. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the + * caller is not a device admin. + * @param policy The content protection policy to set. One of {@link + * #CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY}, + * {@link #CONTENT_PROTECTION_DISABLED} or {@link #CONTENT_PROTECTION_ENABLED}. + * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an + * affiliated user or profile, or the profile owner when no device owner is set or holder of the + * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}. + * @see #isAffiliatedUser + */ + @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true) + @SupportsCoexistence + @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) + public void setContentProtectionPolicy( + @Nullable ComponentName admin, @ContentProtectionPolicy int policy) { + throwIfParentInstance("setContentProtectionPolicy"); + if (mService != null) { + try { + mService.setContentProtectionPolicy(admin, mContext.getPackageName(), policy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the current content protection policy. + * <p> + * The returned policy will be the current resolved policy rather than the policy set by the + * calling admin. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the + * caller is not a device admin. + * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an + * affiliated user or profile, or the profile owner when no device owner is set or holder of the + * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}. + * @see #isAffiliatedUser + * @see #setContentProtectionPolicy + */ + @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true) + @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED) + public @ContentProtectionPolicy int getContentProtectionPolicy(@Nullable ComponentName admin) { + throwIfParentInstance("getContentProtectionPolicy"); + if (mService != null) { + try { + return mService.getContentProtectionPolicy(admin, mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return CONTENT_PROTECTION_DISABLED; + } + + /** * This object is a single place to tack on invalidation and disable calls. All * binder caches in this class derive from this Config, so all can be invalidated or * disabled through this Config. @@ -6330,10 +6416,10 @@ public class DevicePolicyManager { * (PIN, pattern, or password). This API is intended for use only by device admins. * <p> * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have - * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is - * true, then the method will return without completing any action. Before version - * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature, - * regardless of the caller's permissions. + * the LOCK_DEVICE permission or the device must have the + * device admin feature; if neither is true, then the method will return without completing + * any action. Before version {@link android.os.Build.VERSION_CODES#R}, + * the device needed the device admin feature, regardless of the caller's permissions. * <p> * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} * to be able to call this method; if it has not, a security exception will be thrown. @@ -6353,7 +6439,8 @@ public class DevicePolicyManager { * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} */ - @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true) + @SuppressLint("RequiresPermission") + @RequiresPermission(value = LOCK_DEVICE, conditional = true) public void lockNow() { lockNow(0); } @@ -6364,14 +6451,13 @@ public class DevicePolicyManager { * <p> * This method secures the device in response to an urgent situation, such as a lost or stolen * device. After this method is called, the device must be unlocked using strong authentication - * (PIN, pattern, or password). This API is for use only by device admins and holders of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission. + * (PIN, pattern, or password). This API is intended for use only by device admins. * <p> * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have - * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is - * true, then the method will return without completing any action. Before version - * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature, - * regardless of the caller's permissions. + * the LOCK_DEVICE permission or the device must have the + * device admin feature; if neither is true, then the method will return without completing any + * action. Before version {@link android.os.Build.VERSION_CODES#R}, the device needed the device + * admin feature, regardless of the caller's permissions. * <p> * A calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} * to be able to call this method; if it has not, a security exception will be thrown. @@ -6400,7 +6486,7 @@ public class DevicePolicyManager { * @param flags May be 0 or {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}. * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and the does not hold - * the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission, or + * the {@link android.Manifest.permission#LOCK_DEVICE} permission, or * the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is passed by an * application that is not a profile owner of a managed profile. * @throws IllegalArgumentException if the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is @@ -6409,7 +6495,7 @@ public class DevicePolicyManager { * flag is passed when {@link #getStorageEncryptionStatus} does not return * {@link #ENCRYPTION_STATUS_ACTIVE_PER_USER}. */ - @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true) + @RequiresPermission(value = LOCK_DEVICE, conditional = true) public void lockNow(@LockNowFlag int flags) { if (mService != null) { try { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 575fa4cac0b8..efcf5633515d 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -610,4 +610,7 @@ interface IDevicePolicyManager { String getFinancedDeviceKioskRoleHolder(String callerPackageName); void calculateHasIncompatibleAccounts(); + + void setContentProtectionPolicy(in ComponentName who, String callerPackageName, int policy); + int getContentProtectionPolicy(in ComponentName who, String callerPackageName); } diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index c40b23ed3cd0..274d02a79270 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -56,4 +56,14 @@ flag { namespace: "systemui" description: "This flag enables the API to allow setting VibrationEffect for NotificationChannels" bug: "241732519" +} + +flag { + name: "evenly_divided_call_style_action_layout" + namespace: "systemui" + description: "Evenly divides horizontal space for action buttons in CallStyle notifications." + bug: "268733030" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl index ff37bd848d61..3cbc8a21d2d8 100644 --- a/core/java/android/app/wearable/IWearableSensingManager.aidl +++ b/core/java/android/app/wearable/IWearableSensingManager.aidl @@ -16,6 +16,7 @@ package android.app.wearable; +import android.app.PendingIntent; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; @@ -28,7 +29,13 @@ import android.os.SharedMemory; */ interface IWearableSensingManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + void provideWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + void registerDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + void unregisterDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback); }
\ No newline at end of file diff --git a/core/java/android/app/wearable/WearableSensingDataRequest.java b/core/java/android/app/wearable/WearableSensingDataRequest.java new file mode 100644 index 000000000000..9329b37be078 --- /dev/null +++ b/core/java/android/app/wearable/WearableSensingDataRequest.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.wearable; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; + +import java.time.Duration; + +/** + * Data class for a data request for wearable sensing. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) +@SystemApi +public final class WearableSensingDataRequest implements Parcelable { + private static final int MAX_REQUEST_SIZE = 200; + private static final Duration RATE_LIMIT_WINDOW_SIZE = Duration.ofMinutes(1); + private static final int RATE_LIMIT = 30; + + private final int mDataType; + @NonNull private final PersistableBundle mRequestDetails; + + private WearableSensingDataRequest(int dataType, @NonNull PersistableBundle requestDetails) { + mDataType = dataType; + mRequestDetails = requestDetails; + } + + /** Returns the data type this request is for. */ + public int getDataType() { + return mDataType; + } + + /** Returns the details for this request. */ + @NonNull + public PersistableBundle getRequestDetails() { + return mRequestDetails; + } + + /** Returns the data size of this object when it is parcelled. */ + public int getDataSize() { + Parcel parcel = Parcel.obtain(); + try { + writeToParcel(parcel, describeContents()); + return parcel.dataSize(); + } finally { + parcel.recycle(); + } + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDataType); + dest.writeTypedObject(mRequestDetails, flags); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "WearableSensingDataRequest { " + + "dataType = " + + mDataType + + ", " + + "requestDetails = " + + mRequestDetails + + " }"; + } + + /** + * Returns a String representation of this data request that shows its contents. + * + * @hide + */ + public String toExpandedString() { + if (mRequestDetails != null) { + // Trigger unparcelling so that its individual fields will be listed in toString + boolean unused = + mRequestDetails.getBoolean( + "PlaceholderForWearableSensingDataRequest#toExpandedString()"); + } + return toString(); + } + + /** + * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}. + * + * @hide + */ + public static final String REQUEST_BUNDLE_KEY = + "android.app.wearable.WearableSensingDataRequestBundleKey"; + + /** + * The bundle key for the status callback for a data request, used in {@code + * RemoteCallback#sendResult}. + * + * @hide + */ + public static final String REQUEST_STATUS_CALLBACK_BUNDLE_KEY = + "android.app.wearable.WearableSensingDataRequestStatusCallbackBundleKey"; + + public static final @NonNull Parcelable.Creator<WearableSensingDataRequest> CREATOR = + new Parcelable.Creator<WearableSensingDataRequest>() { + @Override + public WearableSensingDataRequest[] newArray(int size) { + return new WearableSensingDataRequest[size]; + } + + @Override + public WearableSensingDataRequest createFromParcel(@NonNull Parcel in) { + int dataType = in.readInt(); + PersistableBundle requestDetails = + in.readTypedObject(PersistableBundle.CREATOR); + return new WearableSensingDataRequest(dataType, requestDetails); + } + }; + + /** + * Returns the maximum allowed size of a WearableSensingDataRequest when it is parcelled. + * Instances that exceed this size can be constructed, but will be rejected by the system when + * they leave the isolated WearableSensingService. + */ + public static int getMaxRequestSize() { + return MAX_REQUEST_SIZE; + } + + /** + * Returns the rolling time window used to perform rate limiting on data requests leaving the + * WearableSensingService. + */ + @NonNull + public static Duration getRateLimitWindowSize() { + return RATE_LIMIT_WINDOW_SIZE; + } + + /** + * Returns the number of data requests allowed to leave the WearableSensingService in each + * {@link #getRateLimitWindowSize()}. + */ + public static int getRateLimit() { + return RATE_LIMIT; + } + + /** A builder for WearableSensingDataRequest. */ + @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) + public static final class Builder { + private int mDataType; + private PersistableBundle mRequestDetails; + + public Builder(int dataType) { + mDataType = dataType; + } + + /** Sets the request details. */ + public @NonNull Builder setRequestDetails(@NonNull PersistableBundle requestDetails) { + mRequestDetails = requestDetails; + return this; + } + + /** Builds the WearableSensingDataRequest. */ + public @NonNull WearableSensingDataRequest build() { + if (mRequestDetails == null) { + mRequestDetails = PersistableBundle.EMPTY; + } + return new WearableSensingDataRequest(mDataType, mRequestDetails); + } + } +} diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java index eca0039c20f4..077f7b5c2f3e 100644 --- a/core/java/android/app/wearable/WearableSensingManager.java +++ b/core/java/android/app/wearable/WearableSensingManager.java @@ -25,8 +25,11 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; +import android.companion.CompanionDeviceManager; import android.content.Context; +import android.content.Intent; import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; @@ -36,6 +39,8 @@ import android.os.SharedMemory; import android.service.wearable.WearableSensingService; import android.system.OsConstants; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; @@ -55,7 +60,6 @@ import java.util.function.Consumer; * * @hide */ - @SystemApi @SystemService(Context.WEARABLE_SENSING_SERVICE) public class WearableSensingManager { @@ -68,6 +72,14 @@ public class WearableSensingManager { public static final String STATUS_RESPONSE_BUNDLE_KEY = "android.app.wearable.WearableSensingStatusBundleKey"; + /** + * The Intent extra key for the data request in the Intent sent to the PendingIntent registered + * with {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}. + * + * @hide + */ + public static final String EXTRA_WEARABLE_SENSING_DATA_REQUEST = + "android.app.wearable.extra.WEARABLE_SENSING_DATA_REQUEST"; /** * An unknown status. @@ -104,22 +116,54 @@ public class WearableSensingManager { * The value of the status code that indicates the method called is not supported by the * implementation of {@link WearableSensingService}. */ + @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE) public static final int STATUS_UNSUPPORTED_OPERATION = 6; + /** + * The value of the status code that indicates an error occurred in the encrypted channel backed + * by the provided connection. See {@link #provideWearableConnection(ParcelFileDescriptor, + * Executor, Consumer)}. + */ + @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API) + public static final int STATUS_CHANNEL_ERROR = 7; + + /** The value of the status code that indicates the provided data type is not supported. */ + @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) + public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; + /** @hide */ - @IntDef(prefix = { "STATUS_" }, value = { - STATUS_UNKNOWN, - STATUS_SUCCESS, - STATUS_UNSUPPORTED, - STATUS_SERVICE_UNAVAILABLE, - STATUS_WEARABLE_UNAVAILABLE, - STATUS_ACCESS_DENIED, - STATUS_UNSUPPORTED_OPERATION - }) + @IntDef( + prefix = {"STATUS_"}, + value = { + STATUS_UNKNOWN, + STATUS_SUCCESS, + STATUS_UNSUPPORTED, + STATUS_SERVICE_UNAVAILABLE, + STATUS_WEARABLE_UNAVAILABLE, + STATUS_ACCESS_DENIED, + STATUS_UNSUPPORTED_OPERATION, + STATUS_CHANNEL_ERROR, + STATUS_UNSUPPORTED_DATA_TYPE + }) @Retention(RetentionPolicy.SOURCE) public @interface StatusCode {} + /** + * Retrieves a {@link WearableSensingDataRequest} from the Intent sent to the PendingIntent + * provided to {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}. + * + * @param intent The Intent received from the PendingIntent. + * @return The WearableSensingDataRequest in the provided Intent, or null if the Intent does not + * contain a WearableSensingDataRequest. + */ + @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) + @Nullable + public static WearableSensingDataRequest getDataRequestFromIntent(@NonNull Intent intent) { + return intent.getParcelableExtra( + EXTRA_WEARABLE_SENSING_DATA_REQUEST, WearableSensingDataRequest.class); + } + private final Context mContext; private final IWearableSensingManager mService; @@ -132,6 +176,60 @@ public class WearableSensingManager { } /** + * Provides a remote wearable device connection to the WearableSensingService and sends the + * resulting status to the {@code statusConsumer} after the call. + * + * <p>This is used by applications that will also provide an implementation of the isolated + * WearableSensingService. + * + * <p>The provided {@code wearableConnection} is expected to be a connection to a remotely + * connected wearable device. This {@code wearableConnection} will be attached to + * CompanionDeviceManager via {@link CompanionDeviceManager#attachSystemDataTransport(int, + * InputStream, OutputStream)}, which will create an encrypted channel using {@code + * wearableConnection} as the raw underlying connection. The wearable device is expected to + * attach its side of the raw connection to its CompanionDeviceManager via the same method so + * that the two CompanionDeviceManagers on the two devices can perform attestation and set up + * the encrypted channel. Attestation requirements are listed in + * com.android.server.security.AttestationVerificationPeerDeviceVerifier + * + * <p>A proxy to the encrypted channel will be provided to the WearableSensingService, which is + * referred to as the secureWearableConnection in WearableSensingService. Any data written to + * secureWearableConnection will be encrypted by CompanionDeviceManager and sent over the raw + * {@code wearableConnection} to the remote wearable device, which is expected to use its + * CompanionDeviceManager to decrypt the data. Encrypted data arriving at the raw {@code + * wearableConnection} will be decrypted by CompanionDeviceManager and be readable as plain text + * from secureWearableConnection. The raw {@code wearableConnection} provided to this method + * will not be directly available to the WearableSensingService. + * + * <p>If an error occurred in the encrypted channel (such as the underlying stream closed), the + * system will send a status code of {@link STATUS_CHANNEL_ERROR} to the {@code statusConsumer} + * and kill the WearableSensingService process. + * + * <p>Before providing the secureWearableConnection, the system will restart the + * WearableSensingService process. Other method calls into WearableSensingService may be dropped + * during the restart. The caller is responsible for ensuring other method calls are queued + * until a success status is returned from the {@code statusConsumer}. + * + * @param wearableConnection The connection to provide + * @param executor Executor on which to run the consumer callback + * @param statusConsumer A consumer that handles the status codes for providing the connection + * and errors in the encrypted channel. + */ + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API) + public void provideWearableConnection( + @NonNull ParcelFileDescriptor wearableConnection, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> statusConsumer) { + try { + RemoteCallback callback = createStatusCallback(executor, statusConsumer); + mService.provideWearableConnection(wearableConnection, callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Provides a data stream to the WearableSensingService that's backed by the * parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call. * This is used by applications that will also provide an implementation of @@ -149,15 +247,7 @@ public class WearableSensingManager { @NonNull @CallbackExecutor Executor executor, @NonNull @StatusCode Consumer<Integer> statusConsumer) { try { - RemoteCallback callback = new RemoteCallback(result -> { - int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); - final long identity = Binder.clearCallingIdentity(); - try { - executor.execute(() -> statusConsumer.accept(status)); - } finally { - Binder.restoreCallingIdentity(identity); - } - }); + RemoteCallback callback = createStatusCallback(executor, statusConsumer); mService.provideDataStream(parcelFileDescriptor, callback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -191,19 +281,117 @@ public class WearableSensingManager { @NonNull @CallbackExecutor Executor executor, @NonNull @StatusCode Consumer<Integer> statusConsumer) { try { - RemoteCallback callback = new RemoteCallback(result -> { - int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); - final long identity = Binder.clearCallingIdentity(); - try { - executor.execute(() -> statusConsumer.accept(status)); - } finally { - Binder.restoreCallingIdentity(identity); - } - }); + RemoteCallback callback = createStatusCallback(executor, statusConsumer); mService.provideData(data, sharedMemory, callback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /** + * Registers a data request observer for the provided data type. + * + * <p>When data is requested, the provided {@code dataRequestPendingIntent} will be invoked. A + * {@link WearableSensingDataRequest} can be extracted from the Intent sent to {@code + * dataRequestPendingIntent} by calling {@link #getDataRequestFromIntent(Intent)}. The observer + * can then provide the requested data via {@link #provideData(PersistableBundle, SharedMemory, + * Executor, Consumer)}. + * + * <p>There is no limit to the number of observers registered for a data type. How they are + * handled depends on the implementation of WearableSensingService. + * + * <p>When the observer is no longer needed, {@link #unregisterDataRequestObserver(int, + * PendingIntent, Executor, Consumer)} should be called with the same {@code + * dataRequestPendingIntent}. It should be done regardless of the status code returned from + * {@code statusConsumer} in order to clean up housekeeping data for the {@code + * dataRequestPendingIntent} maintained by the system. + * + * <p>Example: + * + * <pre>{@code + * // Create a PendingIntent for MyDataRequestBroadcastReceiver + * Intent intent = + * new Intent(actionString).setClass(context, MyDataRequestBroadcastReceiver.class); + * PendingIntent pendingIntent = PendingIntent.getBroadcast( + * context, 0, intent, PendingIntent.FLAG_MUTABLE); + * + * // Register the PendingIntent as a data request observer + * wearableSensingManager.registerDataRequestObserver( + * dataType, pendingIntent, executor, statusConsumer); + * + * // Within MyDataRequestBroadcastReceiver, receive the broadcast Intent and extract the + * // WearableSensingDataRequest + * {@literal @}Override + * public void onReceive(Context context, Intent intent) { + * WearableSensingDataRequest dataRequest = + * WearableSensingManager.getDataRequestFromIntent(intent); + * // After parsing the dataRequest, provide the data + * wearableSensingManager.provideData(data, sharedMemory, executor, statusConsumer); + * } + * }</pre> + * + * @param dataType The data type to listen to. Values are defined by the application that + * implements {@link WearableSensingService}. + * @param dataRequestPendingIntent A mutable {@link PendingIntent} that will be invoked when + * data is requested. See {@link #getDataRequestFromIntent(Intent)}. Activities are not + * allowed to be launched using this PendingIntent. + * @param executor Executor on which to run the consumer callback. + * @param statusConsumer A consumer that handles the status code for the observer registration. + */ + @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + public void registerDataRequestObserver( + int dataType, + @NonNull PendingIntent dataRequestPendingIntent, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> statusConsumer) { + try { + RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer); + mService.registerDataRequestObserver( + dataType, dataRequestPendingIntent, statusCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters a previously registered data request observer. If the provided {@link + * PendingIntent} was not registered, or is already unregistered, the {@link + * WearableSensingService} will not be notified. + * + * @param dataType The data type the observer is for. + * @param dataRequestPendingIntent The observer to unregister. + * @param executor Executor on which to run the consumer callback. + * @param statusConsumer A consumer that handles the status code for the observer + * unregistration. + */ + @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + public void unregisterDataRequestObserver( + int dataType, + @NonNull PendingIntent dataRequestPendingIntent, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> statusConsumer) { + try { + RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer); + mService.unregisterDataRequestObserver( + dataType, dataRequestPendingIntent, statusCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static RemoteCallback createStatusCallback( + Executor executor, Consumer<Integer> statusConsumer) { + return new RemoteCallback( + result -> { + int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY); + final long identity = Binder.clearCallingIdentity(); + try { + executor.execute(() -> statusConsumer.accept(status)); + } finally { + Binder.restoreCallingIdentity(identity); + } + }); + } } diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig index 5e8bdb5a2997..b4f628ffc9b3 100644 --- a/core/java/android/app/wearable/flags.aconfig +++ b/core/java/android/app/wearable/flags.aconfig @@ -22,6 +22,13 @@ flag { } flag { + name: "enable_restart_wss_process" + namespace: "machine_learning" + description: "When this flag is on, the system will restart the WearableSensingService process before providing it with a new secure wearable connection." + bug: "301427767" +} + +flag { name: "enable_hotword_wearable_sensing_api" namespace: "machine_learning" description: "This flag enables the APIs related to hotword in WearableSensingManager and WearableSensingService." diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 9fe8af516694..a64ee5b38cc1 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -239,6 +239,110 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public String requiredDisplayCategory; /** + * Constant corresponding to {@code none} in the + * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute. + * @hide + */ + public static final int CONTENT_URI_PERMISSION_NONE = 0; + + /** + * Constant corresponding to {@code read} in the + * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute. + * @hide + */ + public static final int CONTENT_URI_PERMISSION_READ = 1; + + /** + * Constant corresponding to {@code write} in the + * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute. + * @hide + */ + public static final int CONTENT_URI_PERMISSION_WRITE = 2; + + /** + * Constant corresponding to {@code readOrWrite} in the + * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute. + * @hide + */ + public static final int CONTENT_URI_PERMISSION_READ_OR_WRITE = 3; + + /** + * Constant corresponding to {@code readAndWrite} in the + * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute. + * @hide + */ + public static final int CONTENT_URI_PERMISSION_READ_AND_WRITE = 4; + + /** @hide */ + @SuppressWarnings("SwitchIntDef") + public static boolean isRequiredContentUriPermissionRead( + @RequiredContentUriPermission int permission) { + return switch (permission) { + case CONTENT_URI_PERMISSION_READ_AND_WRITE, CONTENT_URI_PERMISSION_READ_OR_WRITE, + CONTENT_URI_PERMISSION_READ -> true; + default -> false; + }; + } + + /** @hide */ + @SuppressWarnings("SwitchIntDef") + public static boolean isRequiredContentUriPermissionWrite( + @RequiredContentUriPermission int permission) { + return switch (permission) { + case CONTENT_URI_PERMISSION_READ_AND_WRITE, CONTENT_URI_PERMISSION_READ_OR_WRITE, + CONTENT_URI_PERMISSION_WRITE -> true; + default -> false; + }; + } + + /** @hide */ + @IntDef(prefix = "CONTENT_URI_PERMISSION_", value = { + CONTENT_URI_PERMISSION_NONE, + CONTENT_URI_PERMISSION_READ, + CONTENT_URI_PERMISSION_WRITE, + CONTENT_URI_PERMISSION_READ_OR_WRITE, + CONTENT_URI_PERMISSION_READ_AND_WRITE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RequiredContentUriPermission { + } + + private String requiredContentUriPermissionToFullString( + @RequiredContentUriPermission int permission) { + return switch (permission) { + case CONTENT_URI_PERMISSION_NONE -> "CONTENT_URI_PERMISSION_NONE"; + case CONTENT_URI_PERMISSION_READ -> "CONTENT_URI_PERMISSION_READ"; + case CONTENT_URI_PERMISSION_WRITE -> "CONTENT_URI_PERMISSION_WRITE"; + case CONTENT_URI_PERMISSION_READ_OR_WRITE -> "CONTENT_URI_PERMISSION_READ_OR_WRITE"; + case CONTENT_URI_PERMISSION_READ_AND_WRITE -> "CONTENT_URI_PERMISSION_READ_AND_WRITE"; + default -> "unknown=" + permission; + }; + } + + /** @hide */ + public static String requiredContentUriPermissionToShortString( + @RequiredContentUriPermission int permission) { + return switch (permission) { + case CONTENT_URI_PERMISSION_NONE -> "none"; + case CONTENT_URI_PERMISSION_READ -> "read"; + case CONTENT_URI_PERMISSION_WRITE -> "write"; + case CONTENT_URI_PERMISSION_READ_OR_WRITE -> "read or write"; + case CONTENT_URI_PERMISSION_READ_AND_WRITE -> "read and write"; + default -> "unknown=" + permission; + }; + } + + /** + * Specifies permissions necessary to launch this activity via + * {@link android.content.Context#startActivity} when passing content URIs. The default value is + * {@code none}, meaning no specific permissions are required. Setting this attribute restricts + * activity invocation based on the invoker's permissions. + * @hide + */ + @RequiredContentUriPermission + public int requireContentUriPermissionFromCaller; + + /** * Activity can not be resized and always occupies the fullscreen area with all windows fully * visible. * @hide @@ -1590,6 +1694,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { mMinAspectRatio = orig.mMinAspectRatio; supportsSizeChanges = orig.supportsSizeChanges; requiredDisplayCategory = orig.requiredDisplayCategory; + requireContentUriPermissionFromCaller = orig.requireContentUriPermissionFromCaller; } /** @@ -1946,6 +2051,11 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { if (requiredDisplayCategory != null) { pw.println(prefix + "requiredDisplayCategory=" + requiredDisplayCategory); } + if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) { + pw.println(prefix + "requireContentUriPermissionFromCaller=" + + requiredContentUriPermissionToFullString( + requireContentUriPermissionFromCaller)); + } super.dumpBack(pw, prefix, dumpFlags); } @@ -1993,6 +2103,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeBoolean(supportsSizeChanges); sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags); dest.writeString8(requiredDisplayCategory); + dest.writeInt(requireContentUriPermissionFromCaller); } /** @@ -2119,6 +2230,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { mKnownActivityEmbeddingCerts = null; } requiredDisplayCategory = source.readString8(); + requireContentUriPermissionFromCaller = source.readInt(); } /** diff --git a/core/java/android/content/pm/IBackgroundInstallControlService.aidl b/core/java/android/content/pm/IBackgroundInstallControlService.aidl index c8e7caebc821..2e7f19ed2e95 100644 --- a/core/java/android/content/pm/IBackgroundInstallControlService.aidl +++ b/core/java/android/content/pm/IBackgroundInstallControlService.aidl @@ -17,10 +17,15 @@ package android.content.pm; import android.content.pm.ParceledListSlice; +import android.os.IRemoteCallback; /** * {@hide} */ interface IBackgroundInstallControlService { ParceledListSlice getBackgroundInstalledPackages(long flags, int userId); + + void registerBackgroundInstallCallback(IRemoteCallback callback); + + void unregisterBackgroundInstallCallback(IRemoteCallback callback); } diff --git a/core/java/android/credentials/selection/CancelSelectionRequest.java b/core/java/android/credentials/selection/CancelSelectionRequest.java index 2662d761c1c0..55acfdbddf94 100644 --- a/core/java/android/credentials/selection/CancelSelectionRequest.java +++ b/core/java/android/credentials/selection/CancelSelectionRequest.java @@ -21,7 +21,6 @@ import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENAB import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; -import android.annotation.TestApi; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -59,7 +58,7 @@ public final class CancelSelectionRequest implements Parcelable { private final boolean mShouldShowCancellationExplanation; @NonNull - private final String mAppPackageName; + private final String mPackageName; /** * Returns the request token matching the user request that should be cancelled. @@ -85,8 +84,8 @@ public final class CancelSelectionRequest implements Parcelable { * metadata (e.g. "Cancelled by `App Name`"). */ @NonNull - public String getAppPackageName() { - return mAppPackageName; + public String getPackageName() { + return mPackageName; } /** @@ -98,33 +97,36 @@ public final class CancelSelectionRequest implements Parcelable { return mShouldShowCancellationExplanation; } + /** * Constructs a {@link CancelSelectionRequest}. * - * @hide + * @param requestToken request token matching the app request that should be cancelled + * @param shouldShowCancellationExplanation whether the UI should display some informational + * cancellation message before closing + * @param packageName package that is invoking this request + * */ - @TestApi - @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED) - public CancelSelectionRequest(@NonNull IBinder token, boolean shouldShowCancellationExplanation, - @NonNull String appPackageName) { - mToken = token; + public CancelSelectionRequest(@NonNull RequestToken requestToken, + boolean shouldShowCancellationExplanation, @NonNull String packageName) { + mToken = requestToken.getToken(); mShouldShowCancellationExplanation = shouldShowCancellationExplanation; - mAppPackageName = appPackageName; + mPackageName = packageName; } private CancelSelectionRequest(@NonNull Parcel in) { mToken = in.readStrongBinder(); AnnotationValidations.validate(NonNull.class, null, mToken); mShouldShowCancellationExplanation = in.readBoolean(); - mAppPackageName = in.readString8(); - AnnotationValidations.validate(NonNull.class, null, mAppPackageName); + mPackageName = in.readString8(); + AnnotationValidations.validate(NonNull.class, null, mPackageName); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeStrongBinder(mToken); dest.writeBoolean(mShouldShowCancellationExplanation); - dest.writeString8(mAppPackageName); + dest.writeString8(mPackageName); } @Override diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java index 1837976f5d1f..ac2bae495219 100644 --- a/core/java/android/credentials/selection/IntentFactory.java +++ b/core/java/android/credentials/selection/IntentFactory.java @@ -210,7 +210,8 @@ public class IntentFactory { .config_credentialManagerDialogComponent)); intent.setComponent(componentName); intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST, - new CancelSelectionRequest(requestToken, shouldShowCancellationUi, appPackageName)); + new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi, + appPackageName)); return intent; } diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java index 60bbae683680..16d0802709f3 100644 --- a/core/java/android/credentials/selection/RequestInfo.java +++ b/core/java/android/credentials/selection/RequestInfo.java @@ -106,7 +106,7 @@ public final class RequestInfo implements Parcelable { private final String mType; @NonNull - private final String mAppPackageName; + private final String mPackageName; private final boolean mHasPermissionToOverrideDefault; @@ -172,8 +172,8 @@ public final class RequestInfo implements Parcelable { /** Returns the display name of the app that made this request. */ @NonNull - public String getAppPackageName() { - return mAppPackageName; + public String getPackageName() { + return mPackageName; } /** @@ -248,7 +248,7 @@ public final class RequestInfo implements Parcelable { boolean isShowAllOptionsRequested) { mToken = token; mType = type; - mAppPackageName = appPackageName; + mPackageName = appPackageName; mCreateCredentialRequest = createCredentialRequest; mGetCredentialRequest = getCredentialRequest; mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault; @@ -270,8 +270,8 @@ public final class RequestInfo implements Parcelable { AnnotationValidations.validate(NonNull.class, null, mToken); mType = type; AnnotationValidations.validate(NonNull.class, null, mType); - mAppPackageName = appPackageName; - AnnotationValidations.validate(NonNull.class, null, mAppPackageName); + mPackageName = appPackageName; + AnnotationValidations.validate(NonNull.class, null, mPackageName); mCreateCredentialRequest = createCredentialRequest; mGetCredentialRequest = getCredentialRequest; mHasPermissionToOverrideDefault = in.readBoolean(); @@ -284,7 +284,7 @@ public final class RequestInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeStrongBinder(mToken); dest.writeString8(mType); - dest.writeString8(mAppPackageName); + dest.writeString8(mPackageName); dest.writeTypedObject(mCreateCredentialRequest, flags); dest.writeTypedObject(mGetCredentialRequest, flags); dest.writeBoolean(mHasPermissionToOverrideDefault); diff --git a/core/java/android/credentials/selection/RequestToken.java b/core/java/android/credentials/selection/RequestToken.java index 27b83f873732..f1953ced6202 100644 --- a/core/java/android/credentials/selection/RequestToken.java +++ b/core/java/android/credentials/selection/RequestToken.java @@ -30,6 +30,11 @@ import android.os.IBinder; * To compare if two requests pertain to the same session, compare their RequestTokens using * the {@link RequestToken#equals(Object)} method. * + * For example, when receiving a {@link android.credentials.selection.CancelSelectionRequest}, + * the developer should use {@link RequestToken#getToken()} to retrieve the token from request and + * compare whether it is equal with the cached token using {@link RequestToken#equals(Object)}. Only + * cancel the request when two tokens are the same. + * * @hide */ @SystemApi @@ -39,6 +44,12 @@ public final class RequestToken { @NonNull private final IBinder mToken; + /** @hide **/ + @NonNull + public IBinder getToken() { + return mToken; + } + /** @hide */ @TestApi @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED) diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 54e34ecf7006..62473c5c58ce 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -76,6 +76,12 @@ public class InputSettings { */ public static final int MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS = 5000; + /** + * Default value for {@link Settings.Secure#STYLUS_POINTER_ICON_ENABLED}. + * @hide + */ + public static final int DEFAULT_STYLUS_POINTER_ICON_ENABLED = 1; + private InputSettings() { } @@ -383,14 +389,19 @@ public class InputSettings { } /** - * Whether a pointer icon will be shown over the location of a - * stylus pointer. + * Whether a pointer icon will be shown over the location of a stylus pointer. + * * @hide */ public static boolean isStylusPointerIconEnabled(@NonNull Context context) { + if (InputProperties.force_enable_stylus_pointer_icon().orElse(false)) { + return true; + } return context.getResources() - .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon) - || InputProperties.force_enable_stylus_pointer_icon().orElse(false); + .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon) + && Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.STYLUS_POINTER_ICON_ENABLED, + DEFAULT_STYLUS_POINTER_ICON_ENABLED, UserHandle.USER_CURRENT_OR_SELF) != 0; } /** diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 6c728a4a7288..abfa4e3dd8dc 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -122,3 +122,11 @@ flag { is_fixed_read_only: true bug: "309792384" } + +flag { + namespace: "system_performance" + name: "telemetry_apis_framework_initialization" + description: "Control framework initialization APIs of telemetry APIs feature." + is_fixed_read_only: true + bug: "324241334" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ef2d5ebd33fd..ce7a026c368b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12312,6 +12312,16 @@ public final class Settings { public static final String PRIVATE_SPACE_AUTO_LOCK = "private_space_auto_lock"; /** + * Toggle for enabling stylus pointer icon. Pointer icons for styluses will only be be shown + * when this is enabled. Enabling this alone won't enable the stylus pointer; + * config_enableStylusPointerIcon needs to be true as well. + * + * @hide + */ + @Readable + public static final String STYLUS_POINTER_ICON_ENABLED = "stylus_pointer_icon_enabled"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. */ diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl index 11e5ad8789f2..21801c00b701 100644 --- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl +++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl @@ -38,5 +38,33 @@ interface IPersistentDataBlockService { int getFlashLockState(); boolean hasFrpCredentialHandle(); String getPersistentDataPackageName(); -} + /** + * Returns true if Factory Reset Protection (FRP) is active, meaning the device rebooted and has + * not been able to transition to the FRP inactive state. + */ + boolean isFactoryResetProtectionActive(); + + /** + * Attempts to deactivate Factory Reset Protection (FRP) with the provided secret. If the + * provided secret matches the stored FRP secret, FRP is deactivated and the method returns + * true. Otherwise, FRP state remains unchanged and the method returns false. + */ + boolean deactivateFactoryResetProtection(in byte[] secret); + + /** + * Stores the provided Factory Reset Protection (FRP) secret as the secret to be used for future + * FRP deactivation. The secret must be 32 bytes in length. Setting the all-zeros "default" + * value disables the FRP feature entirely. + * + * It's the responsibility of the caller to ensure that copies of the FRP secret are stored + * securely where they can be recovered and used to deactivate FRP after an untrusted reset. + * This method will store a copy in /data/system and use that to automatically deactivate FRP + * until /data is wiped. + * + * Note that this method does nothing if FRP is currently active. + * + * Returns true if the secret was successfully changed, false otherwise. + */ + boolean setFactoryResetProtectionSecret(in byte[] secret); +} diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java index 6da3206708a7..9b9cc1933b93 100644 --- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java +++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java @@ -16,6 +16,7 @@ package android.service.persistentdata; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -24,30 +25,17 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; +import android.security.Flags; import android.service.oemlock.OemLockManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Interface for reading and writing data blocks to a persistent partition. - * - * Allows writing one block at a time. Namely, each time - * {@link PersistentDataBlockManager#write(byte[])} - * is called, it will overwite the data that was previously written on the block. - * - * Clients can query the size of the currently written block via - * {@link PersistentDataBlockManager#getDataBlockSize()}. - * - * Clients can query the maximum size for a block via - * {@link PersistentDataBlockManager#getMaximumDataBlockSize()} - * - * Clients can read the currently written block by invoking - * {@link PersistentDataBlockManager#read()}. - * - * @hide + * Interface to the persistent data partition. Provides access to information about the state + * of factory reset protection. */ -@SystemApi +@FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) @SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE) public class PersistentDataBlockManager { private static final String TAG = PersistentDataBlockManager.class.getSimpleName(); @@ -55,18 +43,32 @@ public class PersistentDataBlockManager { /** * Indicates that the device's bootloader lock state is UNKNOWN. + * + * @hide */ + @SystemApi public static final int FLASH_LOCK_UNKNOWN = -1; /** * Indicates that the device's bootloader is UNLOCKED. + * + * @hide */ + @SystemApi public static final int FLASH_LOCK_UNLOCKED = 0; /** * Indicates that the device's bootloader is LOCKED. + * + * @hide */ + @SystemApi public static final int FLASH_LOCK_LOCKED = 1; - /** @removed mistakenly exposed previously */ + /** + * @removed mistakenly exposed previously + * + * @hide + */ + @SystemApi @IntDef(prefix = { "FLASH_LOCK_" }, value = { FLASH_LOCK_UNKNOWN, FLASH_LOCK_LOCKED, @@ -75,7 +77,9 @@ public class PersistentDataBlockManager { @Retention(RetentionPolicy.SOURCE) public @interface FlashLockState {} - /** @hide */ + /** + * @hide + */ public PersistentDataBlockManager(IPersistentDataBlockService service) { sService = service; } @@ -91,7 +95,10 @@ public class PersistentDataBlockManager { * in which case -1 will be returned. * * @param data the data to write + * + * @hide */ + @SystemApi @SuppressLint("RequiresPermission") public int write(byte[] data) { try { @@ -103,7 +110,10 @@ public class PersistentDataBlockManager { /** * Returns the data block stored on the persistent partition. + * + * @hide */ + @SystemApi @SuppressLint("RequiresPermission") public byte[] read() { try { @@ -117,7 +127,10 @@ public class PersistentDataBlockManager { * Retrieves the size of the block currently written to the persistent partition. * * Return -1 on error. + * + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public int getDataBlockSize() { try { @@ -131,7 +144,10 @@ public class PersistentDataBlockManager { * Retrieves the maximum size allowed for a data block. * * Returns -1 on error. + * + * @hide */ + @SystemApi @SuppressLint("RequiresPermission") public long getMaximumDataBlockSize() { try { @@ -146,7 +162,10 @@ public class PersistentDataBlockManager { * will erase all data written to the persistent data partition. * It will also prevent any further {@link #write} operation until reboot, * in order to prevent a potential race condition. See b/30352311. + * + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.OEM_UNLOCK_STATE) public void wipe() { try { @@ -160,7 +179,11 @@ public class PersistentDataBlockManager { * Writes a byte enabling or disabling the ability to "OEM unlock" the device. * * @deprecated use {@link OemLockManager#setOemUnlockAllowedByUser(boolean)} instead. + * + * @hide */ + @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.OEM_UNLOCK_STATE) public void setOemUnlockEnabled(boolean enabled) { try { @@ -174,7 +197,11 @@ public class PersistentDataBlockManager { * Returns whether or not "OEM unlock" is enabled or disabled on this device. * * @deprecated use {@link OemLockManager#isOemUnlockAllowedByUser()} instead. + * + * @hide */ + @SystemApi + @Deprecated @RequiresPermission(anyOf = { android.Manifest.permission.READ_OEM_UNLOCK_STATE, android.Manifest.permission.OEM_UNLOCK_STATE @@ -193,7 +220,10 @@ public class PersistentDataBlockManager { * @return {@link #FLASH_LOCK_LOCKED} if device bootloader is locked, * {@link #FLASH_LOCK_UNLOCKED} if device bootloader is unlocked, or {@link #FLASH_LOCK_UNKNOWN} * if this information cannot be ascertained on this device. + * + * @hide */ + @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.READ_OEM_UNLOCK_STATE, android.Manifest.permission.OEM_UNLOCK_STATE @@ -222,4 +252,73 @@ public class PersistentDataBlockManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns true if FactoryResetProtection (FRP) is active, meaning the device rebooted and has + * not been able to deactivate FRP because the deactivation secrets were wiped by an untrusted + * factory reset. + */ + @FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) + public boolean isFactoryResetProtectionActive() { + try { + return sService.isFactoryResetProtectionActive(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attempt to deactivate FRP with the provided secret. If the provided secret matches the + * stored FRP secret, FRP is deactivated and the method returns true. Otherwise, FRP state + * remains unchanged and the method returns false. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) + @SystemApi + @RequiresPermission(android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION) + public boolean deactivateFactoryResetProtection(@NonNull byte[] secret) { + try { + return sService.deactivateFactoryResetProtection(secret); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Store the provided FRP secret as the secret to be used for future FRP deactivation. The + * secret must be 32 bytes in length. Setting the all-zeros "default" value disables the FRP + * feature entirely. + * + * To ensure that the device doesn't end up in a bad state if a crash occurs, this method + * should be used in a three-step process: + * + * 1. Generate a new secret and securely store any necessary copies (e.g. by encrypting them + * and calling #write with a new data block that contains both the old encrypted secret + * copies and the new ones). + * 2. Call this method to set the new FRP secret. This will also write the copy used during + * normal boot. + * 3. Delete any old FRP secret copies (e.g. by calling #write with a new data block that + * contains only the new encrypted secret copies). + * + * Note that this method does nothing if FRP is currently active. + * + * This method does not require any permission, but can be called only by the + * PersistentDataBlockService's authorized caller UID. + * + * Returns true if the new secret was successfully written. Returns false if FRP is currently + * active. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT) + @SystemApi + @SuppressLint("RequiresPermission") + public boolean setFactoryResetProtectionSecret(@NonNull byte[] secret) { + try { + return sService.setFactoryResetProtectionSecret(secret); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS index ec4410086edf..763c79e20846 100644 --- a/core/java/android/service/voice/OWNERS +++ b/core/java/android/service/voice/OWNERS @@ -4,4 +4,4 @@ include /core/java/android/app/assist/OWNERS # The owner here should not be assist owner liangyuchen@google.com -tuanng@google.com +adudani@google.com diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl index 44a13c4fb9e5..055618861391 100644 --- a/core/java/android/service/wearable/IWearableSensingService.aidl +++ b/core/java/android/service/wearable/IWearableSensingService.aidl @@ -28,8 +28,11 @@ import android.os.SharedMemory; * @hide */ oneway interface IWearableSensingService { + void provideSecureWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback); void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback); void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback); + void registerDataRequestObserver(int dataType, in RemoteCallback dataRequestCallback, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback); + void unregisterDataRequestObserver(int dataType, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback); void startDetection(in AmbientContextEventRequest request, in String packageName, in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback); void stopDetection(in String packageName); diff --git a/core/java/android/service/wearable/WearableSensingDataRequester.java b/core/java/android/service/wearable/WearableSensingDataRequester.java new file mode 100644 index 000000000000..5a8104f7e0cc --- /dev/null +++ b/core/java/android/service/wearable/WearableSensingDataRequester.java @@ -0,0 +1,87 @@ +/* + * 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.service.wearable; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.app.wearable.Flags; +import android.app.wearable.WearableSensingDataRequest; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.function.Consumer; + +/** + * An interface to request wearable sensing data. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) +@SystemApi +public interface WearableSensingDataRequester { + + /** An unknown status. */ + int STATUS_UNKNOWN = 0; + + /** The value of the status code that indicates success. */ + int STATUS_SUCCESS = 1; + + /** + * The value of the status code that indicates the request is rejected because the data request + * observer PendingIntent has been cancelled. + */ + int STATUS_OBSERVER_CANCELLED = 2; + + /** + * The value of the status code that indicates the request is rejected because it is larger than + * {@link WearableSensingDataRequest#getMaxRequestSize()}. + */ + int STATUS_TOO_LARGE = 3; + + /** + * The value of the status code that indicates the request is rejected because it exceeds the + * rate limit. See {@link WearableSensingDataRequest#getRateLimit()}. + */ + int STATUS_TOO_FREQUENT = 4; + + /** @hide */ + @IntDef( + prefix = {"STATUS_"}, + value = { + STATUS_UNKNOWN, + STATUS_SUCCESS, + STATUS_OBSERVER_CANCELLED, + STATUS_TOO_LARGE, + STATUS_TOO_FREQUENT + }) + @Retention(RetentionPolicy.SOURCE) + @interface StatusCode {} + + /** + * Sends a data request. See {@link WearableSensingService#onDataRequestObserverRegistered(int, + * String, WearableSensingDataRequester, Consumer)} for size and rate restrictions on data + * requests. + * + * @param dataRequest The data request to send. + * @param statusConsumer A consumer that handles the status code for the data request. + */ + void requestData( + @NonNull WearableSensingDataRequest dataRequest, + @NonNull @StatusCode Consumer<Integer> statusConsumer); +} diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java index e7e44a54cbae..d25cff7d35c7 100644 --- a/core/java/android/service/wearable/WearableSensingService.java +++ b/core/java/android/service/wearable/WearableSensingService.java @@ -17,12 +17,15 @@ package android.service.wearable; import android.annotation.BinderThread; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.Service; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.wearable.Flags; +import android.app.wearable.WearableSensingDataRequest; import android.app.wearable.WearableSensingManager; import android.content.Intent; import android.os.Bundle; @@ -34,11 +37,13 @@ import android.os.SharedMemory; import android.service.ambientcontext.AmbientContextDetectionResult; import android.service.ambientcontext.AmbientContextDetectionServiceStatus; import android.util.Slog; +import android.util.SparseArray; import java.util.Arrays; import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -87,6 +92,9 @@ public abstract class WearableSensingService extends Service { public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService"; + private final SparseArray<WearableSensingDataRequester> mDataRequestObserverIdToRequesterMap = + new SparseArray<>(); + @Nullable @Override public final IBinder onBind(@NonNull Intent intent) { @@ -94,17 +102,20 @@ public abstract class WearableSensingService extends Service { return new IWearableSensingService.Stub() { /** {@inheritDoc} */ @Override + public void provideSecureWearableConnection( + ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) { + Objects.requireNonNull(secureWearableConnection); + Consumer<Integer> consumer = createWearableStatusConsumer(callback); + WearableSensingService.this.onSecureWearableConnectionProvided( + secureWearableConnection, consumer); + } + + /** {@inheritDoc} */ + @Override public void provideDataStream( - ParcelFileDescriptor parcelFileDescriptor, - RemoteCallback callback) { + ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) { Objects.requireNonNull(parcelFileDescriptor); - Consumer<Integer> consumer = response -> { - Bundle bundle = new Bundle(); - bundle.putInt( - STATUS_RESPONSE_BUNDLE_KEY, - response); - callback.sendResult(bundle); - }; + Consumer<Integer> consumer = createWearableStatusConsumer(callback); WearableSensingService.this.onDataStreamProvided( parcelFileDescriptor, consumer); } @@ -116,38 +127,87 @@ public abstract class WearableSensingService extends Service { SharedMemory sharedMemory, RemoteCallback callback) { Objects.requireNonNull(data); - Consumer<Integer> consumer = response -> { - Bundle bundle = new Bundle(); - bundle.putInt( - STATUS_RESPONSE_BUNDLE_KEY, - response); - callback.sendResult(bundle); - }; + Consumer<Integer> consumer = createWearableStatusConsumer(callback); WearableSensingService.this.onDataProvided(data, sharedMemory, consumer); } /** {@inheritDoc} */ @Override - public void startDetection(@NonNull AmbientContextEventRequest request, - String packageName, RemoteCallback detectionResultCallback, + public void registerDataRequestObserver( + int dataType, + RemoteCallback dataRequestCallback, + int dataRequestObserverId, + String packageName, + RemoteCallback statusCallback) { + Objects.requireNonNull(dataRequestCallback); + Objects.requireNonNull(statusCallback); + WearableSensingDataRequester dataRequester; + synchronized (mDataRequestObserverIdToRequesterMap) { + dataRequester = + mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId); + if (dataRequester == null) { + dataRequester = createDataRequester(dataRequestCallback); + mDataRequestObserverIdToRequesterMap.put( + dataRequestObserverId, dataRequester); + } + } + Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback); + WearableSensingService.this.onDataRequestObserverRegistered( + dataType, packageName, dataRequester, statusConsumer); + } + + @Override + public void unregisterDataRequestObserver( + int dataType, + int dataRequestObserverId, + String packageName, + RemoteCallback statusCallback) { + WearableSensingDataRequester dataRequester; + synchronized (mDataRequestObserverIdToRequesterMap) { + dataRequester = + mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId); + if (dataRequester == null) { + Slog.w( + TAG, + "dataRequestObserverId not found, cannot unregister data" + + " request observer."); + return; + } + mDataRequestObserverIdToRequesterMap.remove(dataRequestObserverId); + } + Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback); + WearableSensingService.this.onDataRequestObserverUnregistered( + dataType, packageName, dataRequester, statusConsumer); + } + + /** {@inheritDoc} */ + @Override + public void startDetection( + @NonNull AmbientContextEventRequest request, + String packageName, + RemoteCallback detectionResultCallback, RemoteCallback statusCallback) { Objects.requireNonNull(request); Objects.requireNonNull(packageName); Objects.requireNonNull(detectionResultCallback); Objects.requireNonNull(statusCallback); - Consumer<AmbientContextDetectionResult> detectionResultConsumer = result -> { - Bundle bundle = new Bundle(); - bundle.putParcelable( - AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, result); - detectionResultCallback.sendResult(bundle); - }; - Consumer<AmbientContextDetectionServiceStatus> statusConsumer = status -> { - Bundle bundle = new Bundle(); - bundle.putParcelable( - AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY, - status); - statusCallback.sendResult(bundle); - }; + Consumer<AmbientContextDetectionResult> detectionResultConsumer = + result -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, + result); + detectionResultCallback.sendResult(bundle); + }; + Consumer<AmbientContextDetectionServiceStatus> statusConsumer = + status -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionServiceStatus + .STATUS_RESPONSE_BUNDLE_KEY, + status); + statusCallback.sendResult(bundle); + }; WearableSensingService.this.onStartDetection( request, packageName, statusConsumer, detectionResultConsumer); Slog.d(TAG, "startDetection " + request); @@ -162,23 +222,26 @@ public abstract class WearableSensingService extends Service { /** {@inheritDoc} */ @Override - public void queryServiceStatus(@AmbientContextEvent.EventCode int[] eventTypes, - String packageName, RemoteCallback callback) { + public void queryServiceStatus( + @AmbientContextEvent.EventCode int[] eventTypes, + String packageName, + RemoteCallback callback) { Objects.requireNonNull(eventTypes); Objects.requireNonNull(packageName); Objects.requireNonNull(callback); - Consumer<AmbientContextDetectionServiceStatus> consumer = response -> { - Bundle bundle = new Bundle(); - bundle.putParcelable( - AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY, - response); - callback.sendResult(bundle); - }; + Consumer<AmbientContextDetectionServiceStatus> consumer = + response -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionServiceStatus + .STATUS_RESPONSE_BUNDLE_KEY, + response); + callback.sendResult(bundle); + }; Integer[] events = intArrayToIntegerArray(eventTypes); WearableSensingService.this.onQueryServiceStatus( new HashSet<>(Arrays.asList(events)), packageName, consumer); } - }; } Slog.w(TAG, "Incorrect service interface, returning null."); @@ -186,6 +249,30 @@ public abstract class WearableSensingService extends Service { } /** + * Called when a secure connection to the wearable is available. See {@link + * WearableSensingManager#provideWearableConnection(ParcelFileDescriptor, Executor, Consumer)} + * for details about the secure connection. + * + * <p>When the {@code secureWearableConnection} is closed, the system will send a {@link + * WearableSensingManager#STATUS_CHANNEL_ERROR} status code to the status consumer provided by + * the caller of {@link WearableSensingManager#provideWearableConnection(ParcelFileDescriptor, + * Executor, Consumer)}. + * + * <p>The implementing class should override this method. It should return an appropriate status + * code via {@code statusConsumer} after receiving the {@code secureWearableConnection}. + * + * @param secureWearableConnection The secure connection to the wearable. + * @param statusConsumer The consumer for the service status. + */ + @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API) + @BinderThread + public void onSecureWearableConnectionProvided( + @NonNull ParcelFileDescriptor secureWearableConnection, + @NonNull Consumer<Integer> statusConsumer) { + statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); + } + + /** * Called when a data stream to the wearable is provided. This data stream can be used to obtain * data from a wearable device. It is up to the implementation to maintain the data stream and * close the data stream when it is finished. @@ -198,19 +285,19 @@ public abstract class WearableSensingService extends Service { @NonNull Consumer<Integer> statusConsumer); /** - * Called when configurations and read-only data in a {@link PersistableBundle} - * can be used by the WearableSensingService and sends the result to the {@link Consumer} - * right after the call. It is dependent on the application to define the type of data to - * provide. This is used by applications that will also provide an implementation of an isolated - * WearableSensingService. If the data was provided successfully - * {@link WearableSensingManager#STATUS_SUCCESS} will be provided. + * Called when configurations and read-only data in a {@link PersistableBundle} can be used by + * the WearableSensingService and sends the result to the {@link Consumer} right after the call. + * It is dependent on the application to define the type of data to provide. This is used by + * applications that will also provide an implementation of an isolated WearableSensingService. + * If the data was provided successfully {@link WearableSensingManager#STATUS_SUCCESS} will be + * provided. * * @param data Application configuration data to provide to the {@link WearableSensingService}. - * PersistableBundle does not allow any remotable objects or other contents - * that can be used to communicate with other processes. - * @param sharedMemory The unrestricted data blob to - * provide to the {@link WearableSensingService}. Use this to provide the - * sensing models data or other such data to the trusted process. + * PersistableBundle does not allow any remotable objects or other contents that can be used + * to communicate with other processes. + * @param sharedMemory The unrestricted data blob to provide to the {@link + * WearableSensingService}. Use this to provide the sensing models data or other such data + * to the trusted process. * @param statusConsumer the consumer for the service status. */ @BinderThread @@ -220,6 +307,68 @@ public abstract class WearableSensingService extends Service { @NonNull Consumer<Integer> statusConsumer); /** + * Called when a data request observer is registered. Each request must not be larger than + * {@link WearableSensingDataRequest#getMaxRequestSize()}. In addition, at most {@link + * WearableSensingDataRequester#getRateLimit()} requests can be sent every rolling {@link + * WearableSensingDataRequester#getRateLimitWindowSize()}. Requests that are too large or too + * frequent will be dropped by the system. See {@link + * WearableSensingDataRequester#requestData(WearableSensingDataRequest, Consumer)} for details + * about the status code returned for each request. + * + * <p>The implementing class should override this method. After the data requester is received, + * it should send a {@link WearableSensingManager#STATUS_SUCCESS} status code to the {@code + * statusConsumer} unless it encounters an error condition described by a status code listed in + * {@link WearableSensingManager}, such as {@link + * WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, in which case it should return the + * corresponding status code. + * + * @param dataType The data type the observer is registered for. Values are defined by the + * application that implements this class. + * @param packageName The package name of the app that will receive the requests. + * @param dataRequester A handle to the observer registered. It can be used to request data of + * the specified data type. + * @param statusConsumer the consumer for the status of the data request observer registration. + * This is different from the status for each data request. + */ + @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) + @BinderThread + public void onDataRequestObserverRegistered( + int dataType, + @NonNull String packageName, + @NonNull WearableSensingDataRequester dataRequester, + @NonNull Consumer<Integer> statusConsumer) { + statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); + } + + /** + * Called when a data request observer is unregistered. + * + * <p>The implementing class should override this method. It should send a {@link + * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it + * encounters an error condition described by a status code listed in {@link + * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, + * in which case it should return the corresponding status code. + * + * @param dataType The data type the observer is for. + * @param packageName The package name of the app that will receive the requests sent to the + * dataRequester. + * @param dataRequester A handle to the observer to be unregistered. It is the exact same + * instance provided in a previous {@link #onDataRequestConsumerRegistered(int, String, + * WearableSensingDataRequester, Consumer)} invocation. + * @param statusConsumer the consumer for the status of the data request observer + * unregistration. This is different from the status for each data request. + */ + @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API) + @BinderThread + public void onDataRequestObserverUnregistered( + int dataType, + @NonNull String packageName, + @NonNull WearableSensingDataRequester dataRequester, + @NonNull Consumer<Integer> statusConsumer) { + statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); + } + + /** * Called when a client app requests starting detection of the events in the request. The * implementation should keep track of whether the user has explicitly consented to detecting * the events using on-going ambient sensor (e.g. microphone), and agreed to share the @@ -275,4 +424,32 @@ public abstract class WearableSensingService extends Service { } return intArray; } + + private static WearableSensingDataRequester createDataRequester( + RemoteCallback dataRequestCallback) { + return (request, requestStatusConsumer) -> { + Bundle bundle = new Bundle(); + bundle.putParcelable(WearableSensingDataRequest.REQUEST_BUNDLE_KEY, request); + RemoteCallback requestStatusCallback = + new RemoteCallback( + requestStatusBundle -> { + requestStatusConsumer.accept( + requestStatusBundle.getInt( + WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY)); + }); + bundle.putParcelable( + WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY, + requestStatusCallback); + dataRequestCallback.sendResult(bundle); + }; + } + + @NonNull + private static Consumer<Integer> createWearableStatusConsumer(RemoteCallback statusCallback) { + return response -> { + Bundle bundle = new Bundle(); + bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response); + statusCallback.sendResult(bundle); + }; + } } diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index 9b21b765a874..270bf8b17aac 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -32,6 +32,7 @@ import android.graphics.RectF; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.VectorDrawable; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -173,6 +174,8 @@ public final class PointerIcon implements Parcelable { private Bitmap mBitmapFrames[]; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private int mDurationPerFrame; + @SuppressWarnings("unused") + private boolean mDrawNativeDropShadow; private PointerIcon(int type) { mType = type; @@ -231,9 +234,15 @@ public final class PointerIcon implements Parcelable { typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT); } - final int defStyle = useLargeIcons - ? com.android.internal.R.style.LargePointer - : com.android.internal.R.style.Pointer; + final int defStyle; + // TODO(b/305193969): Use scaled vectors when large icons are requested. + if (useLargeIcons) { + defStyle = com.android.internal.R.style.LargePointer; + } else if (android.view.flags.Flags.enableVectorCursors()) { + defStyle = com.android.internal.R.style.VectorPointer; + } else { + defStyle = com.android.internal.R.style.Pointer; + } TypedArray a = context.obtainStyledAttributes(null, com.android.internal.R.styleable.Pointer, 0, defStyle); @@ -333,6 +342,7 @@ public final class PointerIcon implements Parcelable { Bitmap.CREATOR.createFromParcel(in), in.readFloat(), in.readFloat()); + icon.mDrawNativeDropShadow = in.readBoolean(); return icon; } @@ -362,6 +372,7 @@ public final class PointerIcon implements Parcelable { mBitmap.writeToParcel(out, flags); out.writeFloat(mHotSpotX); out.writeFloat(mHotSpotY); + out.writeBoolean(mDrawNativeDropShadow); } @Override @@ -415,6 +426,16 @@ public final class PointerIcon implements Parcelable { return scaled; } + private BitmapDrawable getBitmapDrawableFromVectorDrawable(Resources resources, + VectorDrawable vectorDrawable) { + Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), + vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + vectorDrawable.draw(canvas); + return new BitmapDrawable(resources, bitmap); + } + private void loadResource(Context context, Resources resources, @XmlRes int resourceId) { final XmlResourceParser parser = resources.getXml(resourceId); final int bitmapRes; @@ -476,6 +497,10 @@ public final class PointerIcon implements Parcelable { } } } + if (drawable instanceof VectorDrawable) { + mDrawNativeDropShadow = true; + drawable = getBitmapDrawableFromVectorDrawable(resources, (VectorDrawable) drawable); + } if (!(drawable instanceof BitmapDrawable)) { throw new IllegalArgumentException("<pointer-icon> bitmap attribute must " + "refer to a bitmap drawable."); diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig index 1dd99baf8d2a..3e7a9cb34878 100644 --- a/core/java/android/view/flags/view_flags.aconfig +++ b/core/java/android/view/flags/view_flags.aconfig @@ -1,13 +1,6 @@ package: "android.view.flags" flag { - name: "enable_surface_native_alloc_registration" - namespace: "toolkit" - description: "Feature flag for registering surfaces with the VM for faster cleanup" - bug: "306193257" -} - -flag { name: "enable_surface_native_alloc_registration_ro" namespace: "toolkit" description: "Feature flag for registering surfaces with the VM for faster" @@ -24,3 +17,11 @@ flag { bug: "316170253" is_fixed_read_only: true } + +flag { + name: "enable_vector_cursors" + namespace: "systemui" + description: "Feature flag to enable vector drawables in addition to bitmaps for PointerIcon." + bug: "305193969" + is_fixed_read_only: true +} diff --git a/core/java/android/view/flags/window_insets.aconfig b/core/java/android/view/flags/window_insets.aconfig new file mode 100644 index 000000000000..201b7ad62f14 --- /dev/null +++ b/core/java/android/view/flags/window_insets.aconfig @@ -0,0 +1,9 @@ +package: "android.view.flags" + +flag { + name: "customizable_window_headers" + namespace: "lse_desktop_experience" + description: "Flag to control the caption bar appearance and to fit app content in its empty space" + bug: "316387515" + is_fixed_read_only: true +} diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java index b0f35784fbbd..a051c1b8917e 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java @@ -89,4 +89,7 @@ public interface ParsedActivity extends ParsedMainComponent { */ @Nullable String getRequiredDisplayCategory(); + + /** Gets the permissions necessary for launching the activity when using content URIs. */ + int getRequireContentUriPermissionFromCaller(); } diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java index 2f977eea127d..12187931d2c9 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java @@ -36,9 +36,9 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.ArraySet; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; -import com.android.internal.pm.pkg.parsing.ParsingUtils; import java.util.Collections; import java.util.Locale; @@ -97,6 +97,8 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse @Nullable private String mRequiredDisplayCategory; + private int mRequireContentUriPermissionFromCaller; + public ParsedActivityImpl(ParsedActivityImpl other) { super(other); this.theme = other.theme; @@ -124,6 +126,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse this.windowLayout = other.windowLayout; this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts; this.mRequiredDisplayCategory = other.mRequiredDisplayCategory; + this.mRequireContentUriPermissionFromCaller = other.mRequireContentUriPermissionFromCaller; } /** @@ -192,6 +195,8 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse alias.setDirectBootAware(target.isDirectBootAware()); alias.setProcessName(target.getProcessName()); alias.setRequiredDisplayCategory(target.getRequiredDisplayCategory()); + alias.setRequireContentUriPermissionFromCaller( + target.getRequireContentUriPermissionFromCaller()); return alias; // Not all attributes from the target ParsedActivity are copied to the alias. @@ -320,6 +325,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse } sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags); dest.writeString8(this.mRequiredDisplayCategory); + dest.writeInt(this.mRequireContentUriPermissionFromCaller); } public ParsedActivityImpl() { @@ -355,6 +361,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse } this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in); this.mRequiredDisplayCategory = in.readString8(); + this.mRequireContentUriPermissionFromCaller = in.readInt(); } @NonNull @@ -412,7 +419,8 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse int rotationAnimation, int colorMode, @Nullable ActivityInfo.WindowLayout windowLayout, - @Nullable String requiredDisplayCategory) { + @Nullable String requiredDisplayCategory, + int requireContentUriPermissionFromCaller) { this.theme = theme; this.uiOptions = uiOptions; this.targetActivity = targetActivity; @@ -438,6 +446,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse this.colorMode = colorMode; this.windowLayout = windowLayout; this.mRequiredDisplayCategory = requiredDisplayCategory; + this.mRequireContentUriPermissionFromCaller = requireContentUriPermissionFromCaller; // onConstructed(); // You can define this method to get a callback } @@ -563,6 +572,11 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse } @DataClass.Generated.Member + public int getRequireContentUriPermissionFromCaller() { + return mRequireContentUriPermissionFromCaller; + } + + @DataClass.Generated.Member public @NonNull ParsedActivityImpl setTheme( int value) { theme = value; return this; @@ -694,11 +708,17 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse return this; } + @DataClass.Generated.Member + public @NonNull ParsedActivityImpl setRequireContentUriPermissionFromCaller( int value) { + mRequireContentUriPermissionFromCaller = value; + return this; + } + @DataClass.Generated( - time = 1701338377709L, + time = 1706180262165L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java", - inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\nprivate int mRequireContentUriPermissionFromCaller\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java index c3f7dab3c46a..9f71d88c24bc 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java @@ -241,6 +241,10 @@ public class ParsedActivityUtils { activity.setRequiredDisplayCategory(requiredDisplayCategory); + activity.setRequireContentUriPermissionFromCaller(sa.getInt( + R.styleable.AndroidManifestActivity_requireContentUriPermissionFromCaller, + ActivityInfo.CONTENT_URI_PERMISSION_NONE)); + return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver, false /*isAlias*/, visibleToEphemeral, input, R.styleable.AndroidManifestActivity_parentActivityName, diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index a8d0d37f78bd..889434f40472 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -168,12 +168,12 @@ public class ConversationLayout extends FrameLayout } public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr) { + @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); } public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @@ -432,8 +432,14 @@ public class ConversationLayout extends FrameLayout final List<MessagingMessage> newHistoricMessagingMessages = createMessages(newHistoricMessages, /* isHistoric= */true, usePrecomputedText); + // Add our new MessagingMessages to groups + List<List<MessagingMessage>> groups = new ArrayList<>(); + List<Person> senders = new ArrayList<>(); + // Lets first find the groups (populate `groups` and `senders`) + findGroups(newHistoricMessagingMessages, newMessagingMessages, user, groups, senders); + return new MessagingData(user, showSpinner, unreadCount, - newHistoricMessagingMessages, newMessagingMessages); + newHistoricMessagingMessages, newMessagingMessages, groups, senders); } /** @@ -509,21 +515,13 @@ public class ConversationLayout extends FrameLayout setUser(messagingData.getUser()); setUnreadCount(messagingData.getUnreadCount()); - List<MessagingMessage> messages = messagingData.getNewMessagingMessages(); - List<MessagingMessage> historicMessages = messagingData.getHistoricMessagingMessages(); // Copy our groups, before they get clobbered ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups); - // Add our new MessagingMessages to groups - List<List<MessagingMessage>> groups = new ArrayList<>(); - List<Person> senders = new ArrayList<>(); - - // Lets first find the groups (populate `groups` and `senders`) - findGroups(historicMessages, messages, groups, senders); - // Let's now create the views and reorder them accordingly // side-effect: updates mGroups, mAddedGroups - createGroupViews(groups, senders, messagingData.getShowSpinner()); + createGroupViews(messagingData.getGroups(), messagingData.getSenders(), + messagingData.getShowSpinner()); // Let's first check which groups were removed altogether and remove them in one animation removeGroups(oldGroups); @@ -536,8 +534,8 @@ public class ConversationLayout extends FrameLayout historicMessage.removeMessage(mToRecycle); } - mMessages = messages; - mHistoricMessages = historicMessages; + mMessages = messagingData.getNewMessagingMessages(); + mHistoricMessages = messagingData.getHistoricMessagingMessages(); updateHistoricMessageVisibility(); updateTitleAndNamesDisplay(); @@ -935,7 +933,7 @@ public class ConversationLayout extends FrameLayout } private void createGroupViews(List<List<MessagingMessage>> groups, - List<Person> senders, boolean showSpinner) { + List<Person> senders, boolean showSpinner) { mGroups.clear(); for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) { List<MessagingMessage> group = groups.get(groupIndex); @@ -983,9 +981,12 @@ public class ConversationLayout extends FrameLayout } } + /** + * Finds groups and senders from the given messaging messages and fills outGroups and outSenders + */ private void findGroups(List<MessagingMessage> historicMessages, - List<MessagingMessage> messages, List<List<MessagingMessage>> groups, - List<Person> senders) { + List<MessagingMessage> messages, Person user, List<List<MessagingMessage>> outGroups, + List<Person> outSenders) { CharSequence currentSenderKey = null; List<MessagingMessage> currentGroup = null; int histSize = historicMessages.size(); @@ -1003,14 +1004,14 @@ public class ConversationLayout extends FrameLayout isNewGroup |= !TextUtils.equals(key, currentSenderKey); if (isNewGroup) { currentGroup = new ArrayList<>(); - groups.add(currentGroup); + outGroups.add(currentGroup); if (sender == null) { - sender = mUser; + sender = user; } else { // Remove all formatting from the sender name sender = sender.toBuilder().setName(Objects.toString(sender.getName())).build(); } - senders.add(sender); + outSenders.add(sender); currentSenderKey = key; } currentGroup.add(message); diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java index 5cda3f2b2bc0..3e065bf9f450 100644 --- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java +++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java @@ -16,8 +16,8 @@ package com.android.internal.widget; +import static android.app.Flags.evenlyDividedCallStyleActionLayout; import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT; -import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT; import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER; import android.annotation.NonNull; @@ -166,7 +166,7 @@ public class EmphasizedNotificationButton extends Button { } private void setIconToGlue(@Nullable Drawable icon) { - if (!USE_NEW_ACTION_LAYOUT) { + if (!evenlyDividedCallStyleActionLayout()) { Log.e(TAG, "glueIcon: new action layout disabled; doing nothing"); return; } @@ -207,7 +207,7 @@ public class EmphasizedNotificationButton extends Button { } private void setLabelToGlue(@Nullable CharSequence label) { - if (!USE_NEW_ACTION_LAYOUT) { + if (!evenlyDividedCallStyleActionLayout()) { Log.e(TAG, "glueLabel: new action layout disabled; doing nothing"); return; } @@ -255,7 +255,7 @@ public class EmphasizedNotificationButton extends Button { return; } - if (!USE_NEW_ACTION_LAYOUT) { + if (!evenlyDividedCallStyleActionLayout()) { Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing"); return; } diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java index 85b02018e7c7..42de60e08e18 100644 --- a/core/java/com/android/internal/widget/MessagingData.java +++ b/core/java/com/android/internal/widget/MessagingData.java @@ -28,24 +28,33 @@ final class MessagingData { private final boolean mShowSpinner; private final List<MessagingMessage> mHistoricMessagingMessages; private final List<MessagingMessage> mNewMessagingMessages; + private final List<List<MessagingMessage>> mGroups; + private final List<Person> mSenders; private final int mUnreadCount; MessagingData(Person user, boolean showSpinner, List<MessagingMessage> historicMessagingMessages, - List<MessagingMessage> newMessagingMessages) { + List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups, + List<Person> senders) { this(user, showSpinner, /* unreadCount= */0, - historicMessagingMessages, newMessagingMessages); + historicMessagingMessages, newMessagingMessages, + groups, + senders); } MessagingData(Person user, boolean showSpinner, int unreadCount, List<MessagingMessage> historicMessagingMessages, - List<MessagingMessage> newMessagingMessages) { + List<MessagingMessage> newMessagingMessages, + List<List<MessagingMessage>> groups, + List<Person> senders) { mUser = user; mShowSpinner = showSpinner; mUnreadCount = unreadCount; mHistoricMessagingMessages = historicMessagingMessages; mNewMessagingMessages = newMessagingMessages; + mGroups = groups; + mSenders = senders; } public Person getUser() { @@ -67,4 +76,12 @@ final class MessagingData { public int getUnreadCount() { return mUnreadCount; } + + public List<Person> getSenders() { + return mSenders; + } + + public List<List<MessagingMessage>> getGroups() { + return mGroups; + } } diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index b6d7503119fe..d000596390ec 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -189,9 +189,15 @@ public class MessagingLayout extends FrameLayout /* isHistoric= */true, usePrecomputedText); final List<MessagingMessage> newMessagingMessages = createMessages(newMessages, /* isHistoric */false, usePrecomputedText); + // Let's first find our groups! + List<List<MessagingMessage>> groups = new ArrayList<>(); + List<Person> senders = new ArrayList<>(); + + // Lets first find the groups + findGroups(historicMessagingMessages, newMessagingMessages, groups, senders); return new MessagingData(user, showSpinner, - historicMessagingMessages, newMessagingMessages); + historicMessagingMessages, newMessagingMessages, groups, senders); } /** @@ -256,10 +262,10 @@ public class MessagingLayout extends FrameLayout private void bind(MessagingData messagingData) { setUser(messagingData.getUser()); - List<MessagingMessage> historicMessages = messagingData.getHistoricMessagingMessages(); - List<MessagingMessage> messages = messagingData.getNewMessagingMessages(); + // Let's now create the views and reorder them accordingly ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups); - addMessagesToGroups(historicMessages, messages, messagingData.getShowSpinner()); + createGroupViews(messagingData.getGroups(), messagingData.getSenders(), + messagingData.getShowSpinner()); // Let's first check which groups were removed altogether and remove them in one animation removeGroups(oldGroups); @@ -272,8 +278,8 @@ public class MessagingLayout extends FrameLayout historicMessage.removeMessage(mToRecycle); } - mMessages = messages; - mHistoricMessages = historicMessages; + mMessages = messagingData.getNewMessagingMessages(); + mHistoricMessages = messagingData.getHistoricMessagingMessages(); updateHistoricMessageVisibility(); updateTitleAndNamesDisplay(); @@ -451,19 +457,6 @@ public class MessagingLayout extends FrameLayout } } - private void addMessagesToGroups(List<MessagingMessage> historicMessages, - List<MessagingMessage> messages, boolean showSpinner) { - // Let's first find our groups! - List<List<MessagingMessage>> groups = new ArrayList<>(); - List<Person> senders = new ArrayList<>(); - - // Lets first find the groups - findGroups(historicMessages, messages, groups, senders); - - // Let's now create the views and reorder them accordingly - createGroupViews(groups, senders, showSpinner); - } - private void createGroupViews(List<List<MessagingMessage>> groups, List<Person> senders, boolean showSpinner) { mGroups.clear(); diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java index 69d254499ef4..301dc392c125 100644 --- a/core/java/com/android/internal/widget/NotificationActionListLayout.java +++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java @@ -17,7 +17,7 @@ package com.android.internal.widget; import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT; -import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT; +import static android.app.Flags.evenlyDividedCallStyleActionLayout; import android.annotation.DimenRes; import android.app.Notification; @@ -410,7 +410,7 @@ public class NotificationActionListLayout extends LinearLayout { */ @RemotableViewMethod public void setEvenlyDividedMode(boolean evenlyDividedMode) { - if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) { + if (evenlyDividedMode && !evenlyDividedCallStyleActionLayout()) { Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; " + "leaving evenly divided mode disabled"); return; diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 3fc16833fc70..240028c191bc 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -98,6 +98,7 @@ cc_library_shared_for_libandroid_runtime { "libminikin", "libz", "server_configurable_flags", + "android.media.audiopolicy-aconfig-cc", ], static_libs: [ diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 969e47b6a803..070d07c22fbc 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -22,6 +22,7 @@ #include <android/media/INativeSpatializerCallback.h> #include <android/media/ISpatializer.h> #include <android/media/audio/common/AudioConfigBase.h> +#include <android_media_audiopolicy.h> #include <android_os_Parcel.h> #include <audiomanager/AudioManager.h> #include <jni.h> @@ -55,6 +56,8 @@ // ---------------------------------------------------------------------------- +namespace audio_flags = android::media::audiopolicy; + using namespace android; using media::audio::common::AudioConfigBase; @@ -145,6 +148,7 @@ static struct { } gAudioPatchFields; static jclass gAudioMixClass; +static jmethodID gAudioMixCstor; static struct { jfieldID mRule; jfieldID mFormat; @@ -165,7 +169,15 @@ static struct { // other fields unused by JNI } gAudioFormatFields; +static jclass gAudioAttributesClass; +static jmethodID gAudioAttributesCstor; +static struct { + jfieldID mSource; + jfieldID mUsage; +} gAudioAttributesFields; + static jclass gAudioMixingRuleClass; +static jmethodID gAudioMixingRuleCstor; static struct { jfieldID mCriteria; jfieldID mAllowPrivilegedPlaybackCapture; @@ -174,6 +186,8 @@ static struct { } gAudioMixingRuleFields; static jclass gAudioMixMatchCriterionClass; +static jmethodID gAudioMixMatchCriterionAttrCstor; +static jmethodID gAudioMixMatchCriterionIntPropCstor; static struct { jfieldID mAttr; jfieldID mIntProp; @@ -2087,6 +2101,39 @@ jobject nativeAudioConfigBaseToJavaAudioFormat(JNIEnv *env, const audio_config_b channelMask, channelIndexMask); } +jint nativeAudioConfigToJavaAudioFormat(JNIEnv *env, const audio_config_t *nConfigBase, + jobject *jAudioFormat, bool isInput) { + if (!audio_flags::audio_mix_test_api()) { + return AUDIO_JAVA_INVALID_OPERATION; + } + + if (nConfigBase == nullptr) { + return AUDIO_JAVA_BAD_VALUE; + } + int propertyMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING | AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE; + int channelMask = 0; + int channelIndexMask = 0; + switch (audio_channel_mask_get_representation(nConfigBase->channel_mask)) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + channelMask = isInput ? inChannelMaskFromNative(nConfigBase->channel_mask) + : outChannelMaskFromNative(nConfigBase->channel_mask); + propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK; + break; + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + channelIndexMask = audio_channel_mask_get_bits(nConfigBase->channel_mask); + propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK; + break; + default: + // This must not happen + break; + } + + *jAudioFormat = env->NewObject(gAudioFormatClass, gAudioFormatCstor, propertyMask, + audioFormatFromNative(nConfigBase->format), + nConfigBase->sample_rate, channelMask, channelIndexMask); + return AUDIO_JAVA_SUCCESS; +} + jint convertAudioMixerAttributesToNative(JNIEnv *env, const jobject jAudioMixerAttributes, audio_mixer_attributes_t *nMixerAttributes) { ScopedLocalRef<jobject> jFormat(env, @@ -2179,6 +2226,88 @@ static jint convertAudioMixingRuleToNative(JNIEnv *env, const jobject audioMixin return AUDIO_JAVA_SUCCESS; } +static jint nativeAudioMixToJavaAudioMixingRule(JNIEnv *env, const AudioMix &nAudioMix, + jobject *jAudioMixingRule) { + if (!audio_flags::audio_mix_test_api()) { + return AUDIO_JAVA_INVALID_OPERATION; + } + + jobject jAudioMixMatchCriterionList = env->NewObject(gArrayListClass, gArrayListMethods.cstor); + for (const auto &criteria : nAudioMix.mCriteria) { + jobject jAudioAttributes = NULL; + jobject jMixMatchCriterion = NULL; + jobject jValueInteger = NULL; + switch (criteria.mRule) { + case RULE_MATCH_UID: + jValueInteger = env->NewObject(gIntegerClass, gIntegerCstor, criteria.mValue.mUid); + jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass, + gAudioMixMatchCriterionIntPropCstor, + jValueInteger, criteria.mRule); + break; + case RULE_MATCH_USERID: + jValueInteger = + env->NewObject(gIntegerClass, gIntegerCstor, criteria.mValue.mUserId); + jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass, + gAudioMixMatchCriterionIntPropCstor, + jValueInteger, criteria.mRule); + break; + case RULE_MATCH_AUDIO_SESSION_ID: + jValueInteger = env->NewObject(gIntegerClass, gIntegerCstor, + criteria.mValue.mAudioSessionId); + jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass, + gAudioMixMatchCriterionIntPropCstor, + jValueInteger, criteria.mRule); + break; + case RULE_MATCH_ATTRIBUTE_USAGE: + jAudioAttributes = env->NewObject(gAudioAttributesClass, gAudioAttributesCstor); + env->SetIntField(jAudioAttributes, gAudioAttributesFields.mUsage, + criteria.mValue.mUsage); + jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass, + gAudioMixMatchCriterionAttrCstor, + jMixMatchCriterion, criteria.mRule); + break; + case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: + jAudioAttributes = env->NewObject(gAudioAttributesClass, gAudioAttributesCstor); + env->SetIntField(jAudioAttributes, gAudioAttributesFields.mSource, + criteria.mValue.mSource); + jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass, + gAudioMixMatchCriterionAttrCstor, + jMixMatchCriterion, criteria.mRule); + break; + } + env->CallBooleanMethod(jAudioMixMatchCriterionList, gArrayListMethods.add, + jMixMatchCriterion); + } + + *jAudioMixingRule = env->NewObject(gAudioMixingRuleClass, gAudioMixingRuleCstor, + nAudioMix.mMixType, jAudioMixMatchCriterionList, + nAudioMix.mAllowPrivilegedMediaPlaybackCapture, + nAudioMix.mVoiceCommunicationCaptureAllowed); + return AUDIO_JAVA_SUCCESS; +} + +static jint convertAudioMixFromNative(JNIEnv *env, jobject *jAudioMix, const AudioMix &nAudioMix) { + if (!audio_flags::audio_mix_test_api()) { + return AUDIO_JAVA_INVALID_OPERATION; + } + jobject jAudioMixingRule = NULL; + int status = nativeAudioMixToJavaAudioMixingRule(env, nAudioMix, &jAudioMixingRule); + if (status != AUDIO_JAVA_SUCCESS) { + return status; + } + jobject jAudioFormat = NULL; + status = nativeAudioConfigToJavaAudioFormat(env, &nAudioMix.mFormat, &jAudioFormat, false); + if (status != AUDIO_JAVA_SUCCESS) { + return status; + } + + jstring deviceAddress = env->NewStringUTF(nAudioMix.mDeviceAddress.c_str()); + *jAudioMix = env->NewObject(gAudioMixClass, gAudioMixCstor, jAudioMixingRule, jAudioFormat, + nAudioMix.mRouteFlags, nAudioMix.mCbFlags, nAudioMix.mDeviceType, + deviceAddress); + return AUDIO_JAVA_SUCCESS; +} + static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, const jobject jAudioMix) { nAudioMix->mMixType = env->GetIntField(jAudioMix, gAudioMixFields.mMixType); nAudioMix->mRouteFlags = env->GetIntField(jAudioMix, gAudioMixFields.mRouteFlags); @@ -2252,6 +2381,34 @@ android_media_AudioSystem_registerPolicyMixes(JNIEnv *env, jobject clazz, return nativeToJavaStatus(status); } +static jint android_media_AudioSystem_getRegisteredPolicyMixes(JNIEnv *env, jobject clazz, + jobject jMixes) { + if (!audio_flags::audio_mix_test_api()) { + return AUDIO_JAVA_INVALID_OPERATION; + } + + status_t status; + std::vector<AudioMix> mixes; + ALOGV("AudioSystem::getRegisteredPolicyMixes"); + status = AudioSystem::getRegisteredPolicyMixes(mixes); + ALOGV("AudioSystem::getRegisteredPolicyMixes() returned %zu mixes. Status=%d", mixes.size(), + status); + if (status != NO_ERROR) { + return nativeToJavaStatus(status); + } + + for (const auto &mix : mixes) { + jobject jAudioMix = NULL; + int conversionStatus = convertAudioMixFromNative(env, &jAudioMix, mix); + if (conversionStatus != AUDIO_JAVA_SUCCESS) { + return conversionStatus; + } + env->CallBooleanMethod(jMixes, gListMethods.add, jAudioMix); + } + + return AUDIO_JAVA_SUCCESS; +} + static jint android_media_AudioSystem_updatePolicyMixes(JNIEnv *env, jobject clazz, jobjectArray mixes, jobjectArray updatedMixingRules) { @@ -3251,6 +3408,8 @@ static const JNINativeMethod gMethods[] = MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession), MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I", android_media_AudioSystem_registerPolicyMixes), + MAKE_JNI_NATIVE_METHOD("getRegisteredPolicyMixes", "(Ljava/util/List;)I", + android_media_AudioSystem_getRegisteredPolicyMixes), MAKE_JNI_NATIVE_METHOD("updatePolicyMixes", "([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/" "AudioMixingRule;)I", @@ -3499,6 +3658,11 @@ int register_android_media_AudioSystem(JNIEnv *env) jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix"); gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass); + if (audio_flags::audio_mix_test_api()) { + gAudioMixCstor = GetMethodIDOrDie(env, audioMixClass, "<init>", + "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/" + "media/AudioFormat;IIILjava/lang/String;)V"); + } gAudioMixFields.mRule = GetFieldIDOrDie(env, audioMixClass, "mRule", "Landroid/media/audiopolicy/AudioMixingRule;"); gAudioMixFields.mFormat = GetFieldIDOrDie(env, audioMixClass, "mFormat", @@ -3521,6 +3685,10 @@ int register_android_media_AudioSystem(JNIEnv *env) jclass audioMixingRuleClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule"); gAudioMixingRuleClass = MakeGlobalRefOrDie(env, audioMixingRuleClass); + if (audio_flags::audio_mix_test_api()) { + gAudioMixingRuleCstor = GetMethodIDOrDie(env, audioMixingRuleClass, "<init>", + "(ILjava/util/Collection;ZZ)V"); + } gAudioMixingRuleFields.mCriteria = GetFieldIDOrDie(env, audioMixingRuleClass, "mCriteria", "Ljava/util/ArrayList;"); gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture = @@ -3529,9 +3697,24 @@ int register_android_media_AudioSystem(JNIEnv *env) gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed = GetFieldIDOrDie(env, audioMixingRuleClass, "mVoiceCommunicationCaptureAllowed", "Z"); + if (audio_flags::audio_mix_test_api()) { + jclass audioAttributesClass = FindClassOrDie(env, "android/media/AudioAttributes"); + gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass); + gAudioAttributesCstor = GetMethodIDOrDie(env, gAudioAttributesClass, "<init>", "()V"); + gAudioAttributesFields.mSource = GetFieldIDOrDie(env, gAudioAttributesClass, "mUsage", "I"); + gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, gAudioAttributesClass, "mSource", "I"); + } + jclass audioMixMatchCriterionClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule$AudioMixMatchCriterion"); gAudioMixMatchCriterionClass = MakeGlobalRefOrDie(env,audioMixMatchCriterionClass); + if (audio_flags::audio_mix_test_api()) { + gAudioMixMatchCriterionAttrCstor = + GetMethodIDOrDie(env, gAudioMixMatchCriterionClass, "<init>", + "(Landroid/media/AudioAttributes;I)V"); + gAudioMixMatchCriterionIntPropCstor = GetMethodIDOrDie(env, gAudioMixMatchCriterionClass, + "<init>", "(Ljava/lang/Integer;I)V"); + } gAudioMixMatchCriterionFields.mAttr = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mAttr", "Landroid/media/AudioAttributes;"); gAudioMixMatchCriterionFields.mIntProp = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mIntProp", diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp index c6a3b52d9397..86b0009227b3 100644 --- a/core/jni/android_view_PointerIcon.cpp +++ b/core/jni/android_view_PointerIcon.cpp @@ -37,6 +37,7 @@ static struct { jfieldID mHotSpotY; jfieldID mBitmapFrames; jfieldID mDurationPerFrame; + jfieldID mDrawNativeDropShadow; } gPointerIconClassInfo; @@ -51,6 +52,8 @@ PointerIcon android_view_PointerIcon_toNative(JNIEnv* env, jobject pointerIconOb env->GetIntField(pointerIconObj, gPointerIconClassInfo.mType)); icon.hotSpotX = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotX); icon.hotSpotY = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotY); + icon.drawNativeDropShadow = + env->GetBooleanField(pointerIconObj, gPointerIconClassInfo.mDrawNativeDropShadow); ScopedLocalRef<jobject> bitmapObj( env, env->GetObjectField(pointerIconObj, gPointerIconClassInfo.mBitmap)); @@ -95,6 +98,9 @@ int register_android_view_PointerIcon(JNIEnv* env) { gPointerIconClassInfo.mBitmapFrames = GetFieldIDOrDie(env, gPointerIconClassInfo.clazz, "mBitmapFrames", "[Landroid/graphics/Bitmap;"); + gPointerIconClassInfo.mDrawNativeDropShadow = + GetFieldIDOrDie(env, gPointerIconClassInfo.clazz, "mDrawNativeDropShadow", "Z"); + gPointerIconClassInfo.mDurationPerFrame = GetFieldIDOrDie(env, gPointerIconClassInfo.clazz, "mDurationPerFrame", "I"); diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h index ee446fb92a4f..1b6a3977bf61 100644 --- a/core/jni/android_view_PointerIcon.h +++ b/core/jni/android_view_PointerIcon.h @@ -39,6 +39,7 @@ struct PointerIcon { float hotSpotY; std::vector<graphics::Bitmap> bitmapFrames; int32_t durationPerFrame; + bool drawNativeDropShadow; inline bool isNullIcon() { return style == PointerIconStyle::TYPE_NULL; } @@ -49,6 +50,7 @@ struct PointerIcon { hotSpotY = 0; bitmapFrames.clear(); durationPerFrame = 0; + drawNativeDropShadow = false; } }; diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 58166bf513b9..0938ce17a4c0 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -2416,6 +2416,11 @@ pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, const std::vector<int>& fds_to_ignore, bool is_priority_fork, bool purge) { + ATRACE_CALL(); + if (is_priority_fork) { + setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX); + } + SetSignalHandlers(); // Curry a failure function. @@ -2501,6 +2506,10 @@ pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, // We blocked SIGCHLD prior to a fork, we unblock it here. UnblockSignal(SIGCHLD, fail_fn); + if (is_priority_fork && pid != 0) { + setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT); + } + return pid; } @@ -2570,6 +2579,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { + ATRACE_CALL(); std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()), fds_to_ignore(fds_to_close); @@ -2645,6 +2655,7 @@ static jint com_android_internal_os_Zygote_nativeForkApp(JNIEnv* env, jintArray managed_session_socket_fds, jboolean args_known, jboolean is_priority_fork) { + ATRACE_CALL(); std::vector<int> session_socket_fds = ExtractJIntArray(env, "USAP", nullptr, managed_session_socket_fds) .value_or(std::vector<int>()); @@ -2660,6 +2671,7 @@ int zygote::forkApp(JNIEnv* env, bool args_known, bool is_priority_fork, bool purge) { + ATRACE_CALL(); std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()), fds_to_ignore(fds_to_close); diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 104c023f550d..10f75d08ee84 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -612,6 +612,7 @@ message SecureSettingsProto { } optional Sounds sounds = 72; + optional SettingProto stylus_pointer_icon_enabled = 99 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto swipe_bottom_to_notification_enabled = 82 [ (android.privacy).dest = DEST_AUTOMATIC ]; // Defines whether managed profile ringtones should be synced from its // parent profile. @@ -720,5 +721,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 99; + // Next tag = 100; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c71a8420ae88..a425bb0e7461 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2772,6 +2772,12 @@ <permission android:name="android.permission.OEM_UNLOCK_STATE" android:protectionLevel="signature" /> + <!-- @SystemApi Allows configuration of factory reset protection + @FlaggedApi("android.security.frp_enforcement") + @hide <p>Not for use by third-party applications. --> + <permission android:name="android.permission.CONFIGURE_FACTORY_RESET_PROTECTION" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows querying state of PersistentDataBlock <p>Not for use by third-party applications. --> <permission android:name="android.permission.ACCESS_PDB_STATE" @@ -3216,6 +3222,13 @@ android:description="@string/permdesc_accessHiddenProfile" android:protectionLevel="normal" /> + <!-- @SystemApi @hide Allows privileged applications to get details about hidden profile + users. + @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") --> + <permission + android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. --> <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES" android:protectionLevel="signature|role" /> @@ -7555,6 +7568,11 @@ <permission android:name="android.permission.RESET_APP_ERRORS" android:protectionLevel="signature" /> + <!-- @hide Allows ThemeOverlayController to delay launch of Home / SetupWizard on boot, ensuring + Theme Palettes and Colors are ready --> + <permission android:name="android.permission.SET_THEME_OVERLAY_CONTROLLER_READY" + android:protectionLevel="signature|setup" /> + <!-- @hide Allows an application to create/destroy input consumer. --> <permission android:name="android.permission.INPUT_CONSUMER" android:protectionLevel="signature" /> diff --git a/core/res/res/drawable/pointer_alias_vector.xml b/core/res/res/drawable/pointer_alias_vector.xml new file mode 100644 index 000000000000..74dd6a0b2a23 --- /dev/null +++ b/core/res/res/drawable/pointer_alias_vector.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#00FFFFFF" + android:pathData="M14.494 12.779a5.2 5.2 0 0 1-1.771 1.414 5.2 5.2 0 0 1-1.68.489l.81 1.658a1.968 1.968 0 0 0 3.536-1.728zM12.03 8.291l-.81-1.658a1.968 1.968 0 0 0-3.536 1.728l.896 1.833a5.2 5.2 0 0 1 1.77-1.414 5.2 5.2 0 0 1 1.68-.489" /> + <path + android:fillColor="#FFFFFF" + android:pathData="m18.323 13.178-.975-1.995a5.2 5.2 0 0 0-1.704-1.978 5.2 5.2 0 0 0-.517-2.01L14.152 5.2a5.232 5.232 0 0 0-9.401 4.594l.975 1.995a5.2 5.2 0 0 0 1.704 1.978 5.2 5.2 0 0 0 .517 2.01l.975 1.995a5.233 5.233 0 0 0 9.401-4.594m-2.843 6.1a4.23 4.23 0 0 1-5.66-1.944l-.975-1.995a4.2 4.2 0 0 1-.431-1.838l-.001-.276-.234-.146a4.2 4.2 0 0 1-1.555-1.729L5.65 9.355a4.232 4.232 0 1 1 7.604-3.716l.975 1.995c.29.594.428 1.22.431 1.838l.001.276.234.146c.648.405 1.194.99 1.555 1.728l.975 1.995a4.234 4.234 0 0 1-1.945 5.661" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M15.313 12.177a3 3 0 0 0-.416-.633l-.459-.534-.353.609a4.2 4.2 0 0 1-1.801 1.675 4.2 4.2 0 0 1-1.977.429l-.704-.02.213.671q.066.208.164.409l.975 1.995a2.967 2.967 0 1 0 5.332-2.606zm-.827 5.066a1.97 1.97 0 0 1-2.632-.904l-.81-1.658a5.2 5.2 0 0 0 1.68-.489 5.2 5.2 0 0 0 1.771-1.414l.896 1.833a1.97 1.97 0 0 1-.905 2.632m-3.697-7.565a4.2 4.2 0 0 1 1.977-.429l.704.02-.213-.671a3 3 0 0 0-.164-.409l-.975-1.995A2.967 2.967 0 1 0 6.785 8.8l.975 1.995q.172.35.416.633l.459.534.353-.609a4.2 4.2 0 0 1 1.801-1.675m-2.21.516-.895-1.833a1.968 1.968 0 1 1 3.536-1.728l.81 1.658a5.2 5.2 0 0 0-1.68.489 5.2 5.2 0 0 0-1.771 1.414m3.151 1.965a3 3 0 0 0 1.02-.818l.755-.95-1.205.142a2.97 2.97 0 0 0-1.975 1.1l-.755.95 1.205-.142c.324-.039.646-.132.955-.282" /> + <path + android:fillColor="#000000" + android:pathData="M16.449 11.622a4.2 4.2 0 0 0-1.555-1.728l-.234-.146-.001-.276a4.2 4.2 0 0 0-.431-1.838l-.975-1.995a4.232 4.232 0 1 0-7.604 3.716l.975 1.995a4.2 4.2 0 0 0 1.555 1.729l.234.146.001.276c.002.617.141 1.244.431 1.838l.975 1.995a4.232 4.232 0 1 0 7.604-3.716zm-7.814.34-.459-.534a3 3 0 0 1-.416-.633L6.785 8.8a2.967 2.967 0 1 1 5.332-2.606l.975 1.995q.098.202.164.409l.214.672-.704-.02a4.2 4.2 0 0 0-1.977.429 4.2 4.2 0 0 0-1.801 1.675zm1.689-.33a2.97 2.97 0 0 1 1.975-1.1l1.205-.142-.755.95a2.95 2.95 0 0 1-1.02.818 3 3 0 0 1-.955.281l-1.204.143zm4.601 6.51a2.967 2.967 0 0 1-3.969-1.363l-.975-1.995a3 3 0 0 1-.164-.409l-.213-.671.704.02a4.2 4.2 0 0 0 1.977-.429 4.2 4.2 0 0 0 1.801-1.675l.353-.609.459.534q.245.284.416.633l.975 1.995a2.97 2.97 0 0 1-1.364 3.969" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_alias_vector_icon.xml b/core/res/res/drawable/pointer_alias_vector_icon.xml new file mode 100644 index 000000000000..6057a2ebc5dc --- /dev/null +++ b/core/res/res/drawable/pointer_alias_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_alias_vector" + android:hotSpotX="11.5dp" + android:hotSpotY="11.5dp" /> diff --git a/core/res/res/drawable/pointer_all_scroll_vector.xml b/core/res/res/drawable/pointer_all_scroll_vector.xml new file mode 100644 index 000000000000..1692e5e62a46 --- /dev/null +++ b/core/res/res/drawable/pointer_all_scroll_vector.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12.93 4.54a1.06 1.06 0 0 0-1.85 0L9.32 7.6c-.4.71.1 1.6.92 1.6h.82v1.86H9.2v-.84c0-.82-.88-1.33-1.6-.93l-3.06 1.76c-.7.41-.7 1.44 0 1.85l3.07 1.76c.7.4 1.6-.1 1.6-.93v-.79h1.86v1.87h-.82c-.81 0-1.33.88-.92 1.6l1.76 3.06c.4.71 1.44.71 1.85 0l1.75-3.07c.41-.7-.1-1.6-.92-1.6h-.82v-1.86h1.86v.8c0 .81.89 1.32 1.6.92l3.07-1.76c.7-.41.7-1.44 0-1.85L16.4 9.3c-.71-.4-1.6.1-1.6.93v.84h-1.86V9.2h.82c.82 0 1.33-.89.92-1.6l-1.75-3.06z" + android:fillColor="#000000"/> + <path + android:pathData="M12 4c.36 0 .72.18.93.54l1.75 3.06c.41.71-.1 1.6-.92 1.6h-.82v1.86h1.86v-.84a1.07 1.07 0 0 1 1.6-.92l3.06 1.75c.72.41.72 1.44 0 1.85l-3.06 1.76a1.07 1.07 0 0 1-1.6-.92v-.8h-1.86v1.87h.82c.82 0 1.33.88.92 1.6l-1.75 3.06a1.07 1.07 0 0 1-1.85 0L9.32 16.4c-.4-.7.1-1.6.93-1.6h.81v-1.86H9.2v.8a1.07 1.07 0 0 1-1.6.92L4.54 12.9a1.06 1.06 0 0 1 0-1.85L7.6 9.3a1.07 1.07 0 0 1 1.6.92v.85h1.86V9.2h-.82c-.81 0-1.33-.89-.92-1.6l1.76-3.06c.2-.36.56-.54.92-.54m0-1c-.74 0-1.41.39-1.79 1.04L8.45 7.1c-.18.33-.28.7-.27 1.05h-.05c-.36 0-.71.1-1.03.28l-3.06 1.76a2.05 2.05 0 0 0 0 3.58l3.06 1.75c.32.18.67.28 1.03.28h.05c-.01.38.08.76.28 1.1l1.75 3.07c.38.65 1.05 1.03 1.8 1.03s1.41-.38 1.78-1.03l1.76-3.07c.2-.34.3-.72.28-1.1h.04c.36 0 .71-.1 1.03-.28l3.06-1.75a2.07 2.07 0 0 0 0-3.58L16.9 8.43a2.07 2.07 0 0 0-1.03-.28h-.04c0-.36-.09-.72-.28-1.05L13.8 4.04A2.04 2.04 0 0 0 12 3z" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/core/res/res/drawable/pointer_all_scroll_vector_icon.xml b/core/res/res/drawable/pointer_all_scroll_vector_icon.xml new file mode 100644 index 000000000000..d64b99abb53e --- /dev/null +++ b/core/res/res/drawable/pointer_all_scroll_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_all_scroll_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_arrow_vector.xml b/core/res/res/drawable/pointer_arrow_vector.xml new file mode 100644 index 000000000000..562f0c05f662 --- /dev/null +++ b/core/res/res/drawable/pointer_arrow_vector.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M16.34 11.18 6.77 4.02a1.78 1.78 0 0 0-1.88-.17c-.63.31-1 .91-1 1.6l.01 11.96c0 .9.6 1.46 1.15 1.67a1.74 1.74 0 0 0 1.98-.45l2.96-3.19c.3-.32.7-.52 1.13-.56l4.33-.47a1.8 1.8 0 0 0 .89-3.23z" + android:fillColor="#000000"/> + <path + android:pathData="M16.94 10.38 7.37 3.22a2.77 2.77 0 0 0-2.93-.27 2.75 2.75 0 0 0-1.55 2.51l.01 11.95a2.78 2.78 0 0 0 2.82 2.8c.77 0 1.5-.32 2.03-.9l2.97-3.19a.8.8 0 0 1 .5-.25l4.34-.46a2.76 2.76 0 0 0 2.4-2.05 2.8 2.8 0 0 0-1.02-2.98zM17 13.1a1.77 1.77 0 0 1-1.55 1.31l-4.33.47a1.8 1.8 0 0 0-1.13.56l-2.97 3.2c-.4.42-.86.57-1.3.57-.24 0-.48-.05-.68-.13a1.77 1.77 0 0 1-1.14-1.67V5.46a1.81 1.81 0 0 1 1.8-1.8c.38 0 .75.11 1.07.36l9.57 7.16c.72.54.81 1.35.66 1.92z" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/core/res/res/drawable/pointer_arrow_vector_icon.xml b/core/res/res/drawable/pointer_arrow_vector_icon.xml new file mode 100644 index 000000000000..b7a89924e417 --- /dev/null +++ b/core/res/res/drawable/pointer_arrow_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_arrow_vector" + android:hotSpotX="4.5dp" + android:hotSpotY="3.5dp" /> diff --git a/core/res/res/drawable/pointer_cell_vector.xml b/core/res/res/drawable/pointer_cell_vector.xml new file mode 100644 index 000000000000..044a4f4014cb --- /dev/null +++ b/core/res/res/drawable/pointer_cell_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="M19 9.667h-4.668V5a2 2 0 0 0-2-2h-.667a2 2 0 0 0-2 2v4.667H5a2 2 0 0 0-2 2v.667a2 2 0 0 0 2 2h4.665V19a2 2 0 0 0 2 2h.667a2 2 0 0 0 2-2v-4.666H19a2 2 0 0 0 2-2v-.667a2 2 0 0 0-2-2m1 2.667a1 1 0 0 1-1 1h-5.668V19a1 1 0 0 1-1 1h-.667a1 1 0 0 1-1-1v-5.666H5a1 1 0 0 1-1-1v-.667a1 1 0 0 1 1-1h5.665V5a1 1 0 0 1 1-1h.667a1 1 0 0 1 1 1v5.667H19a1 1 0 0 1 1 1z" /> + <path + android:fillColor="#000000" + android:pathData="M19 10.667h-5.668V5a1 1 0 0 0-1-1h-.667a1 1 0 0 0-1 1v5.667H5a1 1 0 0 0-1 1v.667a1 1 0 0 0 1 1h5.665V19a1 1 0 0 0 1 1h.667a1 1 0 0 0 1-1v-5.666H19a1 1 0 0 0 1-1v-.667a1 1 0 0 0-1-1" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_cell_vector_icon.xml b/core/res/res/drawable/pointer_cell_vector_icon.xml new file mode 100644 index 000000000000..9e0f63274d2d --- /dev/null +++ b/core/res/res/drawable/pointer_cell_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_cell_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_context_menu_vector.xml b/core/res/res/drawable/pointer_context_menu_vector.xml new file mode 100644 index 000000000000..8e954d290619 --- /dev/null +++ b/core/res/res/drawable/pointer_context_menu_vector.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group> + <path android:fillColor="#FFFFFF" android:pathData="M19.475 2.604h-2.66c-.842 0-1.527.685-1.527 1.527v2.66c0 .842.685 1.527 1.527 1.527h2.66c.842 0 1.527-.685 1.527-1.527v-2.66c0-.842-.685-1.527-1.527-1.527m.67 4.187c0 .37-.3.67-.67.67h-2.66a.67.67 0 0 1-.67-.67v-2.66c0-.37.3-.67.67-.67h2.66c.37 0 .67.3.67.67z" /> + <path android:fillColor="#FFFFFF" android:pathData="M19.175 4.17h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6m0 .886h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6m0 .868h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6" /> + </group> + <path + android:fillColor="#FFFFFF" + android:pathData="M16.938 10.38 7.372 3.216a2.77 2.77 0 0 0-2.931-.262A2.75 2.75 0 0 0 2.894 5.46l.009 11.951a2.785 2.785 0 0 0 1.776 2.604c.33.129.691.197 1.044.197a2.75 2.75 0 0 0 2.031-.897l2.969-3.193a.8.8 0 0 1 .5-.25l4.336-.467c1.397-.15 2.157-1.153 2.401-2.041a2.785 2.785 0 0 0-1.022-2.984m.058 2.718c-.157.571-.645 1.216-1.544 1.312l-4.335.467a1.8 1.8 0 0 0-1.126.563l-2.97 3.193a1.74 1.74 0 0 1-1.298.578 1.9 1.9 0 0 1-.678-.128c-.551-.217-1.141-.771-1.142-1.674l-.009-11.95c0-.697.371-1.299.994-1.611.262-.131.538-.196.813-.196.377 0 .75.123 1.072.365l9.566 7.163c.723.542.814 1.346.657 1.918" /> + <path + android:fillColor="#000000" + android:pathData="M16.339 11.18 6.773 4.017a1.78 1.78 0 0 0-1.072-.365c-.274 0-.551.065-.813.196a1.77 1.77 0 0 0-.994 1.611l.009 11.951c0 .903.59 1.457 1.142 1.674.2.078.433.128.678.128.434 0 .906-.155 1.298-.578l2.97-3.193a1.8 1.8 0 0 1 1.126-.563l4.335-.467c.899-.097 1.387-.741 1.544-1.312.157-.573.066-1.377-.657-1.919" /> + <path + android:fillColor="#000000" + android:pathData="M19.475 3.461h-2.66c-.37 0-.67.3-.67.67v2.66c0 .37.3.67.67.67h2.66c.37 0 .67-.3.67-.67v-2.66a.67.67 0 0 0-.67-.67m-.3 3.062h-2.067a.3.3 0 1 1 0-.6h2.067a.3.3 0 1 1 0 .6m0-.868h-2.067a.3.3 0 1 1 0-.6h2.067a.3.3 0 1 1 0 .6m0-.885h-2.067a.3.3 0 1 1 0-.6h2.067a.3.3 0 1 1 0 .6" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_context_menu_vector_icon.xml b/core/res/res/drawable/pointer_context_menu_vector_icon.xml new file mode 100644 index 000000000000..90f90431438b --- /dev/null +++ b/core/res/res/drawable/pointer_context_menu_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_context_menu_vector" + android:hotSpotX="4.5dp" + android:hotSpotY="3.5dp" /> diff --git a/core/res/res/drawable/pointer_copy_vector.xml b/core/res/res/drawable/pointer_copy_vector.xml new file mode 100644 index 000000000000..b1e8995269a7 --- /dev/null +++ b/core/res/res/drawable/pointer_copy_vector.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group> + <path android:fillColor="#FFFFFF" android:pathData="M17.5 2c-2.104 0-3.861 1.457-4.351 3.41A4.5 4.5 0 0 0 13 6.5c0 .344.047.675.12.997-.062-.002-.122-.009-.185-.009q-.225 0-.446.018V6.484a1 1 0 0 0-2 0v1.57a5.7 5.7 0 0 0-.997.625V7.583a1 1 0 0 0-2 0v4.205l-.697-.713c-.482-.494-1.265-.494-1.747 0s-.482 1.294 0 1.787l3.847 3.936q.056.057.117.106a5.58 5.58 0 0 0 3.922 1.613c3.045 0 5.563-2.469 5.563-5.514q0-.192-.013-.38v-1.739a4.4 4.4 0 0 0 1-.37C20.969 9.778 22 8.265 22 6.5 22 4.019 19.981 2 17.5 2m1.985 7.364a3.6 3.6 0 0 1-1 .478A3.5 3.5 0 0 1 17.5 10a3.5 3.5 0 0 1-3.486-3.358C14.012 6.594 14 6.549 14 6.5c0-.328.06-.639.145-.941C14.559 4.088 15.898 3 17.5 3 19.43 3 21 4.57 21 6.5a3.47 3.47 0 0 1-1.515 2.864" /> + <path android:fillColor="#FFFFFF" android:pathData="M19.299 6H18V4.7a.5.5 0 0 0-1 0V6h-1.301a.5.5 0 0 0 0 1H17v1.3a.5.5 0 0 0 1 0V7h1.299a.5.5 0 0 0 0-1" /> + </group> + <path + android:fillColor="#000000" + android:pathData="M18.485 10.884v1.739q.013.189.013.38c0 3.045-2.518 5.514-5.563 5.514a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022q.22-.018.446-.018c.062 0 .123.007.185.009A4.4 4.4 0 0 1 13 6.5c0-.378.061-.739.149-1.09a1.97 1.97 0 0 0-1.659-.926c-.903 0-1.658.603-1.906 1.425a1.997 1.997 0 0 0-3.091 1.674v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514q-.001-.192-.013-.381v-2.108c-.316.156-.645.29-.999.37" /> + <path + android:fillColor="#1FA54A" + android:pathData="M17.5 3C15.57 3 14 4.57 14 6.5s1.57 3.5 3.5 3.5S21 8.43 21 6.5 19.43 3 17.5 3m1.799 4H18v1.3a.5.5 0 0 1-1 0V7h-1.301a.5.5 0 0 1 0-1H17V4.7a.5.5 0 0 1 1 0V6h1.299a.5.5 0 0 1 0 1" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_copy_vector_icon.xml b/core/res/res/drawable/pointer_copy_vector_icon.xml new file mode 100644 index 000000000000..fe2db15a5c24 --- /dev/null +++ b/core/res/res/drawable/pointer_copy_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_copy_vector" + android:hotSpotX="8.5dp" + android:hotSpotY="7.5dp" /> diff --git a/core/res/res/drawable/pointer_crosshair_vector.xml b/core/res/res/drawable/pointer_crosshair_vector.xml new file mode 100644 index 000000000000..b2e7e8a68615 --- /dev/null +++ b/core/res/res/drawable/pointer_crosshair_vector.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="M19.25 10.25h-5.5v-5.5a1.75 1.75 0 0 0-3.5 0v5.5h-5.5a1.75 1.75 0 0 0 0 3.5h5.5v5.5a1.75 1.75 0 0 0 3.5 0v-5.5h5.5a1.75 1.75 0 0 0 0-3.5m0 2.5h-6.5v6.5a.75.75 0 0 1-1.5 0v-6.5h-6.5a.75.75 0 0 1 0-1.5h6.5v-6.5a.75.75 0 0 1 1.5 0v6.5h6.5a.75.75 0 0 1 0 1.5" /> + <path + android:fillType="evenOdd" + android:fillColor="#000000" + android:pathData="M19.25 11.25h-6.5v-6.5a.75.75 0 0 0-1.5 0v6.5h-6.5a.75.75 0 0 0 0 1.5h6.5v6.5a.75.75 0 0 0 1.5 0v-6.5h6.5a.75.75 0 0 0 0-1.5" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_crosshair_vector_icon.xml b/core/res/res/drawable/pointer_crosshair_vector_icon.xml new file mode 100644 index 000000000000..d938514d2877 --- /dev/null +++ b/core/res/res/drawable/pointer_crosshair_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_crosshair_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_grab_vector.xml b/core/res/res/drawable/pointer_grab_vector.xml new file mode 100644 index 000000000000..7d9f048bbbb6 --- /dev/null +++ b/core/res/res/drawable/pointer_grab_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000000" + android:pathData="M20.442 7.562a2 2 0 0 0-2-2c-.366 0-.705.106-1 .277V4.686a2 2 0 0 0-2-2 2 2 0 0 0-1.004.279 1.995 1.995 0 0 0-3.986-.06 2 2 0 0 0-1.006-.28 2 2 0 0 0-2 2v6.501l-.247-.253a2.216 2.216 0 0 0-3.178 0 2.286 2.286 0 0 0 0 3.186l5.106 5.224q.063.061.131.118l-.001.001a6.58 6.58 0 0 0 4.624 1.901c3.587 0 6.565-2.906 6.565-6.516q0-.105-.004-.21m-6.561 5.727a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-5.106-5.224a1.286 1.286 0 0 1 0-1.788 1.215 1.215 0 0 1 1.747 0l1.962 2.008V4.625a1 1 0 0 1 2 0v5.833q.463-.362.996-.623V3a1 1 0 0 1 2 0v6.29a6 6 0 0 1 1 .011V4.686a1 1 0 0 1 2 0v5.21c.357.185.693.408 1 .663V7.562a1 1 0 0 1 2 0v7.019h.001-.001q.004.104.004.207c.001 3.046-2.518 5.516-5.564 5.516" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M19.442 14.581V7.562a1 1 0 0 0-2 0v2.997a5.7 5.7 0 0 0-1-.663v-5.21a1 1 0 0 0-2 0v4.615a5.5 5.5 0 0 0-1-.011V3a1 1 0 0 0-2 0v6.835a5.6 5.6 0 0 0-.996.623V4.625a1 1 0 0 0-2 0v8.955l-1.962-2.008a1.215 1.215 0 0 0-1.747 0 1.286 1.286 0 0 0 0 1.788l5.106 5.224q.056.057.117.106a5.58 5.58 0 0 0 3.922 1.613c3.046 0 5.565-2.469 5.565-5.516z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_grab_vector_icon.xml b/core/res/res/drawable/pointer_grab_vector_icon.xml new file mode 100644 index 000000000000..6ff70825395c --- /dev/null +++ b/core/res/res/drawable/pointer_grab_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_grab_vector" + android:hotSpotX="9.5dp" + android:hotSpotY="4.5dp" /> diff --git a/core/res/res/drawable/pointer_grabbing_vector.xml b/core/res/res/drawable/pointer_grabbing_vector.xml new file mode 100644 index 000000000000..9c9610366b6d --- /dev/null +++ b/core/res/res/drawable/pointer_grabbing_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000000" + android:pathData="M19.485 12.622V8.508a2 2 0 0 0-3.12-1.657 1.993 1.993 0 0 0-2.99-1.006 1.99 1.99 0 0 0-1.886-1.361c-.903 0-1.658.603-1.906 1.425a2 2 0 0 0-3.09 1.674v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514a5 5 0 0 0-.012-.381m-6.55 5.895a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022a5.5 5.5 0 0 1 .996.009v-.007a1 1 0 0 1 2 0v.599q.537.277 1 .66v-.259a1 1 0 0 1 2 0v4.115q.013.189.013.38c-.001 3.045-2.518 5.514-5.564 5.514" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M18.485 12.622V8.508a1 1 0 0 0-2 0v.259a5.6 5.6 0 0 0-1-.66v-.599a1 1 0 0 0-2 0v.008a5.6 5.6 0 0 0-.996-.009V6.484a1 1 0 0 0-2 0v1.57a5.7 5.7 0 0 0-.997.625V7.583a1 1 0 0 0-2 0v4.205l-.697-.713c-.482-.494-1.265-.494-1.747 0s-.482 1.294 0 1.787l3.847 3.936q.056.057.117.106a5.58 5.58 0 0 0 3.922 1.613c3.045 0 5.563-2.469 5.563-5.514a5 5 0 0 0-.012-.381" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_grabbing_vector_icon.xml b/core/res/res/drawable/pointer_grabbing_vector_icon.xml new file mode 100644 index 000000000000..903c69346d75 --- /dev/null +++ b/core/res/res/drawable/pointer_grabbing_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_grabbing_vector" + android:hotSpotX="8.5dp" + android:hotSpotY="7.5dp" /> diff --git a/core/res/res/drawable/pointer_hand_vector.xml b/core/res/res/drawable/pointer_hand_vector.xml new file mode 100644 index 000000000000..79792f840c85 --- /dev/null +++ b/core/res/res/drawable/pointer_hand_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000000" + android:pathData="M20.492 15.197v-4.198A1.995 1.995 0 0 0 18.5 9.001c-.413 0-.797.126-1.115.342a1.99 1.99 0 0 0-1.873-1.341c-.411 0-.792.125-1.109.339a1.99 1.99 0 0 0-1.879-1.361c-.363 0-.699.105-.992.275V3.998A1.99 1.99 0 0 0 9.542 2c-1.1 0-1.992.895-1.992 1.998v7.831l-.242-.249a2.2 2.2 0 0 0-3.164 0 2.29 2.29 0 0 0 0 3.183l5.084 5.219q.063.061.13.118l-.001.001A6.54 6.54 0 0 0 13.963 22c3.572 0 6.537-2.903 6.537-6.509q0-.148-.008-.294m-6.529 5.804a5.55 5.55 0 0 1-3.906-1.611 1 1 0 0 1-.117-.106l-5.084-5.219a1.286 1.286 0 0 1 0-1.786 1.21 1.21 0 0 1 1.74 0l1.95 2.002V3.998c0-.552.446-.999.996-.999s.996.447.996.999v7.17l.011-.007a.495.495 0 0 0 .989-.037V8.939a.992.992 0 0 1 1.984.039v.796l-.007 1.386a.5.5 0 0 0 .495.502h.003a.5.5 0 0 0 .498-.497l.006-1.157h.001V10a.997.997 0 1 1 1.991 0v.601l.004.003v1.02q.001.107.042.199a.5.5 0 0 0 .153.187l.031.021a.5.5 0 0 0 .231.083c.014.001.026.008.04.008a.5.5 0 0 0 .498-.5v-.642a.996.996 0 0 1 .993-.98c.55 0 .996.447.996.999v4.199a6 6 0 0 1 .008.293c-.001 3.043-2.509 5.51-5.542 5.51" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M19.496 10.999A.997.997 0 0 0 18.5 10a.995.995 0 0 0-.992.98v.644a.5.5 0 0 1-.498.5c-.014 0-.026-.007-.04-.008a.493.493 0 0 1-.457-.491v-1.02l-.004-.003V10c0-.552-.446-.999-.996-.999s-.996.447-.996.999v.008h-.001l-.005 1.003-.001.154a.5.5 0 0 1-.498.497h-.003a.5.5 0 0 1-.495-.502l.001-.159.006-1.227v-.796a.997.997 0 0 0-.996-.999.993.993 0 0 0-.988.96v2.185a.496.496 0 0 1-.989.037l-.011.007v-7.17a.997.997 0 0 0-.996-.999.997.997 0 0 0-.996.999V14.28l-1.95-2.002a1.21 1.21 0 0 0-1.74 0 1.286 1.286 0 0 0 0 1.786l5.084 5.219q.056.057.117.106A5.54 5.54 0 0 0 13.962 21c3.033 0 5.541-2.467 5.541-5.51a6 6 0 0 0-.008-.293z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_hand_vector_icon.xml b/core/res/res/drawable/pointer_hand_vector_icon.xml new file mode 100644 index 000000000000..c59c8dc8de44 --- /dev/null +++ b/core/res/res/drawable/pointer_hand_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_hand_vector" + android:hotSpotX="9.5dp" + android:hotSpotY="2.5dp" /> diff --git a/core/res/res/drawable/pointer_handwriting_vector.xml b/core/res/res/drawable/pointer_handwriting_vector.xml new file mode 100644 index 000000000000..09f3e31473dd --- /dev/null +++ b/core/res/res/drawable/pointer_handwriting_vector.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group> + <path android:fillColor="#FFFFFF" android:pathData="M20.12 6.8 18.7 5.38c-.57-.57-1.32-.88-2.12-.88s-1.56.31-2.13.88l-7.16 7.16-.29.29V5c0-1.1-.9-2-2-2s-2 .9-2 2v14c0 1.1.9 2 2 2s2-.9 2-2v-.5h5.67l.29-.29 7.16-7.16c.57-.57.88-1.32.88-2.12s-.31-1.57-.88-2.13M6 19c0 .55-.45 1-1 1s-1-.45-1-1V5c0-.55.45-1 1-1s1 .45 1 1zm13.41-8.66-7.16 7.16H8v-4.25l7.16-7.16c.39-.39.9-.59 1.41-.59h.01c.51 0 1.02.2 1.41.59l1.42 1.42c.78.78.78 2.05 0 2.83" /> + <path android:fillColor="#FFFFFF" android:pathData="m16.431 7.64-6.29 6.29 1.43 1.43 6.29-6.29-1.42-1.43z" /> + </group> + <path + android:fillColor="#000000" + android:pathData="M5 4c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1s1-.45 1-1V5c0-.55-.45-1-1-1m14.41 3.51-1.42-1.42c-.39-.39-.9-.59-1.41-.59h-.01c-.51 0-1.02.2-1.41.59L8 13.25v4.25h4.25l7.16-7.16c.78-.78.78-2.05 0-2.83m-7.839 7.85-1.43-1.43 6.29-6.29h.01l1.42 1.43z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_handwriting_vector_icon.xml b/core/res/res/drawable/pointer_handwriting_vector_icon.xml new file mode 100644 index 000000000000..14a8700bbe70 --- /dev/null +++ b/core/res/res/drawable/pointer_handwriting_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_handwriting_vector" + android:hotSpotX="8.25dp" + android:hotSpotY="23.75dp" />
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_help_vector.xml b/core/res/res/drawable/pointer_help_vector.xml new file mode 100644 index 000000000000..6b7fd9f99a26 --- /dev/null +++ b/core/res/res/drawable/pointer_help_vector.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000000" + android:pathData="M16.339 11.18 6.773 4.017a1.78 1.78 0 0 0-1.072-.365c-.274 0-.551.065-.813.196a1.77 1.77 0 0 0-.994 1.611l.009 11.951c0 .903.59 1.457 1.142 1.674.2.078.433.128.678.128.434 0 .906-.155 1.298-.578l2.97-3.193a1.8 1.8 0 0 1 1.126-.563l4.335-.467c.899-.097 1.387-.741 1.544-1.312.157-.573.066-1.377-.657-1.919" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M16.94 10.38 7.37 3.22a2.77 2.77 0 0 0-2.93-.27A2.75 2.75 0 0 0 2.9 5.46l.01 11.95a2.79 2.79 0 0 0 2.82 2.8c.78 0 1.5-.32 2.03-.9l2.97-3.19a.8.8 0 0 1 .5-.25l4.34-.46a2.76 2.76 0 0 0 2.4-2.05 2.8 2.8 0 0 0-1.02-2.98zM17 13.1a1.77 1.77 0 0 1-1.55 1.31l-4.33.47a1.8 1.8 0 0 0-1.13.56l-2.97 3.2c-.4.42-.86.57-1.3.57-.24 0-.48-.05-.68-.13a1.77 1.77 0 0 1-1.14-1.67V5.46a1.81 1.81 0 0 1 1.8-1.8c.38 0 .75.11 1.07.36l9.57 7.16c.72.54.81 1.35.66 1.92zm2.64-10.83a2.5 2.5 0 0 0-1.84-.72 3 3 0 0 0-2.83 1.93l-.39.94.96.37.86.32.12.05-.02.03c-.22.4-.3.82-.3 1.33v.94a1.56 1.56 0 0 0 .4 1.47 1.54 1.54 0 0 0 2.24.01 1.55 1.55 0 0 0 .28-1.84v-.52c0-.1.02-.17.03-.25l.16-.15c.32-.25.6-.56.78-.93.18-.37.26-.76.26-1.16 0-.68-.21-1.32-.7-1.82zm-1.5 5.96a.55.55 0 0 1-.82 0 .56.56 0 0 1-.17-.4c0-.16.06-.3.17-.4a.55.55 0 0 1 .41-.18c.15 0 .28.06.4.17a.55.55 0 0 1 0 .81zm1.05-3.42c-.1.22-.28.42-.52.6-.26.22-.42.42-.47.6-.05.18-.08.37-.08.57l-.93-.06c0-.38.07-.62.19-.86.13-.24.3-.46.54-.66.17-.13.3-.28.4-.43s.14-.3.14-.46c0-.2-.08-.37-.22-.5s-.31-.17-.52-.17c-.2 0-.39.06-.56.18-.17.13-.3.31-.4.56l-.87-.33a2.03 2.03 0 0 1 1.91-1.3c.48 0 .86.14 1.13.42.28.28.41.65.41 1.12 0 .26-.05.5-.15.72z" /> + <path + android:fillColor="#000000" + android:pathData="M17.73 7.254a.55.55 0 0 0-.407.169.55.55 0 0 0-.169.407q0 .225.169.401a.55.55 0 0 0 .808 0 .56.56 0 0 0 .175-.413.53.53 0 0 0-.175-.394.56.56 0 0 0-.401-.17m1.202-4.288q-.413-.42-1.126-.419-.651 0-1.164.357a2.1 2.1 0 0 0-.751.945l.864.326q.15-.363.407-.551a.93.93 0 0 1 .557-.188q.313 0 .526.182c.213.182.213.286.213.495q0 .226-.144.457a1.4 1.4 0 0 1-.394.432q-.35.3-.538.657c-.125.238-.187.485-.187.86l.926.06q0-.3.081-.57t.469-.595q.363-.276.519-.601t.156-.726q-.002-.701-.414-1.121" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_help_vector_icon.xml b/core/res/res/drawable/pointer_help_vector_icon.xml new file mode 100644 index 000000000000..78cc3e961c94 --- /dev/null +++ b/core/res/res/drawable/pointer_help_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_help_vector" + android:hotSpotX="4.5dp" + android:hotSpotY="3.5dp" /> diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml new file mode 100644 index 000000000000..d1aea9eacf3f --- /dev/null +++ b/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="m19.963 10.185-3.065-1.758c-1.327-.761-2.96.14-3.072 1.633h-3.651c-.113-1.492-1.746-2.394-3.072-1.633l-3.065 1.758c-1.383.793-1.383 2.786 0 3.579l3.065 1.758c1.311.752 2.918-.12 3.065-1.581h3.666c.147 1.46 1.754 2.333 3.065 1.581l3.065-1.758c1.382-.793 1.382-2.786-.001-3.579m-.498 2.712L16.4 14.655a1.065 1.065 0 0 1-1.596-.922v-.791H9.195v.791c0 .818-.886 1.33-1.596.922l-3.065-1.758a1.063 1.063 0 0 1 0-1.845l3.065-1.758a1.065 1.065 0 0 1 1.596.922v.843h5.609v-.843c0-.818.886-1.33 1.596-.922l3.065 1.758a1.063 1.063 0 0 1 0 1.845" /> + <path + android:fillColor="#000000" + android:pathData="M19.465 11.052 16.4 9.294a1.065 1.065 0 0 0-1.596.922v.843H9.195v-.843c0-.818-.886-1.33-1.596-.922l-3.065 1.758a1.063 1.063 0 0 0 0 1.845l3.065 1.758a1.065 1.065 0 0 0 1.596-.922v-.791h5.609v.791c0 .818.886 1.33 1.596.922l3.065-1.758a1.063 1.063 0 0 0 0-1.845" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_vector_icon.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_vector_icon.xml new file mode 100644 index 000000000000..cee5f9191f18 --- /dev/null +++ b/core/res/res/drawable/pointer_horizontal_double_arrow_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_horizontal_double_arrow_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_nodrop_vector.xml b/core/res/res/drawable/pointer_nodrop_vector.xml new file mode 100644 index 000000000000..3a38babd12d2 --- /dev/null +++ b/core/res/res/drawable/pointer_nodrop_vector.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group> + <path android:fillColor="#FFFFFF" android:pathData="M17.5 1.953c-2.108 0-3.869 1.449-4.382 3.398a4.5 4.5 0 0 0-.165 1.148c0 .343.045.674.117.995-.045-.001-.09-.007-.135-.007q-.225 0-.446.018V6.484a1 1 0 0 0-2 0v1.57a5.7 5.7 0 0 0-.997.625V7.583a1 1 0 0 0-2 0v4.205l-.697-.713c-.482-.494-1.265-.494-1.747 0s-.482 1.294 0 1.787l3.847 3.936q.056.057.117.106a5.58 5.58 0 0 0 3.922 1.613c3.045 0 5.563-2.469 5.563-5.514q0-.192-.013-.38v-1.69a4.5 4.5 0 0 0 1-.366c1.51-.739 2.562-2.275 2.562-4.066A4.55 4.55 0 0 0 17.5 1.953m0 8.047C15.57 10 14 8.43 14 6.5S15.57 3 17.5 3 21 4.57 21 6.5 19.43 10 17.5 10" /> + <path android:fillColor="#FFFFFF" android:pathData="M17.5 4c-.493 0-.95.148-1.337.395l3.442 3.442C19.852 7.45 20 6.993 20 6.5 20 5.121 18.879 4 17.5 4M15 6.5C15 7.879 16.121 9 17.5 9c.525 0 1.011-.164 1.413-.441l-3.472-3.472A2.5 2.5 0 0 0 15 6.5" /> + </group> + <path + android:fillColor="#000000" + android:pathData="M18.485 10.932v1.69q.013.188.013.38c0 3.045-2.518 5.514-5.563 5.514a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022q.22-.018.446-.018c.046 0 .09.006.135.007a4.5 4.5 0 0 1-.117-.995c0-.399.068-.779.165-1.148a1.97 1.97 0 0 0-1.629-.867c-.903 0-1.658.603-1.906 1.425a2 2 0 0 0-1.091-.327 2 2 0 0 0-2 2v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514q-.001-.192-.013-.381v-2.056a4.5 4.5 0 0 1-.999.366" /> + <path + android:fillColor="#B22A25" + android:pathData="M17.5 3C15.57 3 14 4.57 14 6.5s1.57 3.5 3.5 3.5S21 8.43 21 6.5 19.43 3 17.5 3m0 6A2.5 2.5 0 0 1 15 6.5c0-.525.164-1.011.441-1.413l3.472 3.472A2.5 2.5 0 0 1 17.5 9m2.105-1.163-3.442-3.442A2.5 2.5 0 0 1 17.5 4C18.879 4 20 5.121 20 6.5c0 .493-.148.95-.395 1.337" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_nodrop_vector_icon.xml b/core/res/res/drawable/pointer_nodrop_vector_icon.xml new file mode 100644 index 000000000000..ceba0029133e --- /dev/null +++ b/core/res/res/drawable/pointer_nodrop_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_nodrop_vector" + android:hotSpotX="8.5dp" + android:hotSpotY="7.5dp" /> diff --git a/core/res/res/drawable/pointer_spot_anchor_vector.xml b/core/res/res/drawable/pointer_spot_anchor_vector.xml new file mode 100644 index 000000000000..54de2aecb4ce --- /dev/null +++ b/core/res/res/drawable/pointer_spot_anchor_vector.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group> + <path android:fillColor="#ADC6E7" android:pathData="M12 3c-4.963 0-9 4.038-9 9 0 4.963 4.037 9 9 9s9-4.037 9-9c0-4.962-4.037-9-9-9m0 17c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" /> + <path android:fillColor="#ADC6E7" android:pathData="M12 5c-3.859 0-7 3.14-7 7s3.141 7 7 7 7-3.141 7-7-3.141-7-7-7m0 13c-3.309 0-6-2.691-6-6s2.691-6 6-6 6 2.691 6 6-2.691 6-6 6" /> + </group> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_spot_anchor_vector_icon.xml b/core/res/res/drawable/pointer_spot_anchor_vector_icon.xml new file mode 100644 index 000000000000..83b767c7f7ec --- /dev/null +++ b/core/res/res/drawable/pointer_spot_anchor_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_spot_anchor_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_spot_hover_vector.xml b/core/res/res/drawable/pointer_spot_hover_vector.xml new file mode 100644 index 000000000000..ef596c470480 --- /dev/null +++ b/core/res/res/drawable/pointer_spot_hover_vector.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group> + <path android:fillColor="#ADC6E7" android:pathData="M12 3c-4.963 0-9 4.038-9 9 0 4.963 4.037 9 9 9s9-4.037 9-9c0-4.962-4.037-9-9-9m0 17c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" /> + <path android:fillColor="#ADC6E7" android:pathData="M12 7c-2.757 0-5 2.243-5 5s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5m0 9c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4" /> + </group> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_spot_hover_vector_icon.xml b/core/res/res/drawable/pointer_spot_hover_vector_icon.xml new file mode 100644 index 000000000000..f8929586bc04 --- /dev/null +++ b/core/res/res/drawable/pointer_spot_hover_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_spot_hover_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_spot_touch_vector.xml b/core/res/res/drawable/pointer_spot_touch_vector.xml new file mode 100644 index 000000000000..afd2956858fa --- /dev/null +++ b/core/res/res/drawable/pointer_spot_touch_vector.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#ADC6E7" + android:pathData="M21 12c0-4.963-4.038-9-9-9s-9 4.037-9 9 4.038 9 9 9 9-4.037 9-9m-9 8c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_spot_touch_vector_icon.xml b/core/res/res/drawable/pointer_spot_touch_vector_icon.xml new file mode 100644 index 000000000000..7b9693828c33 --- /dev/null +++ b/core/res/res/drawable/pointer_spot_touch_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_spot_touch_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_text_vector.xml b/core/res/res/drawable/pointer_text_vector.xml new file mode 100644 index 000000000000..9e44f28f5779 --- /dev/null +++ b/core/res/res/drawable/pointer_text_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000000" + android:pathData="M12 3c-.551 0-1 .448-1 1v14a1.001 1.001 0 0 0 2 0V4c0-.552-.449-1-1-1" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M12 2c-1.103 0-2 .897-2 2v14c0 1.103.897 2 2 2s2-.897 2-2V4c0-1.103-.897-2-2-2m1 16a1.001 1.001 0 0 1-2 0V4a1.001 1.001 0 0 1 2 0z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_text_vector_icon.xml b/core/res/res/drawable/pointer_text_vector_icon.xml new file mode 100644 index 000000000000..b03f8da376ce --- /dev/null +++ b/core/res/res/drawable/pointer_text_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_text_vector" + android:hotSpotX="12dp" + android:hotSpotY="11dp" /> diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml new file mode 100644 index 000000000000..e5d5301ce009 --- /dev/null +++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="m18.896 16.365-.924-3.41c-.398-1.467-2.169-1.985-3.305-1.035L12.08 9.333c.952-1.136.434-2.908-1.034-3.306l-3.41-.924c-1.539-.416-2.948.993-2.532 2.532l.924 3.41c.398 1.468 2.17 1.986 3.306 1.034l2.586 2.586c-.953 1.136-.435 2.91 1.033 3.307l3.41.924c1.54.417 2.949-.992 2.533-2.531m-2.27 1.566-3.41-.924a1.065 1.065 0 0 1-.476-1.781l.579-.579-3.966-3.966-.579.579a1.066 1.066 0 0 1-1.781-.476L6.07 7.373a1.063 1.063 0 0 1 1.304-1.304l3.41.924a1.065 1.065 0 0 1 .476 1.781l-.578.578 3.966 3.966.577-.577a1.066 1.066 0 0 1 1.781.477l.924 3.41a1.062 1.062 0 0 1-1.304 1.303" /> + <path + android:fillType="evenOdd" + android:fillColor="#000000" + android:pathData="M6.07 7.373a1.063 1.063 0 0 1 1.304-1.304l3.41.924a1.065 1.065 0 0 1 .476 1.781l-.578.578 3.966 3.966.577-.577a1.066 1.066 0 0 1 1.781.476l.924 3.41a1.063 1.063 0 0 1-1.304 1.304l-3.41-.924a1.065 1.065 0 0 1-.476-1.781l.579-.579-3.966-3.966-.579.579a1.066 1.066 0 0 1-1.781-.476z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector_icon.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector_icon.xml new file mode 100644 index 000000000000..7fd2a7f1059b --- /dev/null +++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_top_left_diagonal_double_arrow_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml new file mode 100644 index 000000000000..e6f7aafe2e6b --- /dev/null +++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="m16.365 5.104-3.41.924c-1.468.398-1.986 2.171-1.033 3.307l-2.586 2.586c-1.136-.952-2.909-.434-3.306 1.034l-.924 3.41c-.417 1.539.992 2.948 2.531 2.531l3.41-.924c1.468-.398 1.986-2.17 1.034-3.306l2.587-2.587c1.136.951 2.908.432 3.305-1.035l.924-3.41c.415-1.538-.994-2.947-2.532-2.53m1.565 2.269-.924 3.41a1.065 1.065 0 0 1-1.781.476l-.577-.577-3.966 3.966.578.578a1.066 1.066 0 0 1-.476 1.781l-3.41.924a1.063 1.063 0 0 1-1.304-1.304l.924-3.41a1.066 1.066 0 0 1 1.781-.477l.578.578 3.966-3.966-.579-.579a1.066 1.066 0 0 1 .476-1.781l3.41-.924a1.063 1.063 0 0 1 1.304 1.305" /> + <path + android:fillColor="#000000" + android:pathData="m16.626 6.069-3.41.924a1.065 1.065 0 0 0-.476 1.781l.579.579-3.966 3.966-.579-.579a1.066 1.066 0 0 0-1.781.477l-.924 3.41a1.063 1.063 0 0 0 1.304 1.304l3.41-.924a1.065 1.065 0 0 0 .476-1.781l-.578-.578 3.966-3.966.577.577a1.066 1.066 0 0 0 1.781-.476l.924-3.41a1.062 1.062 0 0 0-1.303-1.304" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector_icon.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector_icon.xml new file mode 100644 index 000000000000..d2516b16fb71 --- /dev/null +++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_top_right_diagonal_double_arrow_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml b/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml new file mode 100644 index 000000000000..6ffcfefead3d --- /dev/null +++ b/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FFFFFF" + android:pathData="M13.945 13.829V10.17c1.476-.131 2.363-1.75 1.606-3.069l-1.758-3.065c-.793-1.383-2.786-1.383-3.579 0L8.455 7.102c-.757 1.319.131 2.939 1.607 3.069v3.658c-1.477.13-2.364 1.75-1.607 3.069l1.758 3.065c.793 1.383 2.786 1.383 3.579 0l1.758-3.065c.758-1.319-.129-2.938-1.605-3.069m.739 2.572-1.758 3.065a1.063 1.063 0 0 1-1.845 0l-1.758-3.065a1.065 1.065 0 0 1 .922-1.596h.818v-5.61h-.818c-.818 0-1.33-.886-.922-1.596l1.758-3.065a1.063 1.063 0 0 1 1.845 0l1.758 3.065a1.065 1.065 0 0 1-.922 1.596h-.817v5.609h.817c.817.001 1.329.886.922 1.597" /> + <path + android:fillColor="#000000" + android:pathData="M13.761 14.805h-.817v-5.61h.817c.818 0 1.33-.886.922-1.596l-1.758-3.065a1.063 1.063 0 0 0-1.845 0L9.323 7.599c-.407.71.104 1.596.922 1.596h.818v5.609h-.818c-.818 0-1.33.886-.922 1.596l1.758 3.065a1.063 1.063 0 0 0 1.845 0l1.758-3.065a1.065 1.065 0 0 0-.923-1.595" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_vector_icon.xml b/core/res/res/drawable/pointer_vertical_double_arrow_vector_icon.xml new file mode 100644 index 000000000000..64f342467915 --- /dev/null +++ b/core/res/res/drawable/pointer_vertical_double_arrow_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_vertical_double_arrow_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_vertical_text_vector.xml b/core/res/res/drawable/pointer_vertical_text_vector.xml new file mode 100644 index 000000000000..72f40ccfc82b --- /dev/null +++ b/core/res/res/drawable/pointer_vertical_text_vector.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000000" + android:pathData="M19 11H5a1 1 0 0 0 0 2h14a1 1 0 0 0 0-2" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M19 10H5c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2m0 3H5a1 1 0 0 1 0-2h14a1 1 0 0 1 0 2" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_vertical_text_vector_icon.xml b/core/res/res/drawable/pointer_vertical_text_vector_icon.xml new file mode 100644 index 000000000000..afb225aa8999 --- /dev/null +++ b/core/res/res/drawable/pointer_vertical_text_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_vertical_text_vector" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_zoom_in_vector.xml b/core/res/res/drawable/pointer_zoom_in_vector.xml new file mode 100644 index 000000000000..89216662964b --- /dev/null +++ b/core/res/res/drawable/pointer_zoom_in_vector.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group> + <path android:fillColor="#FFFFFF" android:pathData="m20.445 17.298-3.591-3.613a7.5 7.5 0 0 0 1.243-4.138 7.547 7.547 0 1 0-7.546 7.546c1.239 0 2.402-.31 3.435-.84l3.733 3.756a1.922 1.922 0 1 0 2.726-2.711m-.713 2.009a.923.923 0 0 1-1.305-.004l-4.268-4.294a6.547 6.547 0 1 1 2.938-5.462 6.52 6.52 0 0 1-1.555 4.236l4.194 4.22a.92.92 0 0 1-.004 1.304" /> + <path android:fillColor="#FFFFFF" android:pathData="M10.55 5a4.546 4.546 0 1 0 0 9.093 4.546 4.546 0 0 0 0-9.093m2.462 5h-2v2a.5.5 0 0 1-1 0v-2h-2a.5.5 0 0 1 0-1h2V7a.5.5 0 0 1 1 0v2h2a.5.5 0 0 1 0 1" /> + </group> + <group> + <path android:fillColor="#000000" android:pathData="m19.736 18.003-4.194-4.22a6.547 6.547 0 1 0-1.382 1.226l4.268 4.294a.923.923 0 0 0 1.308-1.3m-9.186-3.91A4.546 4.546 0 1 1 10.549 5a4.546 4.546 0 0 1 .001 9.093" /> + <path android:fillColor="#000000" android:pathData="M13.012 9h-2V7a.5.5 0 0 0-1 0v2h-2a.5.5 0 0 0 0 1h2v2a.5.5 0 0 0 1 0v-2h2a.5.5 0 0 0 0-1" /> + </group> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_zoom_in_vector_icon.xml b/core/res/res/drawable/pointer_zoom_in_vector_icon.xml new file mode 100644 index 000000000000..fcc0c2867ae0 --- /dev/null +++ b/core/res/res/drawable/pointer_zoom_in_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_zoom_in_vector" + android:hotSpotX="10.5dp" + android:hotSpotY="9.5dp" /> diff --git a/core/res/res/drawable/pointer_zoom_out_vector.xml b/core/res/res/drawable/pointer_zoom_out_vector.xml new file mode 100644 index 000000000000..815ce0ebe9b4 --- /dev/null +++ b/core/res/res/drawable/pointer_zoom_out_vector.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <group> + <path android:fillColor="#FFFFFF" android:pathData="m20.445 17.298-3.591-3.613a7.5 7.5 0 0 0 1.243-4.138 7.547 7.547 0 1 0-7.546 7.546c1.239 0 2.402-.31 3.435-.84l3.733 3.756a1.922 1.922 0 1 0 2.726-2.711m-.713 2.009a.923.923 0 0 1-1.305-.004l-4.268-4.294a6.547 6.547 0 1 1 2.938-5.462 6.52 6.52 0 0 1-1.555 4.236l4.194 4.22a.92.92 0 0 1-.004 1.304" /> + <path android:fillColor="#FFFFFF" android:pathData="M10.55 5a4.546 4.546 0 1 0 0 9.093 4.546 4.546 0 0 0 0-9.093m2.462 5h-5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1" /> + </group> + <group> + <path android:fillColor="#000000" android:pathData="m19.736 18.003-4.194-4.22a6.547 6.547 0 1 0-1.382 1.226l4.268 4.294a.923.923 0 0 0 1.308-1.3m-9.186-3.91A4.546 4.546 0 1 1 10.549 5a4.546 4.546 0 0 1 .001 9.093" /> + <path android:fillColor="#000000" android:pathData="M13.012 9h-5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1" /> + </group> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_zoom_out_vector_icon.xml b/core/res/res/drawable/pointer_zoom_out_vector_icon.xml new file mode 100644 index 000000000000..37f4e793608c --- /dev/null +++ b/core/res/res/drawable/pointer_zoom_out_vector_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_zoom_out_vector" + android:hotSpotX="10.5dp" + android:hotSpotY="9.5dp" /> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index d91094042402..47410125fd65 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -3278,6 +3278,31 @@ <p> By default, the behavior is configured by the same attribute in application. --> <attr name="enableOnBackInvokedCallback" format="boolean"/> + + <!-- Specifies permissions necessary to launch this activity via + {@link android.content.Context#startActivity} when passing content URIs. The default + value is {@code none}, meaning no specific permissions are required. Setting this + attribute restricts activity invocation based on the invoker's permissions. If the + invoker doesn't have the required permissions, the activity start will be denied via a + {@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}. + @FlaggedApi("android.security.content_uri_permission_apis") --> + <attr name="requireContentUriPermissionFromCaller" format="string"> + <!-- Default, no specific permissions are required. --> + <enum name="none" value="0" /> + <!-- Enforces the invoker to have read access to the passed content URIs. --> + <enum name="read" value="1" /> + <!-- Enforces the invoker to have write access to the passed content URIs. --> + <enum name="write" value="2" /> + <!-- Enforces the invoker to have either read or write access to the passed content + URIs. --> + <enum name="readOrWrite" value="3" /> + <!-- Enforces the invoker to have both read and write access to the passed content + URIs. --> + <enum name="readAndWrite" value="4" /> + </attr> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 4799c3769b0f..f9cf28c1cd04 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -149,6 +149,8 @@ <public name="autoTransact"/> <!-- @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") --> <public name="windowOptOutEdgeToEdgeEnforcement"/> + <!-- @FlaggedApi("android.security.content_uri_permission_apis") --> + <public name="requireContentUriPermissionFromCaller" /> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 22d028cb079e..adf8d9f95b52 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1460,6 +1460,43 @@ please see styles_device_defaults.xml. </style> <!-- @hide --> + <style name="VectorPointer"> + <item name="pointerIconArrow">@drawable/pointer_arrow_vector_icon</item> + <item name="pointerIconSpotHover">@drawable/pointer_spot_hover_vector_icon</item> + <item name="pointerIconSpotTouch">@drawable/pointer_spot_touch_vector_icon</item> + <item name="pointerIconSpotAnchor">@drawable/pointer_spot_anchor_vector_icon</item> + <item name="pointerIconHand">@drawable/pointer_hand_vector_icon</item> + <item name="pointerIconContextMenu">@drawable/pointer_context_menu_vector_icon</item> + <item name="pointerIconHelp">@drawable/pointer_help_vector_icon</item> + <item name="pointerIconWait">@drawable/pointer_wait_icon</item> + <item name="pointerIconCell">@drawable/pointer_cell_vector_icon</item> + <item name="pointerIconCrosshair">@drawable/pointer_crosshair_vector_icon</item> + <item name="pointerIconText">@drawable/pointer_text_vector_icon</item> + <item name="pointerIconVerticalText">@drawable/pointer_vertical_text_vector_icon</item> + <item name="pointerIconAlias">@drawable/pointer_alias_vector_icon</item> + <item name="pointerIconCopy">@drawable/pointer_copy_vector_icon</item> + <item name="pointerIconAllScroll">@drawable/pointer_all_scroll_vector_icon</item> + <item name="pointerIconNodrop">@drawable/pointer_nodrop_vector_icon</item> + <item name="pointerIconHorizontalDoubleArrow"> + @drawable/pointer_horizontal_double_arrow_vector_icon + </item> + <item name="pointerIconVerticalDoubleArrow"> + @drawable/pointer_vertical_double_arrow_vector_icon + </item> + <item name="pointerIconTopRightDiagonalDoubleArrow"> + @drawable/pointer_top_right_diagonal_double_arrow_vector_icon + </item> + <item name="pointerIconTopLeftDiagonalDoubleArrow"> + @drawable/pointer_top_left_diagonal_double_arrow_vector_icon + </item> + <item name="pointerIconZoomIn">@drawable/pointer_zoom_in_vector_icon</item> + <item name="pointerIconZoomOut">@drawable/pointer_zoom_out_vector_icon</item> + <item name="pointerIconGrab">@drawable/pointer_grab_vector_icon</item> + <item name="pointerIconGrabbing">@drawable/pointer_grabbing_vector_icon</item> + <item name="pointerIconHandwriting">@drawable/pointer_handwriting_vector_icon</item> + </style> + + <!-- @hide --> <style name="aerr_list_item" parent="Widget.Material.Light.Button.Borderless"> <item name="minHeight">?attr/listPreferredItemHeightSmall</item> <item name="textAppearance">?attr/textAppearanceListItemSmall</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 58427b19fac4..3df7570c818e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1681,6 +1681,7 @@ <java-symbol type="style" name="Theme.DeviceDefault.VoiceInteractionSession" /> <java-symbol type="style" name="Pointer" /> <java-symbol type="style" name="LargePointer" /> + <java-symbol type="style" name="VectorPointer" /> <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Title" /> <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Info" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 9bb249999d99..61e6a36839ff 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -127,7 +127,7 @@ <!-- France: 5 digits, free: 3xxxx, premium [4-8]xxxx, plus EU: http://clients.txtnation.com/entries/161972-france-premium-sms-short-code-requirements, visual voicemail code for Orange: 21101 --> - <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366|555|2051" /> + <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366|555|2051|33033" /> <!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU: http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf, @@ -150,6 +150,9 @@ http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations --> <shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" /> + <!-- Honduras --> + <shortcode country="hn" pattern="\\d{4,6}" free="466453" /> + <!-- India: 1-5 digits (standard system default, not country specific) --> <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" /> @@ -171,7 +174,7 @@ <shortcode country="jp" pattern="\\d{1,5}" free="8083" /> <!-- Kenya: 5 digits, known premium codes listed --> - <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023" /> + <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023|24088|23054" /> <!-- Kyrgyzstan: 4 digits, known premium codes listed --> <shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" /> @@ -183,7 +186,7 @@ <shortcode country="kz" pattern="\\d{4}" premium="335[02]|4161|444[469]|77[2359]0|8444|919[3-5]|968[2-5]" /> <!-- Kuwait: 1-5 digits (standard system default, not country specific) --> - <shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006|55991" /> + <shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006|55991|50976" /> <!-- Lithuania: 3-5 digits, known premium codes listed, plus EU --> <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}|1399|1324" /> @@ -195,9 +198,18 @@ <!-- Latvia: 4 digits, known premium codes listed, plus EU --> <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}|1399" /> + <!-- Morocco: 1-5 digits (standard system default, not country specific) --> + <shortcode country="ma" pattern="\\d{1,5}" free="53819" /> + <!-- Macedonia: 1-6 digits (not confirmed), known premium codes listed --> <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> + <!-- Malawi: 1-5 digits (standard system default, not country specific) --> + <shortcode country="mw" pattern="\\d{1,5}" free="4276" /> + + <!-- Mozambique: 1-5 digits (standard system default, not country specific) --> + <shortcode country="mz" pattern="\\d{1,5}" free="1714" /> + <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" /> @@ -207,6 +219,9 @@ <!-- Namibia: 1-5 digits (standard system default, not country specific) --> <shortcode country="na" pattern="\\d{1,5}" free="40005" /> + <!-- Nicaragua --> + <shortcode country="ni" pattern="\\d{4,6}" free="466453" /> + <!-- The Netherlands, 4 digits, known premium codes listed, plus EU --> <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" /> @@ -219,8 +234,8 @@ <!-- New Zealand: 3-4 digits, known premium codes listed --> <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> - <!-- Peru: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" /> + <!-- Peru: 4-6 digits (not confirmed), known premium codes listed --> + <shortcode country="pe" pattern="\\d{4,6}" free="9963|40778|301303" /> <!-- Philippines --> <shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" /> @@ -269,6 +284,12 @@ <!-- Slovakia: 4 digits (premium), plus EU: http://www.cmtelecom.com/premium-sms/slovakia --> <shortcode country="sk" premium="\\d{4}" free="116\\d{3}|8000" /> + <!-- Senegal(SN): 1-5 digits (standard system default, not country specific) --> + <shortcode country="sn" pattern="\\d{1,5}" free="21215" /> + + <!-- El Salvador(SV): 1-5 digits (standard system default, not country specific) --> + <shortcode country="sv" pattern="\\d{4,6}" free="466453" /> + <!-- Taiwan --> <shortcode country="tw" pattern="\\d{4}" free="1922" /> @@ -278,15 +299,21 @@ <!-- Tajikistan: 4 digits, known premium codes listed --> <shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" /> + <!-- Tanzania: 1-5 digits (standard system default, not country specific) --> + <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234" /> + <!-- Turkey --> <shortcode country="tr" pattern="\\d{1,5}" free="7529|5528|6493|3193" /> <!-- Ukraine: 4 digits, known premium codes listed --> <shortcode country="ua" pattern="\\d{4}" premium="444[3-9]|70[579]4|7540" /> + <!-- Uganda(UG): 4 digits (standard system default, not country specific) --> + <shortcode country="ug" pattern="\\d{4}" free="8000" /> + <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm), visual voicemail code for T-Mobile: 122 --> - <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611" /> + <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831" /> <!-- Vietnam: 1-5 digits (standard system default, not country specific) --> <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055" /> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 13d38d2ff2c6..9d1e5074dd3e 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -124,6 +124,10 @@ <group gid="security_log_writer" /> </permission> + <permission name="android.permission.MANAGE_VIRTUAL_MACHINE"> + <group gid="virtualmachine" /> + </permission> + <!-- These are permissions that were mapped to gids but we need to keep them here until an upgrade from L to the current version is to be supported. These permissions are built-in diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml index bf6094469215..782327713fdc 100644 --- a/data/etc/preinstalled-packages-platform.xml +++ b/data/etc/preinstalled-packages-platform.xml @@ -108,6 +108,7 @@ to pre-existing users, but cannot uninstall pre-existing system packages from pr <install-in user-type="FULL" /> <install-in user-type="PROFILE" /> <do-not-install-in user-type="android.os.usertype.profile.CLONE" /> + <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" /> </install-in-user-type> <!-- Settings (Settings app) --> diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 9854e58dd7cf..a80afe2bf40a 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -45,9 +45,6 @@ <!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu --> <bool name="config_pipEnableResizeForMenu">true</bool> - <!-- Allow PIP to resize via dragging the corner of PiP. --> - <bool name="config_pipEnableDragCornerResize">false</bool> - <!-- PiP minimum size, which is a % based off the shorter side of display width and height --> <fraction name="config_pipShortestEdgePercent">40%</fraction> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java index 160f922dd928..55982dca79b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java @@ -310,12 +310,16 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top); float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width()); float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height()); - float alpha = mapRange(progress, mEnteringProgress, 1.0f); - + float alpha = mapRange(progress, getPreCommitEnteringAlpha(), 1.0f); mEnteringRect.set(left, top, left + width, top + height); applyTransform(mEnteringTarget.leash, mEnteringRect, alpha); } + private float getPreCommitEnteringAlpha() { + return Math.max(smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress), + MIN_WINDOW_ALPHA); + } + private float getEnteringProgress() { return mEnteringProgress * SCALE_FACTOR; } @@ -325,9 +329,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { if (mEnteringTarget != null && mEnteringTarget.leash != null) { transformWithProgress( mEnteringProgress, - Math.max( - smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress), - MIN_WINDOW_ALPHA), /* alpha */ + getPreCommitEnteringAlpha(), mEnteringTarget.leash, mEnteringRect, -mWindowXShift, @@ -336,6 +338,11 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { } } + private float getPreCommitLeavingAlpha() { + return Math.max(1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress), + MIN_WINDOW_ALPHA); + } + private float getLeavingProgress() { return mLeavingProgress * SCALE_FACTOR; } @@ -345,9 +352,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation { if (mClosingTarget != null && mClosingTarget.leash != null) { transformWithProgress( mLeavingProgress, - Math.max( - 1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress), - MIN_WINDOW_ALPHA), + getPreCommitLeavingAlpha(), mClosingTarget.leash, mClosingRect, 0, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index a2a2914f969f..da530d740d48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -53,8 +53,6 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.bubbles.BubbleInfo; -import com.android.wm.shell.taskview.TaskView; -import com.android.wm.shell.taskview.TaskViewTaskController; import java.io.PrintWriter; import java.util.List; @@ -403,13 +401,9 @@ public class Bubble implements BubbleViewProvider { * Returns the existing {@link #mBubbleTaskView} if it's not {@code null}. Otherwise a new * instance of {@link BubbleTaskView} is created. */ - public BubbleTaskView getOrCreateBubbleTaskView(Context context, BubbleController controller) { + public BubbleTaskView getOrCreateBubbleTaskView(BubbleTaskViewFactory taskViewFactory) { if (mBubbleTaskView == null) { - TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context, - controller.getTaskOrganizer(), - controller.getTaskViewTransitions(), controller.getSyncTransactionQueue()); - TaskView taskView = new TaskView(context, taskViewTaskController); - mBubbleTaskView = new BubbleTaskView(taskView, controller.getMainExecutor()); + mBubbleTaskView = taskViewFactory.create(); } return mBubbleTaskView; } @@ -514,14 +508,18 @@ public class Bubble implements BubbleViewProvider { * * @param callback the callback to notify one the bubble is ready to be displayed. * @param context the context for the bubble. - * @param controller the bubble controller. + * @param expandedViewManager the bubble expanded view manager. + * @param taskViewFactory the task view factory used to create the task view for the bubble. + * @param positioner the bubble positioner. * @param stackView the view the bubble is added to, iff showing as floating. * @param layerView the layer the bubble is added to, iff showing in the bubble bar. - * @param iconFactory the icon factory use to create images for the bubble. + * @param iconFactory the icon factory used to create images for the bubble. */ void inflate(BubbleViewInfoTask.Callback callback, Context context, - BubbleController controller, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, @Nullable BubbleStackView stackView, @Nullable BubbleBarLayerView layerView, BubbleIconFactory iconFactory, @@ -531,7 +529,9 @@ public class Bubble implements BubbleViewProvider { } mInflationTask = new BubbleViewInfoTask(this, context, - controller, + expandedViewManager, + taskViewFactory, + positioner, stackView, layerView, iconFactory, @@ -912,9 +912,8 @@ public class Bubble implements BubbleViewProvider { if (uid != -1) { intent.putExtra(Settings.EXTRA_APP_UID, uid); } - intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 0aa89598cd10..5c6f73f0a4a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -113,6 +113,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.taskview.TaskView; +import com.android.wm.shell.taskview.TaskViewTaskController; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.Transitions; @@ -190,6 +191,8 @@ public class BubbleController implements ConfigurationChangeListener, private final ShellCommandHandler mShellCommandHandler; private final IWindowManager mWmService; private final BubbleProperties mBubbleProperties; + private final BubbleTaskViewFactory mBubbleTaskViewFactory; + private final BubbleExpandedViewManager mExpandedViewManager; // Used to post to main UI thread private final ShellExecutor mMainExecutor; @@ -333,6 +336,16 @@ public class BubbleController implements ConfigurationChangeListener, mWmService = wmService; mBubbleProperties = bubbleProperties; shellInit.addInitCallback(this::onInit, this); + mBubbleTaskViewFactory = new BubbleTaskViewFactory() { + @Override + public BubbleTaskView create() { + TaskViewTaskController taskViewTaskController = new TaskViewTaskController( + context, organizer, taskViewTransitions, syncQueue); + TaskView taskView = new TaskView(context, taskViewTaskController); + return new BubbleTaskView(taskView, mainExecutor); + } + }; + mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this); } private void registerOneHandedState(OneHandedController oneHanded) { @@ -802,7 +815,13 @@ public class BubbleController implements ConfigurationChangeListener, try { mAddedToWindowManager = true; registerBroadcastReceiver(); - mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar()); + if (isShowingAsBubbleBar()) { + mBubbleData.getOverflow().initializeForBubbleBar( + mExpandedViewManager, mBubblePositioner); + } else { + mBubbleData.getOverflow().initialize( + mExpandedViewManager, mStackView, mBubblePositioner); + } // (TODO: b/273314541) some duplication in the inset listener if (isShowingAsBubbleBar()) { mWindowManager.addView(mLayerView, mWmLayoutParams); @@ -984,7 +1003,9 @@ public class BubbleController implements ConfigurationChangeListener, for (Bubble b : mBubbleData.getBubbles()) { b.inflate(null /* callback */, mContext, - this, + mExpandedViewManager, + mBubbleTaskViewFactory, + mBubblePositioner, mStackView, mLayerView, mBubbleIconFactory, @@ -993,7 +1014,9 @@ public class BubbleController implements ConfigurationChangeListener, for (Bubble b : mBubbleData.getOverflowBubbles()) { b.inflate(null /* callback */, mContext, - this, + mExpandedViewManager, + mBubbleTaskViewFactory, + mBubblePositioner, mStackView, mLayerView, mBubbleIconFactory, @@ -1377,7 +1400,9 @@ public class BubbleController implements ConfigurationChangeListener, bubble.inflate( (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble), mContext, - this, + mExpandedViewManager, + mBubbleTaskViewFactory, + mBubblePositioner, mStackView, mLayerView, mBubbleIconFactory, @@ -1431,7 +1456,9 @@ public class BubbleController implements ConfigurationChangeListener, Bubble bubble = mBubbleData.getBubbles().get(i); bubble.inflate(callback, mContext, - this, + mExpandedViewManager, + mBubbleTaskViewFactory, + mBubblePositioner, mStackView, mLayerView, mBubbleIconFactory, @@ -1506,8 +1533,14 @@ public class BubbleController implements ConfigurationChangeListener, // Lazy init stack view when a bubble is created ensureBubbleViewsAndWindowCreated(); bubble.setInflateSynchronously(mInflateSynchronously); - bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade), - mContext, this, mStackView, mLayerView, + bubble.inflate( + b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade), + mContext, + mExpandedViewManager, + mBubbleTaskViewFactory, + mBubblePositioner, + mStackView, + mLayerView, mBubbleIconFactory, false /* skipInflation */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 6d3f0c32bcbb..6c2f925119f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -37,10 +37,8 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; -import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubbles.DismissReason; -import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.common.bubbles.RemovedBubble; @@ -180,7 +178,7 @@ public class BubbleData { * This interface reports changes to the state and appearance of bubbles which should be applied * as necessary to the UI. */ - interface Listener { + public interface Listener { /** Reports changes have have occurred as a result of the most recent operation. */ void applyUpdate(Update update); } @@ -419,8 +417,10 @@ public class BubbleData { /** * When this method is called it is expected that all info in the bubble has completed loading. - * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView, - * BubbleBarLayerView, BubbleIconFactory, boolean) + * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager, + * BubbleTaskViewFactory, BubblePositioner, BubbleStackView, + * com.android.wm.shell.bubbles.bar.BubbleBarLayerView, + * com.android.launcher3.icons.BubbleIconFactory, boolean) */ void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) { mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 088660e4e8dc..df9ba63b4cc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -184,7 +184,7 @@ public class BubbleExpandedView extends LinearLayout { private boolean mIsOverflow; private boolean mIsClipping; - private BubbleController mController; + private BubbleExpandedViewManager mManager; private BubbleStackView mStackView; private BubblePositioner mPositioner; @@ -261,7 +261,7 @@ public class BubbleExpandedView extends LinearLayout { // the bubble again so we'll just remove it. Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() + ", " + e.getMessage() + "; removing bubble"); - mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT); + mManager.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT); } }); mInitialized = true; @@ -281,7 +281,7 @@ public class BubbleExpandedView extends LinearLayout { if (mBubble != null && mBubble.isAppBubble()) { // Let the controller know sooner what the taskId is. - mController.setAppBubbleTaskId(mBubble.getKey(), mTaskId); + mManager.setAppBubbleTaskId(mBubble.getKey(), mTaskId); } // With the task org, the taskAppeared callback will only happen once the task has @@ -301,7 +301,7 @@ public class BubbleExpandedView extends LinearLayout { ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s", taskId, getBubbleKey()); if (mBubble != null) { - mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED); + mManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED); } if (mTaskView != null) { // Release the surface @@ -421,17 +421,20 @@ public class BubbleExpandedView extends LinearLayout { * Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need * to be called after view inflate. */ - void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow, + void initialize(BubbleExpandedViewManager expandedViewManager, + BubbleStackView stackView, + BubblePositioner positioner, + boolean isOverflow, @Nullable BubbleTaskView bubbleTaskView) { - mController = controller; + mManager = expandedViewManager; mStackView = stackView; mIsOverflow = isOverflow; - mPositioner = mController.getPositioner(); + mPositioner = positioner; if (mIsOverflow) { mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate( R.layout.bubble_overflow_container, null /* root */); - mOverflowView.setBubbleController(mController); + mOverflowView.initialize(expandedViewManager, positioner); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); mExpandedViewContainer.addView(mOverflowView, lp); mExpandedViewContainer.setLayoutParams( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt new file mode 100644 index 000000000000..b0d3cc4a5d5c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles + +/** Manager interface for bubble expanded views. */ +interface BubbleExpandedViewManager { + + val overflowBubbles: List<Bubble> + fun setOverflowListener(listener: BubbleData.Listener) + fun collapseStack() + fun updateWindowFlagsForBackpress(intercept: Boolean) + fun promoteBubbleFromOverflow(bubble: Bubble) + fun removeBubble(key: String, reason: Int) + fun dismissBubble(bubble: Bubble, reason: Int) + fun setAppBubbleTaskId(key: String, taskId: Int) + fun isStackExpanded(): Boolean + fun isShowingAsBubbleBar(): Boolean + + companion object { + /** + * Convenience function for creating a [BubbleExpandedViewManager] that delegates to the + * given `controller`. + */ + @JvmStatic + fun fromBubbleController(controller: BubbleController): BubbleExpandedViewManager { + return object : BubbleExpandedViewManager { + + override val overflowBubbles: List<Bubble> + get() = controller.overflowBubbles + + override fun setOverflowListener(listener: BubbleData.Listener) { + controller.setOverflowListener(listener) + } + + override fun collapseStack() { + controller.collapseStack() + } + + override fun updateWindowFlagsForBackpress(intercept: Boolean) { + controller.updateWindowFlagsForBackpress(intercept) + } + + override fun promoteBubbleFromOverflow(bubble: Bubble) { + controller.promoteBubbleFromOverflow(bubble) + } + + override fun removeBubble(key: String, reason: Int) { + controller.removeBubble(key, reason) + } + + override fun dismissBubble(bubble: Bubble, reason: Int) { + controller.dismissBubble(bubble, reason) + } + + override fun setAppBubbleTaskId(key: String, taskId: Int) { + controller.setAppBubbleTaskId(key, taskId) + } + + override fun isStackExpanded(): Boolean = controller.isStackExpanded + + override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index e5d9acedc903..f32974e1765d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -56,19 +56,32 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl } /** Call before use and again if cleanUpExpandedState was called. */ - fun initialize(controller: BubbleController, forBubbleBar: Boolean) { - if (forBubbleBar) { - createBubbleBarExpandedView() - .initialize(controller, /* isOverflow= */ true, /* bubbleTaskView= */ null) - } else { - createExpandedView() + fun initialize( + expandedViewManager: BubbleExpandedViewManager, + stackView: BubbleStackView, + positioner: BubblePositioner + ) { + createExpandedView() .initialize( - controller, - controller.stackView, + expandedViewManager, + stackView, + positioner, /* isOverflow= */ true, /* bubbleTaskView= */ null ) - } + } + + fun initializeForBubbleBar( + expandedViewManager: BubbleExpandedViewManager, + positioner: BubblePositioner + ) { + createBubbleBarExpandedView() + .initialize( + expandedViewManager, + positioner, + /* isOverflow= */ true, + /* bubbleTaskView= */ null + ) } fun cleanUpExpandedState() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java index 70cdc82c47ad..b06de4f4002c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java @@ -62,7 +62,8 @@ public class BubbleOverflowContainerView extends LinearLayout { private ImageView mEmptyStateImage; private int mHorizontalMargin; private int mVerticalMargin; - private BubbleController mController; + private BubbleExpandedViewManager mExpandedViewManager; + private BubblePositioner mPositioner; private BubbleOverflowAdapter mAdapter; private RecyclerView mRecyclerView; private List<Bubble> mOverflowBubbles = new ArrayList<>(); @@ -70,7 +71,7 @@ public class BubbleOverflowContainerView extends LinearLayout { private View.OnKeyListener mKeyListener = (view, i, keyEvent) -> { if (keyEvent.getAction() == KeyEvent.ACTION_UP && keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) { - mController.collapseStack(); + mExpandedViewManager.collapseStack(); return true; } return false; @@ -126,8 +127,11 @@ public class BubbleOverflowContainerView extends LinearLayout { setFocusableInTouchMode(true); } - public void setBubbleController(BubbleController controller) { - mController = controller; + /** Initializes the view. Must be called after creation. */ + public void initialize(BubbleExpandedViewManager expandedViewManager, + BubblePositioner positioner) { + mExpandedViewManager = expandedViewManager; + mPositioner = positioner; } public void show() { @@ -149,9 +153,9 @@ public class BubbleOverflowContainerView extends LinearLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (mController != null) { + if (mExpandedViewManager != null) { // For the overflow to get key events (e.g. back press) we need to adjust the flags - mController.updateWindowFlagsForBackpress(true); + mExpandedViewManager.updateWindowFlagsForBackpress(true); } setOnKeyListener(mKeyListener); } @@ -159,8 +163,8 @@ public class BubbleOverflowContainerView extends LinearLayout { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (mController != null) { - mController.updateWindowFlagsForBackpress(false); + if (mExpandedViewManager != null) { + mExpandedViewManager.updateWindowFlagsForBackpress(false); } setOnKeyListener(null); } @@ -177,15 +181,15 @@ public class BubbleOverflowContainerView extends LinearLayout { mRecyclerView.addItemDecoration(new OverflowItemDecoration()); } mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles, - mController::promoteBubbleFromOverflow, - mController.getPositioner()); + mExpandedViewManager::promoteBubbleFromOverflow, + mPositioner); mRecyclerView.setAdapter(mAdapter); mOverflowBubbles.clear(); - mOverflowBubbles.addAll(mController.getOverflowBubbles()); + mOverflowBubbles.addAll(mExpandedViewManager.getOverflowBubbles()); mAdapter.notifyDataSetChanged(); - mController.setOverflowListener(mDataListener); + mExpandedViewManager.setOverflowListener(mDataListener); updateEmptyStateVisibility(); updateTheme(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt new file mode 100644 index 000000000000..230626f49c51 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles + +/** Factory for creating [BubbleTaskView]s. */ +fun interface BubbleTaskViewFactory { + /** Creates a new instance of [BubbleTaskView]. */ + fun create(): BubbleTaskView +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 530ec5a6c9fe..21b70b8e32da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -62,7 +62,7 @@ public class BubbleTaskViewHelper { } private final Context mContext; - private final BubbleController mController; + private final BubbleExpandedViewManager mExpandedViewManager; private final BubbleTaskViewHelper.Listener mListener; private final View mParentView; @@ -142,7 +142,8 @@ public class BubbleTaskViewHelper { // the bubble again so we'll just remove it. Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() + ", " + e.getMessage() + "; removing bubble"); - mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT); + mExpandedViewManager.removeBubble( + getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT); } mInitialized = true; }); @@ -175,7 +176,7 @@ public class BubbleTaskViewHelper { ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s", taskId, getBubbleKey()); if (mBubble != null) { - mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED); + mExpandedViewManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED); } if (mTaskView != null) { mTaskView.release(); @@ -186,19 +187,19 @@ public class BubbleTaskViewHelper { @Override public void onBackPressedOnTaskRoot(int taskId) { - if (mTaskId == taskId && mController.isStackExpanded()) { + if (mTaskId == taskId && mExpandedViewManager.isStackExpanded()) { mListener.onBackPressed(); } } }; public BubbleTaskViewHelper(Context context, - BubbleController controller, + BubbleExpandedViewManager expandedViewManager, BubbleTaskViewHelper.Listener listener, BubbleTaskView bubbleTaskView, View parent) { mContext = context; - mController = controller; + mExpandedViewManager = expandedViewManager; mListener = listener; mParentView = parent; mTaskView = bubbleTaskView.getTaskView(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 5fc10a9f6b69..69119cf4338e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -70,7 +70,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask private Bubble mBubble; private WeakReference<Context> mContext; - private WeakReference<BubbleController> mController; + private WeakReference<BubbleExpandedViewManager> mExpandedViewManager; + private WeakReference<BubbleTaskViewFactory> mTaskViewFactory; + private WeakReference<BubblePositioner> mPositioner; private WeakReference<BubbleStackView> mStackView; private WeakReference<BubbleBarLayerView> mLayerView; private BubbleIconFactory mIconFactory; @@ -84,7 +86,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask */ BubbleViewInfoTask(Bubble b, Context context, - BubbleController controller, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, @Nullable BubbleStackView stackView, @Nullable BubbleBarLayerView layerView, BubbleIconFactory factory, @@ -93,7 +97,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Executor mainExecutor) { mBubble = b; mContext = new WeakReference<>(context); - mController = new WeakReference<>(controller); + mExpandedViewManager = new WeakReference<>(expandedViewManager); + mTaskViewFactory = new WeakReference<>(taskViewFactory); + mPositioner = new WeakReference<>(positioner); mStackView = new WeakReference<>(stackView); mLayerView = new WeakReference<>(layerView); mIconFactory = factory; @@ -109,11 +115,13 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask return null; } if (mLayerView.get() != null) { - return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(), - mLayerView.get(), mIconFactory, mBubble, mSkipInflation); + return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(), + mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory, + mBubble, mSkipInflation); } else { - return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(), - mIconFactory, mBubble, mSkipInflation); + return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(), + mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory, + mBubble, mSkipInflation); } } @@ -135,7 +143,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask } private boolean verifyState() { - if (mController.get().isShowingAsBubbleBar()) { + if (mExpandedViewManager.get().isShowingAsBubbleBar()) { return mLayerView.get() != null; } else { return mStackView.get() != null; @@ -167,18 +175,23 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Bitmap badgeBitmap; @Nullable - public static BubbleViewInfo populateForBubbleBar(Context c, BubbleController controller, - BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b, + public static BubbleViewInfo populateForBubbleBar(Context c, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, + BubbleBarLayerView layerView, + BubbleIconFactory iconFactory, + Bubble b, boolean skipInflation) { BubbleViewInfo info = new BubbleViewInfo(); if (!skipInflation && !b.isInflated()) { - BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(c, controller); + BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory); LayoutInflater inflater = LayoutInflater.from(c); info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); info.bubbleBarExpandedView.initialize( - controller, false /* isOverflow */, bubbleTaskView); + expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView); } if (!populateCommonInfo(info, c, b, iconFactory)) { @@ -191,8 +204,13 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @VisibleForTesting @Nullable - public static BubbleViewInfo populate(Context c, BubbleController controller, - BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b, + public static BubbleViewInfo populate(Context c, + BubbleExpandedViewManager expandedViewManager, + BubbleTaskViewFactory taskViewFactory, + BubblePositioner positioner, + BubbleStackView stackView, + BubbleIconFactory iconFactory, + Bubble b, boolean skipInflation) { BubbleViewInfo info = new BubbleViewInfo(); @@ -201,13 +219,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask LayoutInflater inflater = LayoutInflater.from(c); info.imageView = (BadgedImageView) inflater.inflate( R.layout.bubble_view, stackView, false /* attachToRoot */); - info.imageView.initialize(controller.getPositioner()); + info.imageView.initialize(positioner); - BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(c, controller); + BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory); info.expandedView = (BubbleExpandedView) inflater.inflate( R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); info.expandedView.initialize( - controller, stackView, false /* isOverflow */, bubbleTaskView); + expandedViewManager, stackView, positioner, false /* isOverflow */, + bubbleTaskView); } if (!populateCommonInfo(info, c, b, iconFactory)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 73a9cf456a6e..ebb8e3e2d207 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -36,8 +36,9 @@ import android.widget.FrameLayout; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; -import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubbleExpandedViewManager; import com.android.wm.shell.bubbles.BubbleOverflowContainerView; +import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleTaskView; import com.android.wm.shell.bubbles.BubbleTaskViewHelper; import com.android.wm.shell.bubbles.Bubbles; @@ -45,11 +46,7 @@ import com.android.wm.shell.taskview.TaskView; import java.util.function.Supplier; -/** - * Expanded view of a bubble when it's part of the bubble bar. - * - * {@link BubbleController#isShowingAsBubbleBar()} - */ +/** Expanded view of a bubble when it's part of the bubble bar. */ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener { /** * The expanded view listener notifying the {@link BubbleBarLayerView} about the internal @@ -67,7 +64,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private static final String TAG = BubbleBarExpandedView.class.getSimpleName(); private static final int INVALID_TASK_ID = -1; - private BubbleController mController; + private BubbleExpandedViewManager mManager; private boolean mIsOverflow; private BubbleTaskViewHelper mBubbleTaskViewHelper; private BubbleBarMenuViewController mMenuViewController; @@ -133,20 +130,22 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView mMenuViewController.hideMenu(false /* animated */); } - /** Set the BubbleController on the view, must be called before doing anything else. */ - public void initialize(BubbleController controller, boolean isOverflow, + /** Initializes the view, must be called before doing anything else. */ + public void initialize(BubbleExpandedViewManager expandedViewManager, + BubblePositioner positioner, + boolean isOverflow, @Nullable BubbleTaskView bubbleTaskView) { - mController = controller; + mManager = expandedViewManager; mIsOverflow = isOverflow; if (mIsOverflow) { mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate( R.layout.bubble_overflow_container, null /* root */); - mOverflowView.setBubbleController(mController); + mOverflowView.initialize(expandedViewManager, positioner); addView(mOverflowView); } else { mTaskView = bubbleTaskView.getTaskView(); - mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController, + mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager, /* listener= */ this, bubbleTaskView, /* viewParent= */ this); if (mTaskView.getParent() != null) { @@ -178,13 +177,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override public void onOpenAppSettings(Bubble bubble) { - mController.collapseStack(); + mManager.collapseStack(); mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser()); } @Override public void onDismissBubble(Bubble bubble) { - mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED); + mManager.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED); } }); mHandleView.setOnClickListener(view -> { @@ -279,7 +278,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView if (mMenuViewController.isMenuVisible()) { mMenuViewController.hideMenu(/* animated = */ true); } else { - mController.collapseStack(); + mManager.collapseStack(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 8b6c7b663f82..68d26da2002f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -140,7 +140,7 @@ public class PipBoundsState { // spec takes the aspect ratio of the bounds into account, so both width and height // scale by the same factor. addPipExclusionBoundsChangeCallback((bounds) -> { - mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f); + updateBoundsScale(); }); } @@ -152,6 +152,11 @@ public class PipBoundsState { mSizeSpecSource.onConfigurationChanged(); } + /** Update the bounds scale percentage value. */ + public void updateBoundsScale() { + mBoundsScale = Math.min((float) mBounds.width() / mMaxSize.x, 1.0f); + } + private void reloadResources() { mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 896ca9647720..e018ecc0f7e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -286,12 +286,6 @@ public class PipTransition extends PipTransitionController { // For transition that we don't animate, but contains the PIP leash, we need to update the // PIP surface, otherwise it will be reset after the transition. if (currentPipTaskChange != null) { - // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is - // changing the *finish*Transaction, we need to use the end bounds. This will also - // make sure that the fade-in animation (below) uses the end bounds as well. - if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) { - mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds()); - } updatePipForUnhandledTransition(currentPipTaskChange, startTransaction, finishTransaction); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index c5a01025dcdd..05d4f53986b8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -244,6 +244,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb // The same rotation may have been set by auto PiP-able or fixed rotation. So notify // the change with fromRotation=false to apply the rotated destination bounds from // PipTaskOrganizer#onMovementBoundsChanged. + // We need to update the bounds scale in case this was from fixed rotation, as the + // current proportion was computed using the previous orientation max size and is wrong. + mPipBoundsState.updateBoundsScale(); updateMovementBounds(null, false /* fromRotation */, false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t); return; @@ -797,21 +800,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm.getMovementBounds(postChangeBounds), mPipBoundsState.getStashedState()); - // Scale PiP on density dpi change, so it appears to be the same size physically. - final boolean densityDpiChanged = - mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0 - && (mPipDisplayLayoutState.getDisplayLayout().densityDpi() - != layout.densityDpi()); - if (densityDpiChanged) { - final float scale = (float) layout.densityDpi() - / mPipDisplayLayoutState.getDisplayLayout().densityDpi(); - postChangeBounds.set(0, 0, - (int) (postChangeBounds.width() * scale), - (int) (postChangeBounds.height() * scale)); - } - updateDisplayLayout.run(); + // Resize the PiP bounds to be at the same scale relative to the new size spec. For + // example, if PiP was resized to 90% of the maximum size on the previous layout, + // make sure it is 90% of the new maximum size spec. + postChangeBounds.set(0, 0, + (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()), + (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale())); + // Calculate the PiP bounds in the new orientation based on same fraction along the // rotated movement bounds. final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds( @@ -827,6 +824,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.setHasUserResizedPip(true); mTouchHandler.setUserResizeBounds(postChangeBounds); + final boolean densityDpiChanged = + mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0 + && (mPipDisplayLayoutState.getDisplayLayout().densityDpi() + != layout.densityDpi()); if (densityDpiChanged) { // Using PipMotionHelper#movePip directly here may cause race condition since // the app content in PiP mode may or may not be updated for the new density dpi. @@ -1146,6 +1147,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Update the display layout mPipDisplayLayoutState.rotateTo(toRotation); + mTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio()); + + postChangeStackBounds.set(0, 0, + (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()), + (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale())); // Calculate the stack bounds in the new orientation based on same fraction along the // rotated movement bounds. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index f175775ce8b2..5f9195a37c1b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -15,19 +15,13 @@ */ package com.android.wm.shell.pip.phone; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; -import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.Region; import android.hardware.input.InputManager; import android.os.Looper; import android.view.BatchedInputEventReceiver; @@ -41,7 +35,6 @@ import android.view.ViewConfiguration; import androidx.annotation.VisibleForTesting; -import com.android.internal.policy.TaskResizingAlgorithm; import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; @@ -53,7 +46,6 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import java.io.PrintWriter; import java.util.function.Consumer; -import java.util.function.Function; /** * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to @@ -77,7 +69,6 @@ public class PipResizeGestureHandler { private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; private final int mDisplayId; private final ShellExecutor mMainExecutor; - private final Region mTmpRegion = new Region(); private final PointF mDownPoint = new PointF(); private final PointF mDownSecondPoint = new PointF(); @@ -88,24 +79,15 @@ public class PipResizeGestureHandler { private final Rect mLastResizeBounds = new Rect(); private final Rect mUserResizeBounds = new Rect(); private final Rect mDownBounds = new Rect(); - private final Rect mDragCornerSize = new Rect(); - private final Rect mTmpTopLeftCorner = new Rect(); - private final Rect mTmpTopRightCorner = new Rect(); - private final Rect mTmpBottomLeftCorner = new Rect(); - private final Rect mTmpBottomRightCorner = new Rect(); - private final Rect mDisplayBounds = new Rect(); - private final Function<Rect, Rect> mMovementBoundsSupplier; private final Runnable mUpdateMovementBoundsRunnable; private final Consumer<Rect> mUpdateResizeBoundsCallback; - private int mDelta; private float mTouchSlop; private boolean mAllowGesture; private boolean mIsAttached; private boolean mIsEnabled; private boolean mEnablePinchResize; - private boolean mEnableDragCornerResize; private boolean mIsSysUiStateValid; private boolean mThresholdCrossed; private boolean mOngoingPinchToResize = false; @@ -123,7 +105,7 @@ public class PipResizeGestureHandler { PipBoundsState pipBoundsState, PipMotionHelper motionHelper, PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer, PipDismissTargetHandler pipDismissTargetHandler, - Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable, + Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, ShellExecutor mainExecutor) { mContext = context; @@ -135,7 +117,6 @@ public class PipResizeGestureHandler { mPipTouchState = pipTouchState; mPipTaskOrganizer = pipTaskOrganizer; mPipDismissTargetHandler = pipDismissTargetHandler; - mMovementBoundsSupplier = movementBoundsSupplier; mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; @@ -171,20 +152,9 @@ public class PipResizeGestureHandler { } private void reloadResources() { - final Resources res = mContext.getResources(); - mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size); - mEnableDragCornerResize = res.getBoolean(R.bool.config_pipEnableDragCornerResize); mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); } - private void resetDragCorners() { - mDragCornerSize.set(0, 0, mDelta, mDelta); - mTmpTopLeftCorner.set(mDragCornerSize); - mTmpTopRightCorner.set(mDragCornerSize); - mTmpBottomLeftCorner.set(mDragCornerSize); - mTmpBottomRightCorner.set(mDragCornerSize); - } - private void disposeInputChannel() { if (mInputEventReceiver != null) { mInputEventReceiver.dispose(); @@ -232,7 +202,7 @@ public class PipResizeGestureHandler { @VisibleForTesting void onInputEvent(InputEvent ev) { - if (!mEnableDragCornerResize && !mEnablePinchResize) { + if (!mEnablePinchResize) { // No need to handle anything if neither form of resizing is enabled. return; } @@ -260,8 +230,6 @@ public class PipResizeGestureHandler { if (mEnablePinchResize && mOngoingPinchToResize) { onPinchResize(mv); - } else if (mEnableDragCornerResize) { - onDragCornerResize(mv); } } } @@ -273,48 +241,6 @@ public class PipResizeGestureHandler { return mCtrlType != CTRL_NONE || mOngoingPinchToResize; } - /** - * Check whether the current x,y coordinate is within the region in which drag-resize should - * start. - * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which - * overlaps with the PIP window while the rest goes outside of the PIP window. - * _ _ _ _ - * |_|_|_________|_|_| - * |_|_| |_|_| - * | PIP | - * | WINDOW | - * _|_ _|_ - * |_|_|_________|_|_| - * |_|_| |_|_| - */ - public boolean isWithinDragResizeRegion(int x, int y) { - if (!mEnableDragCornerResize) { - return false; - } - - final Rect currentPipBounds = mPipBoundsState.getBounds(); - if (currentPipBounds == null) { - return false; - } - resetDragCorners(); - mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2, - currentPipBounds.top - mDelta / 2); - mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2, - currentPipBounds.top - mDelta / 2); - mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2, - currentPipBounds.bottom - mDelta / 2); - mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2, - currentPipBounds.bottom - mDelta / 2); - - mTmpRegion.setEmpty(); - mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION); - mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION); - mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION); - mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION); - - return mTmpRegion.contains(x, y); - } - public boolean isUsingPinchToZoom() { return mEnablePinchResize; } @@ -325,62 +251,17 @@ public class PipResizeGestureHandler { public boolean willStartResizeGesture(MotionEvent ev) { if (isInValidSysUiState()) { - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) { - return true; - } - break; - - case MotionEvent.ACTION_POINTER_DOWN: - if (mEnablePinchResize && ev.getPointerCount() == 2) { - onPinchResize(ev); - mOngoingPinchToResize = mAllowGesture; - return mAllowGesture; - } - break; - - default: - break; + if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { + if (mEnablePinchResize && ev.getPointerCount() == 2) { + onPinchResize(ev); + mOngoingPinchToResize = mAllowGesture; + return mAllowGesture; + } } } return false; } - private void setCtrlType(int x, int y) { - final Rect currentPipBounds = mPipBoundsState.getBounds(); - - Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds); - - mDisplayBounds.set(movementBounds.left, - movementBounds.top, - movementBounds.right + currentPipBounds.width(), - movementBounds.bottom + currentPipBounds.height()); - - if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top - && currentPipBounds.left != mDisplayBounds.left) { - mCtrlType |= CTRL_LEFT; - mCtrlType |= CTRL_TOP; - } - if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top - && currentPipBounds.right != mDisplayBounds.right) { - mCtrlType |= CTRL_RIGHT; - mCtrlType |= CTRL_TOP; - } - if (mTmpBottomRightCorner.contains(x, y) - && currentPipBounds.bottom != mDisplayBounds.bottom - && currentPipBounds.right != mDisplayBounds.right) { - mCtrlType |= CTRL_RIGHT; - mCtrlType |= CTRL_BOTTOM; - } - if (mTmpBottomLeftCorner.contains(x, y) - && currentPipBounds.bottom != mDisplayBounds.bottom - && currentPipBounds.left != mDisplayBounds.left) { - mCtrlType |= CTRL_LEFT; - mCtrlType |= CTRL_BOTTOM; - } - } - private boolean isInValidSysUiState() { return mIsSysUiStateValid; } @@ -457,59 +338,6 @@ public class PipResizeGestureHandler { } } - private void onDragCornerResize(MotionEvent ev) { - int action = ev.getActionMasked(); - float x = ev.getX(); - float y = ev.getY() - mOhmOffset; - if (action == MotionEvent.ACTION_DOWN) { - mLastResizeBounds.setEmpty(); - mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y); - if (mAllowGesture) { - setCtrlType((int) x, (int) y); - mDownPoint.set(x, y); - mDownBounds.set(mPipBoundsState.getBounds()); - } - } else if (mAllowGesture) { - switch (action) { - case MotionEvent.ACTION_POINTER_DOWN: - // We do not support multi touch for resizing via drag - mAllowGesture = false; - break; - case MotionEvent.ACTION_MOVE: - // Capture inputs - if (!mThresholdCrossed - && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) { - mThresholdCrossed = true; - // Reset the down to begin resizing from this point - mDownPoint.set(x, y); - mInputMonitor.pilferPointers(); - } - if (mThresholdCrossed) { - if (mPhonePipMenuController.isMenuVisible()) { - mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE, - false /* resize */); - } - final Rect currentPipBounds = mPipBoundsState.getBounds(); - mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y, - mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, - mMinSize.y, mMaxSize, true, - mDownBounds.width() > mDownBounds.height())); - mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds, - mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */, - true /* useCurrentSize */); - mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, - null); - mPipBoundsState.setHasUserResizedPip(true); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - finishResize(); - break; - } - } - } - private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) { final int leftEdge = bounds.left; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 81705e20a1df..11c356d94ee1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -212,7 +212,7 @@ public class PipTouchHandler { mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler, - this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger, + this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor); mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState, mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(), diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index 8207b85c3e0c..07cd68261083 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -86,6 +86,8 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli .withNavOrTaskBarVisible() .withStatusBarVisible() .waitForAndVerify() + + pipApp.tapPipToShowMenu(wmHelper) } } @@ -194,6 +196,16 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli } } + @Postsubmit + @Test + fun menuOverlayMatchesTaskSurface() { + flicker.assertLayersEnd { + val pipAppRegion = visibleRegion(pipApp) + val pipMenuRegion = visibleRegion(ComponentNameMatcher.PIP_MENU_OVERLAY) + pipAppRegion.coversExactly(pipMenuRegion.region) + } + } + /** {@inheritDoc} */ @FlakyTest(bugId = 267424412) @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java index f5b0174642d1..094af9652ea3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java @@ -18,7 +18,6 @@ package com.android.wm.shell.bubbles; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; @@ -45,18 +44,22 @@ public class BubbleOverflowTest extends ShellTestCase { private TestableBubblePositioner mPositioner; private BubbleOverflow mOverflow; + private BubbleExpandedViewManager mExpandedViewManager; @Mock private BubbleController mBubbleController; + @Mock + private BubbleStackView mBubbleStackView; @Before - public void setUp() throws Exception { + public void setUp() { MockitoAnnotations.initMocks(this); + mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(mBubbleController); mPositioner = new TestableBubblePositioner(mContext, mContext.getSystemService(WindowManager.class)); when(mBubbleController.getPositioner()).thenReturn(mPositioner); - when(mBubbleController.getStackView()).thenReturn(mock(BubbleStackView.class)); + when(mBubbleController.getStackView()).thenReturn(mBubbleStackView); mOverflow = new BubbleOverflow(mContext, mPositioner); } @@ -65,7 +68,7 @@ public class BubbleOverflowTest extends ShellTestCase { public void test_initialize_forStack() { assertThat(mOverflow.getExpandedView()).isNull(); - mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false); + mOverflow.initialize(mExpandedViewManager, mBubbleStackView, mPositioner); assertThat(mOverflow.getExpandedView()).isNotNull(); assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY); @@ -74,7 +77,7 @@ public class BubbleOverflowTest extends ShellTestCase { @Test public void test_initialize_forBubbleBar() { - mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true); + mOverflow.initializeForBubbleBar(mExpandedViewManager, mPositioner); assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull(); assertThat(mOverflow.getExpandedView()).isNull(); @@ -82,11 +85,10 @@ public class BubbleOverflowTest extends ShellTestCase { @Test public void test_cleanUpExpandedState() { - mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false); + mOverflow.initialize(mExpandedViewManager, mBubbleStackView, mPositioner); assertThat(mOverflow.getExpandedView()).isNotNull(); mOverflow.cleanUpExpandedState(); assertThat(mOverflow.getExpandedView()).isNull(); } - } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt index 1668e3712462..ae39fbcb4eed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt @@ -44,6 +44,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewTransitions import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat @@ -55,6 +56,7 @@ import org.mockito.kotlin.doThrow import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +import java.util.concurrent.Executor /** Tests for loading / inflating views & icons for a bubble. */ @SmallTest @@ -65,11 +67,16 @@ class BubbleViewInfoTest : ShellTestCase() { private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener private lateinit var iconFactory: BubbleIconFactory private lateinit var bubble: Bubble - private lateinit var bubbleController: BubbleController private lateinit var mainExecutor: ShellExecutor private lateinit var bubbleStackView: BubbleStackView private lateinit var bubbleBarLayerView: BubbleBarLayerView + private lateinit var bubblePositioner: BubblePositioner + private lateinit var expandedViewManager: BubbleExpandedViewManager + + private val bubbleTaskViewFactory = BubbleTaskViewFactory { + BubbleTaskView(mock<TaskView>(), mock<Executor>()) + } @Before fun setup() { @@ -88,7 +95,7 @@ class BubbleViewInfoTest : ShellTestCase() { val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor) - val bubblePositioner = BubblePositioner(context, windowManager) + bubblePositioner = BubblePositioner(context, windowManager) val bubbleData = BubbleData( context, @@ -143,6 +150,7 @@ class BubbleViewInfoTest : ShellTestCase() { bubbleController, mainExecutor ) + expandedViewManager = BubbleExpandedViewManager.fromBubbleController(bubbleController) bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData) } @@ -152,7 +160,9 @@ class BubbleViewInfoTest : ShellTestCase() { val info = BubbleViewInfoTask.BubbleViewInfo.populate( context, - bubbleController, + expandedViewManager, + bubbleTaskViewFactory, + bubblePositioner, bubbleStackView, iconFactory, bubble, @@ -178,7 +188,9 @@ class BubbleViewInfoTest : ShellTestCase() { val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar( context, - bubbleController, + expandedViewManager, + bubbleTaskViewFactory, + bubblePositioner, bubbleBarLayerView, iconFactory, bubble, @@ -212,7 +224,9 @@ class BubbleViewInfoTest : ShellTestCase() { val info = BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar( context, - bubbleController, + expandedViewManager, + bubbleTaskViewFactory, + bubblePositioner, bubbleBarLayerView, iconFactory, bubble, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 9719ba89b4bb..cc726cb0adcf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -121,7 +121,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm, mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer, mPipDismissTargetHandler, - (Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController, + () -> {}, mPipUiEventLogger, mPhonePipMenuController, mMainExecutor) { @Override public void pilferPointers() { diff --git a/libs/hwui/apex/android_paint.cpp b/libs/hwui/apex/android_paint.cpp index cc79cba5e19c..5e73e7628568 100644 --- a/libs/hwui/apex/android_paint.cpp +++ b/libs/hwui/apex/android_paint.cpp @@ -14,12 +14,13 @@ * limitations under the License. */ -#include "android/graphics/paint.h" +#include <SkBlendMode.h> +#include <SkImageFilter.h> +#include <hwui/Paint.h> #include "TypeCast.h" - -#include <hwui/Paint.h> -#include <SkBlendMode.h> +#include "android/graphics/paint.h" +#include "include/effects/SkImageFilters.h" using namespace android; @@ -43,6 +44,22 @@ static SkBlendMode convertBlendMode(ABlendMode blendMode) { } } +static sk_sp<SkImageFilter> convertImageFilter(AImageFilter imageFilter) { + switch (imageFilter) { + case AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON: + // Material Elevation Level 1 Drop Shadow. + sk_sp<SkImageFilter> key_shadow = SkImageFilters::DropShadow( + 0.0f, 1.0f, 2.0f, 2.0f, SkColorSetARGB(0x4D, 0x00, 0x00, 0x00), nullptr); + sk_sp<SkImageFilter> ambient_shadow = SkImageFilters::DropShadow( + 0.0f, 1.0f, 3.0f, 3.0f, SkColorSetARGB(0x26, 0x00, 0x00, 0x00), nullptr); + return SkImageFilters::Compose(ambient_shadow, key_shadow); + } +} + void APaint_setBlendMode(APaint* paint, ABlendMode blendMode) { TypeCast::toPaint(paint)->setBlendMode(convertBlendMode(blendMode)); } + +void APaint_setImageFilter(APaint* paint, AImageFilter imageFilter) { + TypeCast::toPaint(paint)->setImageFilter(convertImageFilter(imageFilter)); +} diff --git a/libs/hwui/apex/include/android/graphics/paint.h b/libs/hwui/apex/include/android/graphics/paint.h index 058db8d37619..36b7575d585d 100644 --- a/libs/hwui/apex/include/android/graphics/paint.h +++ b/libs/hwui/apex/include/android/graphics/paint.h @@ -26,6 +26,14 @@ __BEGIN_DECLS */ typedef struct APaint APaint; +/** + * Predefined Image filter type. + */ +enum AImageFilter { + /** Drop shadow image filter for PointerIcons. */ + AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON = 0, +}; + /** Bitmap pixel format. */ enum ABlendMode { /** replaces destination with zero: fully transparent */ @@ -42,6 +50,8 @@ ANDROID_API void APaint_destroyPaint(APaint* paint); ANDROID_API void APaint_setBlendMode(APaint* paint, ABlendMode blendMode); +ANDROID_API void APaint_setImageFilter(APaint* paint, AImageFilter imageFilter); + __END_DECLS #ifdef __cplusplus @@ -54,6 +64,10 @@ namespace graphics { void setBlendMode(ABlendMode blendMode) { APaint_setBlendMode(mPaint, blendMode); } + void setImageFilter(AImageFilter imageFilter) { + APaint_setImageFilter(mPaint, imageFilter); + } + const APaint& get() const { return *mPaint; } private: diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt index fdb237387098..d03ceb471d6c 100644 --- a/libs/hwui/libhwui.map.txt +++ b/libs/hwui/libhwui.map.txt @@ -32,6 +32,7 @@ LIBHWUI { # platform-only /* HWUI isn't current a module, so all of these are st APaint_createPaint; APaint_destroyPaint; APaint_setBlendMode; + APaint_setImageFilter; ARegionIterator_acquireIterator; ARegionIterator_releaseIterator; ARegionIterator_isComplex; diff --git a/libs/input/SpriteIcon.cpp b/libs/input/SpriteIcon.cpp index b7e51e22a214..59e36e4b0d1e 100644 --- a/libs/input/SpriteIcon.cpp +++ b/libs/input/SpriteIcon.cpp @@ -34,6 +34,9 @@ bool SpriteIcon::draw(sp<Surface> surface) const { graphics::Paint paint; paint.setBlendMode(ABLEND_MODE_SRC); + if (drawNativeDropShadow) { + paint.setImageFilter(AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON); + } graphics::Canvas canvas(outBuffer, (int32_t)surface->getBuffersDataSpace()); canvas.drawBitmap(bitmap, 0, 0, &paint); diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h index 5f085bbd2374..9e6cc81f0bb4 100644 --- a/libs/input/SpriteIcon.h +++ b/libs/input/SpriteIcon.h @@ -29,16 +29,22 @@ namespace android { struct SpriteIcon { inline SpriteIcon() : style(PointerIconStyle::TYPE_NULL), hotSpotX(0), hotSpotY(0) {} inline SpriteIcon(const graphics::Bitmap& bitmap, PointerIconStyle style, float hotSpotX, - float hotSpotY) - : bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) {} + float hotSpotY, bool drawNativeDropShadow) + : bitmap(bitmap), + style(style), + hotSpotX(hotSpotX), + hotSpotY(hotSpotY), + drawNativeDropShadow(drawNativeDropShadow) {} graphics::Bitmap bitmap; PointerIconStyle style; float hotSpotX; float hotSpotY; + bool drawNativeDropShadow; inline SpriteIcon copy() const { - return SpriteIcon(bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888), style, hotSpotX, hotSpotY); + return SpriteIcon(bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888), style, hotSpotX, hotSpotY, + drawNativeDropShadow); } inline void reset() { @@ -46,6 +52,7 @@ struct SpriteIcon { style = PointerIconStyle::TYPE_NULL; hotSpotX = 0; hotSpotY = 0; + drawNativeDropShadow = false; } inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e9ba779c1b13..8f3f82e06cf8 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -5513,6 +5513,26 @@ public class AudioManager { /** * @hide + * @return All currently registered audio policy mixes. + */ + @TestApi + @FlaggedApi(android.media.audiopolicy.Flags.FLAG_AUDIO_MIX_TEST_API) + @NonNull + public List<android.media.audiopolicy.AudioMix> getRegisteredPolicyMixes() { + if (!android.media.audiopolicy.Flags.audioMixTestApi()) { + return Collections.emptyList(); + } + + final IAudioService service = getService(); + try { + return service.getRegisteredPolicyMixes(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide * @return true if an AudioPolicy was previously registered */ @TestApi diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index f73be35fa130..293c561f166c 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1984,6 +1984,9 @@ public class AudioSystem public static native int registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register); /** @hide */ + public static native int getRegisteredPolicyMixes(@NonNull List<AudioMix> devices); + + /** @hide */ public static native int updatePolicyMixes( AudioMix[] mixes, AudioMixingRule[] updatedMixingRules); diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java index 40b0e3e03ef6..4f1a8ee5e728 100644 --- a/media/java/android/media/FadeManagerConfiguration.java +++ b/media/java/android/media/FadeManagerConfiguration.java @@ -18,6 +18,7 @@ package android.media; import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION; +import android.annotation.DurationMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -112,17 +113,11 @@ public final class FadeManagerConfiguration implements Parcelable { */ public static final int FADE_STATE_ENABLED_DEFAULT = 1; - /** - * Defines the enabled state with Automotive specific configurations - */ - public static final int FADE_STATE_ENABLED_AUTO = 2; - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = false, prefix = "FADE_STATE", value = { FADE_STATE_DISABLED, FADE_STATE_ENABLED_DEFAULT, - FADE_STATE_ENABLED_AUTO, }) public @interface FadeStateEnum {} @@ -143,7 +138,14 @@ public final class FadeManagerConfiguration implements Parcelable { * @see #getFadeOutDurationForAudioAttributes(AudioAttributes) * @see #getFadeInDurationForAudioAttributes(AudioAttributes) */ - public static final long DURATION_NOT_SET = 0; + public static final @DurationMillisLong long DURATION_NOT_SET = 0; + + /** Defines the default fade out duration */ + private static final @DurationMillisLong long DEFAULT_FADE_OUT_DURATION_MS = 2_000; + + /** Defines the default fade in duration */ + private static final @DurationMillisLong long DEFAULT_FADE_IN_DURATION_MS = 1_000; + /** Map of Usage to Fade volume shaper configs wrapper */ private final SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap; /** Map of AudioAttributes to Fade volume shaper configs wrapper */ @@ -161,14 +163,15 @@ public final class FadeManagerConfiguration implements Parcelable { /** fade state */ private final @FadeStateEnum int mFadeState; /** fade out duration from builder - used for creating default fade out volume shaper */ - private final long mFadeOutDurationMillis; + private final @DurationMillisLong long mFadeOutDurationMillis; /** fade in duration from builder - used for creating default fade in volume shaper */ - private final long mFadeInDurationMillis; + private final @DurationMillisLong long mFadeInDurationMillis; /** delay after which the offending players are faded back in */ - private final long mFadeInDelayForOffendersMillis; + private final @DurationMillisLong long mFadeInDelayForOffendersMillis; - private FadeManagerConfiguration(int fadeState, long fadeOutDurationMillis, - long fadeInDurationMillis, long offendersFadeInDelayMillis, + private FadeManagerConfiguration(int fadeState, @DurationMillisLong long fadeOutDurationMillis, + @DurationMillisLong long fadeInDurationMillis, + @DurationMillisLong long offendersFadeInDelayMillis, @NonNull SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap, @NonNull ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap, @NonNull IntArray fadeableUsages, @NonNull IntArray unfadeableContentTypes, @@ -196,8 +199,6 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Get the fade state - * - * @return one of the {@link FadeStateEnum} state */ @FadeStateEnum public int getFadeState() { @@ -207,7 +208,7 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Get the list of usages that can be faded * - * @return list of {@link android.media.AudioAttributes.AttributeUsage} that shall be faded + * @return list of {@link android.media.AudioAttributes usages} that shall be faded * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ @NonNull @@ -217,10 +218,10 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Get the list of {@link android.media.AudioPlaybackConfiguration.PlayerType player types} - * that cannot be faded + * Get the list of {@link android.media.AudioPlaybackConfiguration player types} that can be + * faded * - * @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType} + * @return list of {@link android.media.AudioPlaybackConfiguration player types} * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ @NonNull @@ -230,10 +231,9 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Get the list of {@link android.media.AudioAttributes.AttributeContentType content types} - * that cannot be faded + * Get the list of {@link android.media.AudioAttributes content types} that can be faded * - * @return list of {@link android.media.AudioAttributes.AttributeContentType} + * @return list of {@link android.media.AudioAttributes content types} * @throws IllegalStateExceptionif if the fade state is set to {@link #FADE_STATE_DISABLED} */ @NonNull @@ -267,15 +267,15 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Get the duration used to fade out players with - * {@link android.media.AudioAttributes.AttributeUsage} + * Get the duration used to fade out players with {@link android.media.AudioAttributes usage} * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} + * @param usage the {@link android.media.AudioAttributes usage} * @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise * @throws IllegalArgumentException if the usage is invalid * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ - public long getFadeOutDurationForUsage(int usage) { + @DurationMillisLong + public long getFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage) { ensureFadingIsEnabled(); validateUsage(usage); return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper( @@ -283,15 +283,15 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Get the duration used to fade in players with - * {@link android.media.AudioAttributes.AttributeUsage} + * Get the duration used to fade in players with {@link android.media.AudioAttributes usage} * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} + * @param usage the {@link android.media.AudioAttributes usage} * @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise * @throws IllegalArgumentException if the usage is invalid * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ - public long getFadeInDurationForUsage(int usage) { + @DurationMillisLong + public long getFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage) { ensureFadingIsEnabled(); validateUsage(usage); return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper( @@ -300,16 +300,17 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with - * {@link android.media.AudioAttributes.AttributeUsage} + * {@link android.media.AudioAttributes usage} * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} + * @param usage the {@link android.media.AudioAttributes usage} * @return {@link android.media.VolumeShaper.Configuration} if set for the usage or - * {@code null} otherwise + * {@code null} otherwise * @throws IllegalArgumentException if the usage is invalid * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ @Nullable - public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int usage) { + public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage( + @AudioAttributes.AttributeUsage int usage) { ensureFadingIsEnabled(); validateUsage(usage); return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage), @@ -318,16 +319,17 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Get the {@link android.media.VolumeShaper.Configuration} used to fade in players with - * {@link android.media.AudioAttributes.AttributeUsage} + * {@link android.media.AudioAttributes usage} * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of player + * @param usage the {@link android.media.AudioAttributes usage} * @return {@link android.media.VolumeShaper.Configuration} if set for the usage or - * {@code null} otherwise + * {@code null} otherwise * @throws IllegalArgumentException if the usage is invalid * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ @Nullable - public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int usage) { + public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage( + @AudioAttributes.AttributeUsage int usage) { ensureFadingIsEnabled(); validateUsage(usage); return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage), @@ -339,10 +341,11 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param audioAttributes {@link android.media.AudioAttributes} * @return duration in milliseconds if set for the audio attributes or - * {@link #DURATION_NOT_SET} otherwise + * {@link #DURATION_NOT_SET} otherwise * @throws NullPointerException if the audio attributes is {@code null} * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ + @DurationMillisLong public long getFadeOutDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) { ensureFadingIsEnabled(); return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper( @@ -354,10 +357,11 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param audioAttributes {@link android.media.AudioAttributes} * @return duration in milliseconds if set for the audio attributes or - * {@link #DURATION_NOT_SET} otherwise + * {@link #DURATION_NOT_SET} otherwise * @throws NullPointerException if the audio attributes is {@code null} * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ + @DurationMillisLong public long getFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) { ensureFadingIsEnabled(); return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper( @@ -370,7 +374,7 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param audioAttributes {@link android.media.AudioAttributes} * @return {@link android.media.VolumeShaper.Configuration} if set for the audio attribute or - * {@code null} otherwise + * {@code null} otherwise * @throws NullPointerException if the audio attributes is {@code null} * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ @@ -389,7 +393,7 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param audioAttributes {@link android.media.AudioAttributes} * @return {@link android.media.VolumeShaper.Configuration} used for fading in if set for the - * audio attribute or {@code null} otherwise + * audio attribute or {@code null} otherwise * @throws NullPointerException if the audio attributes is {@code null} * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED} */ @@ -407,7 +411,7 @@ public final class FadeManagerConfiguration implements Parcelable { * configurations are defined * * @return list of {@link android.media.AudioAttributes} with valid volume shaper configs or - * empty list if none set. + * empty list if none set. */ @NonNull public List<AudioAttributes> getAudioAttributesWithVolumeShaperConfigs() { @@ -417,8 +421,14 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Get the delay after which the offending players are faded back in * + * Players are categorized as offending if they do not honor audio focus state changes. For + * example - when an app loses audio focus, it is expected that the app stops any active + * player in favor of the app(s) that gained audio focus. However, if the app do not stop the + * audio playback, such players are termed as offenders. + * * @return delay in milliseconds */ + @DurationMillisLong public long getFadeInDelayForOffenders() { return mFadeInDelayForOffendersMillis; } @@ -435,8 +445,9 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Query if the usage is fadeable * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} - * @return {@code true} if usage is fadeable, {@code false} otherwise + * @param usage the {@link android.media.AudioAttributes usage} + * @return {@code true} if usage is fadeable, {@code false} when the fade state is set to + * {@link #FADE_STATE_DISABLED} or if the usage is not fadeable. */ public boolean isUsageFadeable(@AudioAttributes.AttributeUsage int usage) { if (!isFadeEnabled()) { @@ -448,9 +459,9 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Query if the content type is unfadeable * - * @param contentType the {@link android.media.AudioAttributes.AttributeContentType} + * @param contentType the {@link android.media.AudioAttributes content type} * @return {@code true} if content type is unfadeable or if fade state is set to - * {@link #FADE_STATE_DISABLED}, {@code false} otherwise + * {@link #FADE_STATE_DISABLED}, {@code false} otherwise */ public boolean isContentTypeUnfadeable(@AudioAttributes.AttributeContentType int contentType) { if (!isFadeEnabled()) { @@ -462,11 +473,11 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Query if the player type is unfadeable * - * @param playerType the {@link android.media.AudioPlaybackConfiguration} player type + * @param playerType the {@link android.media.AudioPlaybackConfiguration player type} * @return {@code true} if player type is unfadeable or if fade state is set to - * {@link #FADE_STATE_DISABLED}, {@code false} otherwise + * {@link #FADE_STATE_DISABLED}, {@code false} otherwise */ - public boolean isPlayerTypeUnfadeable(int playerType) { + public boolean isPlayerTypeUnfadeable(@AudioPlaybackConfiguration.PlayerType int playerType) { if (!isFadeEnabled()) { return true; } @@ -478,7 +489,7 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param audioAttributes the {@link android.media.AudioAttributes} * @return {@code true} if audio attributes is unfadeable or if fade state is set to - * {@link #FADE_STATE_DISABLED}, {@code false} otherwise + * {@link #FADE_STATE_DISABLED}, {@code false} otherwise * @throws NullPointerException if the audio attributes is {@code null} */ public boolean isAudioAttributesUnfadeable(@NonNull AudioAttributes audioAttributes) { @@ -494,7 +505,7 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param uid the uid of application * @return {@code true} if uid is unfadeable or if fade state is set to - * {@link #FADE_STATE_DISABLED}, {@code false} otherwise + * {@link #FADE_STATE_DISABLED}, {@code false} otherwise */ public boolean isUidUnfadeable(int uid) { if (!isFadeEnabled()) { @@ -503,6 +514,20 @@ public final class FadeManagerConfiguration implements Parcelable { return mUnfadeableUids.contains(uid); } + /** + * Returns the default fade out duration (in milliseconds) + */ + public static @DurationMillisLong long getDefaultFadeOutDurationMillis() { + return DEFAULT_FADE_OUT_DURATION_MS; + } + + /** + * Returns the default fade in duration (in milliseconds) + */ + public static @DurationMillisLong long getDefaultFadeInDurationMillis() { + return DEFAULT_FADE_IN_DURATION_MS; + } + @Override public String toString() { return "FadeManagerConfiguration { fade state = " + fadeStateToString(mFadeState) @@ -520,7 +545,7 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Convert fade state into a human-readable string * - * @param fadeState one of the fade state in {@link FadeStateEnum} + * @param fadeState one of {@link #FADE_STATE_DISABLED} or {@link #FADE_STATE_ENABLED_DEFAULT} * @return human-readable string * @hide */ @@ -531,8 +556,6 @@ public final class FadeManagerConfiguration implements Parcelable { return "FADE_STATE_DISABLED"; case FADE_STATE_ENABLED_DEFAULT: return "FADE_STATE_ENABLED_DEFAULT"; - case FADE_STATE_ENABLED_AUTO: - return "FADE_STATE_ENABLED_AUTO"; default: return "unknown fade state: " + fadeState; } @@ -712,9 +735,9 @@ public final class FadeManagerConfiguration implements Parcelable { * * <p><b>Notes:</b> * <ul> - * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT} or - * {@link #FADE_STATE_ENABLED_AUTO}, the builder expects at least one valid usage to be - * set/added. Failure to do so will result in an exception during {@link #build()}</li> + * <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT}, the builder expects at + * least one valid usage to be set/added. Failure to do so will result in an exception + * during {@link #build()}</li> * <li>Every usage added to the fadeable list should have corresponding volume shaper * configs defined. This can be achieved by setting either the duration or volume shaper * config through {@link #setFadeOutDurationForUsage(int, long)} or @@ -741,11 +764,6 @@ public final class FadeManagerConfiguration implements Parcelable { private static final long IS_FADEABLE_USAGES_FIELD_SET = 1 << 1; private static final long IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET = 1 << 2; - /** duration of the fade out curve */ - private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000; - /** duration of the fade in curve */ - private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000; - /** * delay after which a faded out player will be faded back in. This will be heard by the * user only in the case of unmuting players that didn't respect audio focus and didn't @@ -771,9 +789,10 @@ public final class FadeManagerConfiguration implements Parcelable { }); private int mFadeState = FADE_STATE_ENABLED_DEFAULT; - private long mFadeInDelayForOffendersMillis = DEFAULT_DELAY_FADE_IN_OFFENDERS_MS; - private long mFadeOutDurationMillis; - private long mFadeInDurationMillis; + private @DurationMillisLong long mFadeInDelayForOffendersMillis = + DEFAULT_DELAY_FADE_IN_OFFENDERS_MS; + private @DurationMillisLong long mFadeOutDurationMillis; + private @DurationMillisLong long mFadeInDurationMillis; private long mBuilderFieldsSet; private SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap = new SparseArray<>(); @@ -787,7 +806,8 @@ public final class FadeManagerConfiguration implements Parcelable { private List<AudioAttributes> mUnfadeableAudioAttributes = new ArrayList<>(); /** - * Constructs a new Builder with default fade out and fade in durations + * Constructs a new Builder with {@link #DEFAULT_FADE_OUT_DURATION_MS} and + * {@link #DEFAULT_FADE_IN_DURATION_MS} durations. */ public Builder() { mFadeOutDurationMillis = DEFAULT_FADE_OUT_DURATION_MS; @@ -800,7 +820,8 @@ public final class FadeManagerConfiguration implements Parcelable { * @param fadeOutDurationMillis duration in milliseconds used for fading out * @param fadeInDurationMills duration in milliseconds used for fading in */ - public Builder(long fadeOutDurationMillis, long fadeInDurationMills) { + public Builder(@DurationMillisLong long fadeOutDurationMillis, + @DurationMillisLong long fadeInDurationMills) { mFadeOutDurationMillis = fadeOutDurationMillis; mFadeInDurationMillis = fadeInDurationMills; } @@ -830,7 +851,8 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Set the overall fade state * - * @param state one of the {@link FadeStateEnum} states + * @param state one of the {@link #FADE_STATE_DISABLED} or + * {@link #FADE_STATE_ENABLED_DEFAULT} states * @return the same Builder instance * @throws IllegalArgumentException if the fade state is invalid * @see #getFadeState() @@ -844,21 +866,22 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with - * {@link android.media.AudioAttributes.AttributeUsage} + * {@link android.media.AudioAttributes usage} * <p> * This method accepts {@code null} for volume shaper config to clear a previously set * configuration (example, if set through * {@link #Builder(android.media.FadeManagerConfiguration)}) * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player + * @param usage the {@link android.media.AudioAttributes usage} of target player * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used - * to fade out players with usage + * to fade out players with usage * @return the same Builder instance * @throws IllegalArgumentException if the usage is invalid * @see #getFadeOutVolumeShaperConfigForUsage(int) */ @NonNull - public Builder setFadeOutVolumeShaperConfigForUsage(int usage, + public Builder setFadeOutVolumeShaperConfigForUsage( + @AudioAttributes.AttributeUsage int usage, @Nullable VolumeShaper.Configuration fadeOutVShaperConfig) { validateUsage(usage); getFadeVolShaperConfigWrapperForUsage(usage) @@ -869,21 +892,22 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with - * {@link android.media.AudioAttributes.AttributeUsage} + * {@link android.media.AudioAttributes usage} * <p> * This method accepts {@code null} for volume shaper config to clear a previously set * configuration (example, if set through * {@link #Builder(android.media.FadeManagerConfiguration)}) * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} + * @param usage the {@link android.media.AudioAttributes usage} * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used - * to fade in players with usage + * to fade in players with usage * @return the same Builder instance * @throws IllegalArgumentException if the usage is invalid * @see #getFadeInVolumeShaperConfigForUsage(int) */ @NonNull - public Builder setFadeInVolumeShaperConfigForUsage(int usage, + public Builder setFadeInVolumeShaperConfigForUsage( + @AudioAttributes.AttributeUsage int usage, @Nullable VolumeShaper.Configuration fadeInVShaperConfig) { validateUsage(usage); getFadeVolShaperConfigWrapperForUsage(usage) @@ -894,7 +918,7 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Set the duration used for fading out players with - * {@link android.media.AudioAttributes.AttributeUsage} + * {@link android.media.AudioAttributes usage} * <p> * A Volume shaper configuration is generated with the provided duration and default * volume curve definitions. This config is then used to fade out players with given usage. @@ -904,17 +928,18 @@ public final class FadeManagerConfiguration implements Parcelable { * {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to * {@code null} * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player + * @param usage the {@link android.media.AudioAttributes usage} of target player * @param fadeOutDurationMillis positive duration in milliseconds or - * {@link #DURATION_NOT_SET} + * {@link #DURATION_NOT_SET} * @return the same Builder instance * @throws IllegalArgumentException if the fade out duration is non-positive with the - * exception of {@link #DURATION_NOT_SET} + * exception of {@link #DURATION_NOT_SET} * @see #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration) * @see #getFadeOutDurationForUsage(int) */ @NonNull - public Builder setFadeOutDurationForUsage(int usage, long fadeOutDurationMillis) { + public Builder setFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage, + @DurationMillisLong long fadeOutDurationMillis) { validateUsage(usage); VolumeShaper.Configuration fadeOutVShaperConfig = createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false); @@ -924,7 +949,7 @@ public final class FadeManagerConfiguration implements Parcelable { /** * Set the duration used for fading in players with - * {@link android.media.AudioAttributes.AttributeUsage} + * {@link android.media.AudioAttributes usage} * <p> * A Volume shaper configuration is generated with the provided duration and default * volume curve definitions. This config is then used to fade in players with given usage. @@ -934,17 +959,18 @@ public final class FadeManagerConfiguration implements Parcelable { * {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to * {@code null} * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player + * @param usage the {@link android.media.AudioAttributes usage} of target player * @param fadeInDurationMillis positive duration in milliseconds or - * {@link #DURATION_NOT_SET} + * {@link #DURATION_NOT_SET} * @return the same Builder instance * @throws IllegalArgumentException if the fade in duration is non-positive with the - * exception of {@link #DURATION_NOT_SET} + * exception of {@link #DURATION_NOT_SET} * @see #setFadeInVolumeShaperConfigForUsage(int, VolumeShaper.Configuration) * @see #getFadeInDurationForUsage(int) */ @NonNull - public Builder setFadeInDurationForUsage(int usage, long fadeInDurationMillis) { + public Builder setFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage, + @DurationMillisLong long fadeInDurationMillis) { validateUsage(usage); VolumeShaper.Configuration fadeInVShaperConfig = createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true); @@ -962,9 +988,8 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param audioAttributes the {@link android.media.AudioAttributes} * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to - * fade out players with audio attribute + * fade out players with audio attribute * @return the same Builder instance - * @throws NullPointerException if the audio attributes is {@code null} * @see #getFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes) */ @NonNull @@ -988,7 +1013,7 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param audioAttributes the {@link android.media.AudioAttributes} * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to - * fade in players with audio attribute + * fade in players with audio attribute * @return the same Builder instance * @throws NullPointerException if the audio attributes is {@code null} * @see #getFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes) @@ -1017,12 +1042,12 @@ public final class FadeManagerConfiguration implements Parcelable { * {@code null} * * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade out - * duration will be set/updated/reset + * duration will be set/updated/reset * @param fadeOutDurationMillis positive duration in milliseconds or - * {@link #DURATION_NOT_SET} + * {@link #DURATION_NOT_SET} * @return the same Builder instance * @throws IllegalArgumentException if the fade out duration is non-positive with the - * exception of {@link #DURATION_NOT_SET} + * exception of {@link #DURATION_NOT_SET} * @see #getFadeOutDurationForAudioAttributes(AudioAttributes) * @see #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes, * VolumeShaper.Configuration) @@ -1030,7 +1055,7 @@ public final class FadeManagerConfiguration implements Parcelable { @NonNull public Builder setFadeOutDurationForAudioAttributes( @NonNull AudioAttributes audioAttributes, - long fadeOutDurationMillis) { + @DurationMillisLong long fadeOutDurationMillis) { Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null"); VolumeShaper.Configuration fadeOutVShaperConfig = createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false); @@ -1039,8 +1064,7 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Set the duration used for fading in players of type - * {@link android.media.AudioAttributes}. + * Set the duration used for fading in players of type {@link android.media.AudioAttributes} * <p> * A Volume shaper configuration is generated with the provided duration and default * volume curve definitions. This config is then used to fade in players with given usage. @@ -1051,19 +1075,19 @@ public final class FadeManagerConfiguration implements Parcelable { * {@code null} * * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade in - * duration will be set/updated/reset + * duration will be set/updated/reset * @param fadeInDurationMillis positive duration in milliseconds or - * {@link #DURATION_NOT_SET} + * {@link #DURATION_NOT_SET} * @return the same Builder instance * @throws IllegalArgumentException if the fade in duration is non-positive with the - * exception of {@link #DURATION_NOT_SET} + * exception of {@link #DURATION_NOT_SET} * @see #getFadeInDurationForAudioAttributes(AudioAttributes) * @see #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes, * VolumeShaper.Configuration) */ @NonNull public Builder setFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes, - long fadeInDurationMillis) { + @DurationMillisLong long fadeInDurationMillis) { Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null"); VolumeShaper.Configuration fadeInVShaperConfig = createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true); @@ -1072,22 +1096,18 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Set the list of {@link android.media.AudioAttributes.AttributeUsage} that can be faded + * Set the list of {@link android.media.AudioAttributes usage} that can be faded * * <p>This is a positive list. Players with matching usage will be considered for fading. * Usages that are not part of this list will not be faded * - * <p>Passing an empty list as input clears the existing list. This can be used to - * reset the list when using a copy constructor - * * <p><b>Warning:</b> When fade state is set to enabled, the builder expects at least one * usage to be set/added. Failure to do so will result in an exception during * {@link #build()} * - * @param usages List of the {@link android.media.AudioAttributes.AttributeUsage} + * @param usages List of the {@link android.media.AudioAttributes usages} * @return the same Builder instance * @throws IllegalArgumentException if the usages are invalid - * @throws NullPointerException if the usage list is {@code null} * @see #getFadeableUsages() */ @NonNull @@ -1101,9 +1121,9 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Add the {@link android.media.AudioAttributes.AttributeUsage} to the fadeable list + * Add the {@link android.media.AudioAttributes usage} to the fadeable list * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} + * @param usage the {@link android.media.AudioAttributes usage} * @return the same Builder instance * @throws IllegalArgumentException if the usage is invalid * @see #getFadeableUsages() @@ -1120,30 +1140,23 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Remove the {@link android.media.AudioAttributes.AttributeUsage} from the fadeable list - * <p> - * Players of this usage type will not be faded. + * Clears the fadeable {@link android.media.AudioAttributes usage} list + * + * <p>This can be used to reset the list when using a copy constructor * - * @param usage the {@link android.media.AudioAttributes.AttributeUsage} * @return the same Builder instance - * @throws IllegalArgumentException if the usage is invalid * @see #getFadeableUsages() * @see #setFadeableUsages(List) */ @NonNull - public Builder clearFadeableUsage(@AudioAttributes.AttributeUsage int usage) { - validateUsage(usage); + public Builder clearFadeableUsages() { setFlag(IS_FADEABLE_USAGES_FIELD_SET); - int index = mFadeableUsages.indexOf(usage); - if (index != INVALID_INDEX) { - mFadeableUsages.remove(index); - } + mFadeableUsages.clear(); return this; } /** - * Set the list of {@link android.media.AudioAttributes.AttributeContentType} that can not - * be faded + * Set the list of {@link android.media.AudioAttributes content type} that can not be faded * * <p>This is a negative list. Players with matching content type of this list will not be * faded. Content types that are not part of this list will be considered for fading. @@ -1151,10 +1164,9 @@ public final class FadeManagerConfiguration implements Parcelable { * <p>Passing an empty list as input clears the existing list. This can be used to * reset the list when using a copy constructor * - * @param contentTypes list of {@link android.media.AudioAttributes.AttributeContentType} + * @param contentTypes list of {@link android.media.AudioAttributes content types} * @return the same Builder instance * @throws IllegalArgumentException if the content types are invalid - * @throws NullPointerException if the content type list is {@code null} * @see #getUnfadeableContentTypes() */ @NonNull @@ -1168,9 +1180,9 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Add the {@link android.media.AudioAttributes.AttributeContentType} to unfadeable list + * Add the {@link android.media.AudioAttributes content type} to unfadeable list * - * @param contentType the {@link android.media.AudioAttributes.AttributeContentType} + * @param contentType the {@link android.media.AudioAttributes content type} * @return the same Builder instance * @throws IllegalArgumentException if the content type is invalid * @see #setUnfadeableContentTypes(List) @@ -1188,24 +1200,18 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Remove the {@link android.media.AudioAttributes.AttributeContentType} from the - * unfadeable list + * Clears the unfadeable {@link android.media.AudioAttributes content type} list + * + * <p>This can be used to reset the list when using a copy constructor * - * @param contentType the {@link android.media.AudioAttributes.AttributeContentType} * @return the same Builder instance - * @throws IllegalArgumentException if the content type is invalid * @see #setUnfadeableContentTypes(List) * @see #getUnfadeableContentTypes() */ @NonNull - public Builder clearUnfadeableContentType( - @AudioAttributes.AttributeContentType int contentType) { - validateContentType(contentType); + public Builder clearUnfadeableContentTypes() { setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET); - int index = mUnfadeableContentTypes.indexOf(contentType); - if (index != INVALID_INDEX) { - mUnfadeableContentTypes.remove(index); - } + mUnfadeableContentTypes.clear(); return this; } @@ -1213,14 +1219,10 @@ public final class FadeManagerConfiguration implements Parcelable { * Set the uids that cannot be faded * * <p>This is a negative list. Players with matching uid of this list will not be faded. - * Uids that are not part of this list shall be considered for fading - * - * <p>Passing an empty list as input clears the existing list. This can be used to - * reset the list when using a copy constructor + * Uids that are not part of this list shall be considered for fading. * * @param uids list of uids * @return the same Builder instance - * @throws NullPointerException if the uid list is {@code null} * @see #getUnfadeableUids() */ @NonNull @@ -1248,19 +1250,17 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Remove the uid from unfadeable list + * Clears the unfadeable uid list + * + * <p>This can be used to reset the list when using a copy constructor. * - * @param uid client uid * @return the same Builder instance * @see #setUnfadeableUids(List) * @see #getUnfadeableUids() */ @NonNull - public Builder clearUnfadeableUid(int uid) { - int index = mUnfadeableUids.indexOf(uid); - if (index != INVALID_INDEX) { - mUnfadeableUids.remove(index); - } + public Builder clearUnfadeableUids() { + mUnfadeableUids.clear(); return this; } @@ -1270,24 +1270,19 @@ public final class FadeManagerConfiguration implements Parcelable { * <p>This is a negative list. Players with matching audio attributes of this list will not * be faded. Audio attributes that are not part of this list shall be considered for fading. * - * <p>Passing an empty list as input clears any existing list. This can be used to - * reset the list when using a copy constructor - * * <p><b>Note:</b> Be cautious when adding generic audio attributes into this list as it can - * negatively impact fadeability decision if such an audio attribute and corresponding - * usage fall into opposing lists. + * negatively impact fadeability decision (if such an audio attribute and corresponding + * usage fall into opposing lists). * For example: * <pre class=prettyprint> * AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build() </pre> * is a generic audio attribute for {@link android.media.AudioAttributes.USAGE_MEDIA}. - * It is an undefined behavior to have an - * {@link android.media.AudioAttributes.AttributeUsage} in the fadeable usage list and the - * corresponding generic {@link android.media.AudioAttributes} in the unfadeable list. Such - * cases will result in an exception during {@link #build()} + * It is an undefined behavior to have an {@link android.media.AudioAttributes usage} in the + * fadeable usage list and the corresponding generic {@link android.media.AudioAttributes} + * in the unfadeable list. Such cases will result in an exception during {@link #build()}. * * @param attrs list of {@link android.media.AudioAttributes} * @return the same Builder instance - * @throws NullPointerException if the audio attributes list is {@code null} * @see #getUnfadeableAudioAttributes() */ @NonNull @@ -1303,7 +1298,6 @@ public final class FadeManagerConfiguration implements Parcelable { * * @param audioAttributes the {@link android.media.AudioAttributes} * @return the same Builder instance - * @throws NullPointerException if the audio attributes is {@code null} * @see #setUnfadeableAudioAttributes(List) * @see #getUnfadeableAudioAttributes() */ @@ -1317,19 +1311,16 @@ public final class FadeManagerConfiguration implements Parcelable { } /** - * Remove the {@link android.media.AudioAttributes} from the unfadeable list. + * Clears the unfadeable {@link android.media.AudioAttributes} list. + * + * <p>This can be used to reset the list when using a copy constructor. * - * @param audioAttributes the {@link android.media.AudioAttributes} * @return the same Builder instance - * @throws NullPointerException if the audio attributes is {@code null} * @see #getUnfadeableAudioAttributes() */ @NonNull - public Builder clearUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) { - Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null"); - if (mUnfadeableAudioAttributes.contains(audioAttributes)) { - mUnfadeableAudioAttributes.remove(audioAttributes); - } + public Builder clearUnfadeableAudioAttributes() { + mUnfadeableAudioAttributes.clear(); return this; } @@ -1345,7 +1336,7 @@ public final class FadeManagerConfiguration implements Parcelable { * @see #getFadeInDelayForOffenders() */ @NonNull - public Builder setFadeInDelayForOffenders(long delayMillis) { + public Builder setFadeInDelayForOffenders(@DurationMillisLong long delayMillis) { Preconditions.checkArgument(delayMillis >= 0, "Delay cannot be negative"); mFadeInDelayForOffendersMillis = delayMillis; return this; @@ -1469,7 +1460,6 @@ public final class FadeManagerConfiguration implements Parcelable { switch(state) { case FADE_STATE_DISABLED: case FADE_STATE_ENABLED_DEFAULT: - case FADE_STATE_ENABLED_AUTO: break; default: throw new IllegalArgumentException("Unknown fade state: " + state); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 8dfa6be0fd11..98bd3caf3f7d 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -365,6 +365,8 @@ interface IAudioService { oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb); + List<AudioMix> getRegisteredPolicyMixes(); + void unregisterAudioPolicy(in IAudioPolicyCallback pcb); int addMixForPolicy(in AudioPolicyConfig policyConfig, in IAudioPolicyCallback pcb); diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index b85decc74ff8..bbe461c95ed9 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -58,6 +58,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -597,6 +598,21 @@ public class AudioPolicy { setRegistration(null); } + /** + * @hide + */ + @TestApi + @NonNull + @FlaggedApi(Flags.FLAG_AUDIO_MIX_TEST_API) + public List<AudioMix> getMixes() { + if (!Flags.audioMixTestApi()) { + return Collections.emptyList(); + } + synchronized (mLock) { + return List.copyOf(mConfig.getMixes()); + } + } + public void setRegistration(String regId) { synchronized (mLock) { mRegistrationId = regId; diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/java/android/media/browse/MediaBrowserUtils.java index 19d9f008d3db..8c008bc3e28d 100644 --- a/media/java/android/media/browse/MediaBrowserUtils.java +++ b/media/java/android/media/browse/MediaBrowserUtils.java @@ -18,6 +18,9 @@ package android.media.browse; import android.os.Bundle; +import java.util.Collections; +import java.util.List; + /** * @hide */ @@ -75,4 +78,29 @@ public class MediaBrowserUtils { } return false; } + + /** + * Returns a paged version of the given {@code list}, using the paging parameters in {@code + * options}. + */ + public static List<MediaBrowser.MediaItem> applyPagingOptions( + List<MediaBrowser.MediaItem> list, final Bundle options) { + if (list == null) { + return null; + } + int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1); + int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1); + if (page == -1 && pageSize == -1) { + return list; + } + int fromIndex = pageSize * page; + int toIndex = fromIndex + pageSize; + if (page < 0 || pageSize < 1 || fromIndex >= list.size()) { + return Collections.EMPTY_LIST; + } + if (toIndex > list.size()) { + toIndex = list.size(); + } + return list.subList(fromIndex, toIndex); + } } diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index e8ef46499dc8..fa9afa872091 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -48,7 +48,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -104,11 +103,9 @@ public abstract class MediaBrowserService extends Service { RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED }) private @interface ResultFlags { } - private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>(); - private ConnectionRecord mCurConnection; private final Handler mHandler = new Handler(); - private ServiceBinder mBinder; - MediaSession.Token mSession; + + private final ServiceState mServiceState = new ServiceState(); /** * All the info about a connection. @@ -140,7 +137,7 @@ public abstract class MediaBrowserService extends Service { service.mHandler.post(new Runnable() { @Override public void run() { - service.mConnections.remove(callbacks.asBinder()); + service.mServiceState.mConnections.remove(callbacks.asBinder()); } }); } @@ -239,17 +236,16 @@ public abstract class MediaBrowserService extends Service { @Override public void run() { final IBinder b = callbacks.asBinder(); - // Clear out the old subscriptions. We are getting new ones. - service.mConnections.remove(b); + service.mServiceState.mConnections.remove(b); // Temporarily sets a placeholder ConnectionRecord to make // getCurrentBrowserInfo() work in onGetRoot(). - service.mCurConnection = + service.mServiceState.mCurConnection = new ConnectionRecord( service, pkg, pid, uid, rootHints, callbacks, null); BrowserRoot root = service.onGetRoot(pkg, uid, rootHints); - service.mCurConnection = null; + service.mServiceState.mCurConnection = null; // If they didn't return something, don't allow this client. if (root == null) { @@ -266,16 +262,18 @@ public abstract class MediaBrowserService extends Service { ConnectionRecord connection = new ConnectionRecord( service, pkg, pid, uid, rootHints, callbacks, root); - service.mConnections.put(b, connection); + service.mServiceState.mConnections.put(b, connection); b.linkToDeath(connection, 0); - if (service.mSession != null) { - callbacks.onConnect(connection.root.getRootId(), - service.mSession, connection.root.getExtras()); + 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.mConnections.remove(b); + service.mServiceState.mConnections.remove(b); } } } @@ -295,7 +293,7 @@ public abstract class MediaBrowserService extends Service { final IBinder b = callbacks.asBinder(); // Clear out the old subscriptions. We are getting new ones. - final ConnectionRecord old = service.mConnections.remove(b); + final ConnectionRecord old = service.mServiceState.mConnections.remove(b); if (old != null) { // TODO old.callbacks.asBinder().unlinkToDeath(old, 0); @@ -323,7 +321,7 @@ public abstract class MediaBrowserService extends Service { final IBinder b = callbacks.asBinder(); // Get the record for the connection - final ConnectionRecord connection = service.mConnections.get(b); + ConnectionRecord connection = service.mServiceState.mConnections.get(b); if (connection == null) { Log.w(TAG, "addSubscription for callback that isn't registered id=" + id); @@ -354,7 +352,7 @@ public abstract class MediaBrowserService extends Service { public void run() { final IBinder b = callbacks.asBinder(); - ConnectionRecord connection = service.mConnections.get(b); + ConnectionRecord connection = service.mServiceState.mConnections.get(b); if (connection == null) { Log.w(TAG, "removeSubscription for callback that isn't registered id=" + id); @@ -380,7 +378,7 @@ public abstract class MediaBrowserService extends Service { @Override public void run() { final IBinder b = callbacks.asBinder(); - ConnectionRecord connection = service.mConnections.get(b); + ConnectionRecord connection = service.mServiceState.mConnections.get(b); if (connection == null) { Log.w(TAG, "getMediaItem for callback that isn't registered id=" + mediaId); return; @@ -394,13 +392,13 @@ public abstract class MediaBrowserService extends Service { @Override public void onCreate() { super.onCreate(); - mBinder = new ServiceBinder(this); + mServiceState.mBinder = new ServiceBinder(this); } @Override public IBinder onBind(Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { - return mBinder; + return mServiceState.mBinder; } return null; } @@ -525,14 +523,14 @@ public abstract class MediaBrowserService extends Service { if (token == null) { throw new IllegalArgumentException("Session token may not be null."); } - if (mSession != null) { + if (mServiceState.mSession != null) { throw new IllegalStateException("The session token has already been set."); } - mSession = token; + mServiceState.mSession = token; mHandler.post(new Runnable() { @Override public void run() { - Iterator<ConnectionRecord> iter = mConnections.values().iterator(); + Iterator<ConnectionRecord> iter = mServiceState.mConnections.values().iterator(); while (iter.hasNext()) { ConnectionRecord connection = iter.next(); try { @@ -552,7 +550,7 @@ public abstract class MediaBrowserService extends Service { * or if it has been destroyed. */ public @Nullable MediaSession.Token getSessionToken() { - return mSession; + return mServiceState.mSession; } /** @@ -568,11 +566,12 @@ public abstract class MediaBrowserService extends Service { * @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTED */ public final Bundle getBrowserRootHints() { - if (mCurConnection == null) { + ConnectionRecord curConnection = mServiceState.mCurConnection; + if (curConnection == null) { throw new IllegalStateException("This should be called inside of onGetRoot or" + " onLoadChildren or onLoadItem methods"); } - return mCurConnection.rootHints == null ? null : new Bundle(mCurConnection.rootHints); + return curConnection.rootHints == null ? null : new Bundle(curConnection.rootHints); } /** @@ -583,11 +582,12 @@ public abstract class MediaBrowserService extends Service { * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo) */ public final RemoteUserInfo getCurrentBrowserInfo() { - if (mCurConnection == null) { + ConnectionRecord curConnection = mServiceState.mCurConnection; + if (curConnection == null) { throw new IllegalStateException("This should be called inside of onGetRoot or" + " onLoadChildren or onLoadItem methods"); } - return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid); + return new RemoteUserInfo(curConnection.pkg, curConnection.pid, curConnection.uid); } /** @@ -627,8 +627,8 @@ public abstract class MediaBrowserService extends Service { mHandler.post(new Runnable() { @Override public void run() { - for (IBinder binder : mConnections.keySet()) { - ConnectionRecord connection = mConnections.get(binder); + for (IBinder binder : mServiceState.mConnections.keySet()) { + ConnectionRecord connection = mServiceState.mConnections.get(binder); List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(parentId); if (callbackList != null) { @@ -718,7 +718,7 @@ public abstract class MediaBrowserService extends Service { new Result<List<MediaBrowser.MediaItem>>(parentId) { @Override void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) { - if (mConnections.get(connection.callbacks.asBinder()) != connection) { + 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); @@ -728,7 +728,7 @@ public abstract class MediaBrowserService extends Service { List<MediaBrowser.MediaItem> filteredList = (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0 - ? applyOptions(list, options) : list; + ? MediaBrowserUtils.applyPagingOptions(list, options) : list; final ParceledListSlice<MediaBrowser.MediaItem> pls; if (filteredList == null) { pls = null; @@ -748,13 +748,13 @@ public abstract class MediaBrowserService extends Service { } }; - mCurConnection = connection; + mServiceState.mCurConnection = connection; if (options == null) { onLoadChildren(parentId, result); } else { onLoadChildren(parentId, result, options); } - mCurConnection = null; + mServiceState.mCurConnection = null; if (!result.isDone()) { throw new IllegalStateException("onLoadChildren must call detach() or sendResult()" @@ -762,34 +762,13 @@ public abstract class MediaBrowserService extends Service { } } - private List<MediaBrowser.MediaItem> applyOptions(List<MediaBrowser.MediaItem> list, - final Bundle options) { - if (list == null) { - return null; - } - int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1); - int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1); - if (page == -1 && pageSize == -1) { - return list; - } - int fromIndex = pageSize * page; - int toIndex = fromIndex + pageSize; - if (page < 0 || pageSize < 1 || fromIndex >= list.size()) { - return Collections.EMPTY_LIST; - } - if (toIndex > list.size()) { - toIndex = list.size(); - } - return list.subList(fromIndex, toIndex); - } - private void performLoadItem(String itemId, final ConnectionRecord connection, final ResultReceiver receiver) { final Result<MediaBrowser.MediaItem> result = new Result<MediaBrowser.MediaItem>(itemId) { @Override void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) { - if (mConnections.get(connection.callbacks.asBinder()) != connection) { + 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); @@ -806,9 +785,9 @@ public abstract class MediaBrowserService extends Service { } }; - mCurConnection = connection; + mServiceState.mCurConnection = connection; onLoadItem(itemId, result); - mCurConnection = null; + mServiceState.mCurConnection = null; if (!result.isDone()) { throw new IllegalStateException("onLoadItem must call detach() or sendResult()" @@ -903,4 +882,22 @@ public abstract class MediaBrowserService extends Service { return mExtras; } } + + /** + * Holds all state associated with {@link #mSession}. + * + * <p>This class decouples the state associated with the session from the lifecycle of the + * service. This allows us to put the service in a valid state once the session is released + * (which is an irrecoverable invalid state). More details about this in b/185136506. + */ + private static class ServiceState { + + // Fields accessed from any caller thread. + @Nullable private MediaSession.Token mSession; + @Nullable private ServiceBinder mBinder; + + // Fields accessed from mHandler only. + @NonNull private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>(); + @Nullable private ConnectionRecord mCurConnection; + } } diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java index f105ae9cc33e..236b1fdb1989 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java @@ -45,8 +45,10 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION) public final class FadeManagerConfigurationUnitTest { - private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000; - private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000; + private static final long DEFAULT_FADE_OUT_DURATION_MS = + FadeManagerConfiguration.getDefaultFadeOutDurationMillis(); + private static final long DEFAULT_FADE_IN_DURATION_MS = + FadeManagerConfiguration.getDefaultFadeInDurationMillis(); private static final long TEST_FADE_OUT_DURATION_MS = 1_500; private static final long TEST_FADE_IN_DURATION_MS = 750; private static final int TEST_INVALID_USAGE = -10; @@ -259,16 +261,6 @@ public final class FadeManagerConfigurationUnitTest { } @Test - public void testSetFadeState_toEnableAuto() { - final int fadeStateAuto = FadeManagerConfiguration.FADE_STATE_ENABLED_AUTO; - FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder() - .setFadeState(fadeStateAuto).build(); - - expect.withMessage("Fade state when enabled for audio").that(fmc.getFadeState()) - .isEqualTo(fadeStateAuto); - } - - @Test public void testSetFadeState_toInvalid_fails() { IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> new FadeManagerConfiguration.Builder() @@ -310,13 +302,13 @@ public final class FadeManagerConfigurationUnitTest { } @Test - public void testSetFadeVolShaperConfig_withNullVolumeShaper_getsNull() { + public void testSetFadeOutVolShaperConfig_withNullVolumeShaper_getsNull() { FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder(mFmc) .setFadeOutVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE, /* VolumeShaper.Configuration= */ null) .setFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE, /* VolumeShaper.Configuration= */ null) - .clearFadeableUsage(AudioAttributes.USAGE_MEDIA).build(); + .clearFadeableUsages().addFadeableUsage(AudioAttributes.USAGE_MEDIA).build(); expect.withMessage("Fade out volume shaper config set with null value") .that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes( @@ -547,31 +539,13 @@ public final class FadeManagerConfigurationUnitTest { } @Test - public void testClearFadeableUsage() { - final int usageToClear = AudioAttributes.USAGE_MEDIA; - List<Integer> updatedUsages = new ArrayList<>(mFmc.getFadeableUsages()); - updatedUsages.remove((Integer) usageToClear); - - FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration - .Builder(mFmc).clearFadeableUsage(usageToClear).build(); - - expect.withMessage("Clear fadeable usage").that(updatedFmc.getFadeableUsages()) - .containsExactlyElementsIn(updatedUsages); - } - - @Test - public void testClearFadeableUsage_withInvalidUsage_fails() { - FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc); - - IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> - fmcBuilder.clearFadeableUsage(TEST_INVALID_USAGE) - ); + public void testClearFadeableUsages() { + FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder(mFmc) + .clearFadeableUsages().addFadeableUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) + .build(); - FadeManagerConfiguration fmc = fmcBuilder.build(); - expect.withMessage("Clear invalid usage").that(thrown).hasMessageThat() - .contains("Invalid usage"); - expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages()) - .containsExactlyElementsIn(mFmc.getFadeableUsages()); + expect.withMessage("Clear fadeable usages").that(updatedFmc.getFadeableUsages()) + .containsExactlyElementsIn(List.of(AudioAttributes.USAGE_VOICE_COMMUNICATION)); } @Test @@ -673,7 +647,7 @@ public final class FadeManagerConfigurationUnitTest { } @Test - public void testClearUnfadeableContentType() { + public void testClearUnfadeableContentTypes() { List<Integer> unfadeableContentTypes = new ArrayList<>(Arrays.asList( AudioAttributes.CONTENT_TYPE_MOVIE, AudioAttributes.CONTENT_TYPE_SONIFICATION @@ -682,23 +656,10 @@ public final class FadeManagerConfigurationUnitTest { FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder() .setUnfadeableContentTypes(unfadeableContentTypes) - .clearUnfadeableContentType(contentTypeToClear).build(); + .clearUnfadeableContentTypes().build(); - unfadeableContentTypes.remove((Integer) contentTypeToClear); expect.withMessage("Unfadeable content types").that(updatedFmc.getUnfadeableContentTypes()) - .containsExactlyElementsIn(unfadeableContentTypes); - } - - @Test - public void testClearUnfadeableContentType_withInvalidContentType_fails() { - FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc); - - IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> - fmcBuilder.clearUnfadeableContentType(TEST_INVALID_CONTENT_TYPE).build() - ); - - expect.withMessage("Invalid content type exception").that(thrown).hasMessageThat() - .contains("Invalid content type"); + .isEmpty(); } @Test @@ -735,7 +696,7 @@ public final class FadeManagerConfigurationUnitTest { } @Test - public void testClearUnfadebaleUid() { + public void testClearUnfadebaleUids() { final List<Integer> unfadeableUids = List.of( TEST_UID_1, TEST_UID_2 @@ -744,10 +705,9 @@ public final class FadeManagerConfigurationUnitTest { .setUnfadeableUids(unfadeableUids).build(); FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder(fmc) - .clearUnfadeableUid(TEST_UID_1).build(); + .clearUnfadeableUids().build(); - expect.withMessage("Unfadeable uids").that(updatedFmc.getUnfadeableUids()) - .isEqualTo(List.of(TEST_UID_2)); + expect.withMessage("Unfadeable uids").that(updatedFmc.getUnfadeableUids()).isEmpty(); } @Test diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt index 9a2cf61d2b86..e7d1072c352d 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt @@ -40,7 +40,7 @@ fun Intent.parseCancelUiRequest(packageManager: PackageManager): Request? = Log.d(TAG, "Received UI cancel request, shouldShowCancellationUi: $this") } if (showCancel) { - val appLabel = packageManager.appLabel(cancelUiRequest.appPackageName) + val appLabel = packageManager.appLabel(cancelUiRequest.packageName) if (appLabel == null) { Log.d(TAG, "Received UI cancel request with an invalid package name.") null diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index b13df61005e2..30973879de10 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -119,7 +119,7 @@ class CredentialManagerRepo( val cancellationRequest = getCancelUiRequest(intent) val cancelUiRequestState = cancellationRequest?.let { - CancelUiRequestState(getAppLabel(context.getPackageManager(), it.appPackageName)) + CancelUiRequestState(getAppLabel(context.getPackageManager(), it.packageName)) } initialUiState = when (requestInfo?.type) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index 26a97cd30c77..4771237d449f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -135,7 +135,7 @@ class CredentialSelectorActivity : ComponentActivity() { Log.d( Constants.LOG_TAG, "Received UI cancellation intent. Should show cancellation" + " ui = $shouldShowCancellationUi") - val appDisplayName = getAppLabel(packageManager, cancelUiRequest.appPackageName) + val appDisplayName = getAppLabel(packageManager, cancelUiRequest.packageName) if (!shouldShowCancellationUi) { this.finish() } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 6c5a984f20f7..f4da1e6c4770 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -72,7 +72,7 @@ class CredentialSelectorViewModel( init { uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INIT, - credManRepo.requestInfo?.appPackageName) + credManRepo.requestInfo?.packageName) } /**************************************************************************/ @@ -107,7 +107,7 @@ class CredentialSelectorViewModel( if (this.credManRepo.requestInfo?.token != credManRepo.requestInfo?.token) { this.uiMetrics.resetInstanceId() this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_NEW_REQUEST, - credManRepo.requestInfo?.appPackageName) + credManRepo.requestInfo?.packageName) } } @@ -189,7 +189,7 @@ class CredentialSelectorViewModel( private fun onInternalError() { Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state") this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INTERNAL_ERROR, - credManRepo.requestInfo?.appPackageName) + credManRepo.requestInfo?.packageName) credManRepo.onParsingFailureCancel() uiState = uiState.copy(dialogState = DialogState.COMPLETE) } @@ -399,6 +399,6 @@ class CredentialSelectorViewModel( @Composable fun logUiEvent(uiEventEnum: UiEventEnum) { - this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.appPackageName) + this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName) } }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 64595e2642f5..997c45e84180 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -195,7 +195,7 @@ class GetFlowUtils { } return com.android.credentialmanager.getflow.RequestDisplayInfo( appName = originName?.ifEmpty { null } - ?: getAppLabel(context.packageManager, requestInfo.appPackageName) + ?: getAppLabel(context.packageManager, requestInfo.packageName) ?: return null, preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials, preferIdentityDocUi = getCredentialRequest.data.getBoolean( @@ -269,7 +269,7 @@ class CreateFlowUtils { return null } val appLabel = originName?.ifEmpty { null } - ?: getAppLabel(context.packageManager, requestInfo.appPackageName) + ?: getAppLabel(context.packageManager, requestInfo.packageName) ?: return null val createCredentialRequest = requestInfo.createCredentialRequest ?: return null val createCredentialRequestJetpack = CreateCredentialRequest.createFrom( diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt index 3297991e2504..5590219bc011 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt @@ -76,7 +76,7 @@ fun CredentialsScreenChip( Chip( label = labelParam, onClick = onClick, - modifier = modifier, + modifier = modifier.fillMaxWidth(), secondaryLabel = secondaryLabelParam, icon = iconParam, colors = colors, @@ -104,7 +104,6 @@ fun SignInOptionsChip(onClick: () -> Unit) { label = stringResource(R.string.dialog_sign_in_options_button), onClick = onClick, modifier = Modifier - .fillMaxWidth() .padding(top = TOPPADDING) ) } @@ -121,7 +120,6 @@ fun ContinueChip(onClick: () -> Unit) { label = stringResource(R.string.dialog_continue_button), onClick = onClick, modifier = Modifier - .fillMaxWidth() .padding(top = TOPPADDING), colors = ChipDefaults.primaryChipColors(), ) @@ -139,7 +137,6 @@ fun DismissChip(onClick: () -> Unit) { label = stringResource(R.string.dialog_dismiss_button), onClick = onClick, modifier = Modifier - .fillMaxWidth() .padding(top = TOPPADDING), ) } diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml index f44b16104f99..aed985ec1218 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<resources> +<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <style name="TextAppearance.PreferenceTitle.SettingsLib" parent="@android:style/TextAppearance.Material.Subhead"> - <item name="android:textColor">@color/settingslib_text_color_primary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item> <item name="android:textSize">20sp</item> </style> diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt new file mode 100644 index 000000000000..2a4658bc69a1 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt @@ -0,0 +1,69 @@ +/* + * 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.data.repository + +import android.media.AudioDeviceAttributes +import android.media.Spatializer +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext + +interface SpatializerRepository { + + /** + * Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and + * false the otherwise. + */ + suspend fun isAvailableForDevice(audioDeviceAttributes: AudioDeviceAttributes): Boolean + + /** Returns a list [AudioDeviceAttributes] that are compatible with spatial audio. */ + suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> + + /** Adds a [audioDeviceAttributes] to [getCompatibleDevices] list. */ + suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) + + /** Removes a [audioDeviceAttributes] to [getCompatibleDevices] list. */ + suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) +} + +class SpatializerRepositoryImpl( + private val spatializer: Spatializer, + private val backgroundContext: CoroutineContext, +) : SpatializerRepository { + + override suspend fun isAvailableForDevice( + audioDeviceAttributes: AudioDeviceAttributes + ): Boolean { + return withContext(backgroundContext) { + spatializer.isAvailableForDevice(audioDeviceAttributes) + } + } + + override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> = + withContext(backgroundContext) { spatializer.compatibleAudioDevices } + + override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) { + withContext(backgroundContext) { + spatializer.addCompatibleAudioDevice(audioDeviceAttributes) + } + } + + override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) { + withContext(backgroundContext) { + spatializer.removeCompatibleAudioDevice(audioDeviceAttributes) + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt new file mode 100644 index 000000000000..c3cc340d9cd8 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt @@ -0,0 +1,39 @@ +/* + * 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.domain.interactor + +import android.media.AudioDeviceAttributes +import com.android.settingslib.media.data.repository.SpatializerRepository + +class SpatializerInteractor(private val repository: SpatializerRepository) { + + suspend fun isAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean = + repository.isAvailableForDevice(audioDeviceAttributes) + + /** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */ + suspend fun isEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean = + repository.getCompatibleDevices().contains(audioDeviceAttributes) + + /** Enblaes or disables spatial audio for [audioDeviceAttributes]. */ + suspend fun setEnabled(audioDeviceAttributes: AudioDeviceAttributes, isEnabled: Boolean) { + if (isEnabled) { + repository.addCompatibleDevice(audioDeviceAttributes) + } else { + repository.removeCompatibleDevice(audioDeviceAttributes) + } + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt new file mode 100644 index 000000000000..3f52f2494dfc --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.media.domain.interactor + +import android.media.AudioDeviceAttributes +import com.android.settingslib.media.data.repository.SpatializerRepository + +class FakeSpatializerRepository : SpatializerRepository { + + private val availabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> = mutableMapOf() + private val compatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf() + + override suspend fun isAvailableForDevice( + audioDeviceAttributes: AudioDeviceAttributes + ): Boolean = availabilityByDevice.getOrDefault(audioDeviceAttributes, false) + + override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> = + compatibleDevices + + override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) { + compatibleDevices.add(audioDeviceAttributes) + } + + override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) { + compatibleDevices.remove(audioDeviceAttributes) + } + + fun setIsAvailable(audioDeviceAttributes: AudioDeviceAttributes, isAvailable: Boolean) { + availabilityByDevice[audioDeviceAttributes] = isAvailable + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt new file mode 100644 index 000000000000..a44baeb174bf --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt @@ -0,0 +1,56 @@ +/* + * 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.domain.interactor + +import android.media.AudioDeviceAttributes +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SpatializerInteractorTest { + + private val testScope = TestScope() + private val underTest = SpatializerInteractor(FakeSpatializerRepository()) + + @Test + fun setEnabledFalse_isEnabled_false() { + testScope.runTest { + underTest.setEnabled(deviceAttributes, false) + + assertThat(underTest.isEnabled(deviceAttributes)).isFalse() + } + } + + @Test + fun setEnabledTrue_isEnabled_true() { + testScope.runTest { + underTest.setEnabled(deviceAttributes, true) + + assertThat(underTest.isEnabled(deviceAttributes)).isTrue() + } + } + + private companion object { + val deviceAttributes = AudioDeviceAttributes(0, 0, "test_device") + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 8ad5f244b659..dc8116d7f94f 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -261,6 +261,7 @@ public class SecureSettings { Settings.Secure.CREDENTIAL_SERVICE, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, Settings.Secure.EVEN_DIMMER_ACTIVATED, - Settings.Secure.EVEN_DIMMER_MIN_NITS + Settings.Secure.EVEN_DIMMER_MIN_NITS, + Settings.Secure.STYLUS_POINTER_ICON_ENABLED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index d854df38a9ef..fabdafc0dbc3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -416,5 +416,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR); VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR); VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR); + VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index e4a762ae8118..bc0783619b89 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2601,6 +2601,9 @@ class SettingsProtoDumpUtil { p.end(soundsToken); dumpSetting(s, p, + Settings.Secure.STYLUS_POINTER_ICON_ENABLED, + SecureSettingsProto.STYLUS_POINTER_ICON_ENABLED); + dumpSetting(s, p, Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, SecureSettingsProto.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED); dumpSetting(s, p, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index e99fcc92dea7..84ef6e51a00b 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -914,6 +914,9 @@ <!-- Permission required for Cts test ScreenRecordingCallbackTests --> <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" /> + <!-- Permissions required for CTS test - GrammaticalInflectionManagerTest --> + <uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index f877d7a89b92..12e8f574e906 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -262,6 +262,9 @@ <uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" /> + <!-- Activity Manager --> + <uses-permission android:name="android.permission.SET_THEME_OVERLAY_CONTROLLER_READY" /> + <!-- accessibility --> <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" /> <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" /> diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 4973cafbb397..c489795d861b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -45,13 +45,13 @@ import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import kotlin.math.roundToInt -private const val TAG = "ActivityLaunchAnimator" +private const val TAG = "ActivityTransitionAnimator" /** * A class that allows activities to be started in a seamless way from a view that is transforming * nicely into the starting window. */ -class ActivityLaunchAnimator( +class ActivityTransitionAnimator( /** The animator used when animating a View into an app. */ private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR, @@ -97,7 +97,7 @@ class ActivityLaunchAnimator( ) // TODO(b/288507023): Remove this flag. - @JvmField val DEBUG_LAUNCH_ANIMATION = Build.IS_DEBUGGABLE + @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS) private val DEFAULT_DIALOG_TO_APP_ANIMATOR = @@ -113,13 +113,13 @@ class ActivityLaunchAnimator( private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) /** The time we wait before timing out the remote animation after starting the intent. */ - private const val LAUNCH_TIMEOUT = 1_000L + private const val TRANSITION_TIMEOUT = 1_000L /** * The time we wait before we Log.wtf because the remote animation was neither started or * cancelled by WM. */ - private const val LONG_LAUNCH_TIMEOUT = 5_000L + private const val LONG_TRANSITION_TIMEOUT = 5_000L } /** @@ -134,20 +134,20 @@ class ActivityLaunchAnimator( /** Top-level listener that can be used to notify all registered [listeners]. */ private val lifecycleListener = object : Listener { - override fun onLaunchAnimationStart() { - listeners.forEach { it.onLaunchAnimationStart() } + override fun onTransitionAnimationStart() { + listeners.forEach { it.onTransitionAnimationStart() } } - override fun onLaunchAnimationEnd() { - listeners.forEach { it.onLaunchAnimationEnd() } + override fun onTransitionAnimationEnd() { + listeners.forEach { it.onTransitionAnimationEnd() } } - override fun onLaunchAnimationProgress(linearProgress: Float) { - listeners.forEach { it.onLaunchAnimationProgress(linearProgress) } + override fun onTransitionAnimationProgress(linearProgress: Float) { + listeners.forEach { it.onTransitionAnimationProgress(linearProgress) } } - override fun onLaunchAnimationCancelled() { - listeners.forEach { it.onLaunchAnimationCancelled() } + override fun onTransitionAnimationCancelled() { + listeners.forEach { it.onTransitionAnimationCancelled() } } } @@ -188,7 +188,7 @@ class ActivityLaunchAnimator( val callback = this.callback ?: throw IllegalStateException( - "ActivityLaunchAnimator.callback must be set before using this animator" + "ActivityTransitionAnimator.callback must be set before using this animator" ) val runner = createRunner(controller) val runnerDelegate = runner.delegate!! @@ -260,7 +260,7 @@ class ActivityLaunchAnimator( callOnIntentStartedOnMainThread(willAnimate) } } else { - if (DEBUG_LAUNCH_ANIMATION) { + if (DEBUG_TRANSITION_ANIMATION) { Log.d( TAG, "Calling controller.onIntentStarted(willAnimate=$willAnimate) " + @@ -293,7 +293,7 @@ class ActivityLaunchAnimator( } } - /** Add a [Listener] that can listen to launch animations. */ + /** Add a [Listener] that can listen to transition animations. */ fun addListener(listener: Listener) { listeners.add(listener) } @@ -340,24 +340,24 @@ class ActivityLaunchAnimator( } interface Listener { - /** Called when an activity launch animation started. */ - fun onLaunchAnimationStart() {} + /** Called when an activity transition animation started. */ + fun onTransitionAnimationStart() {} /** - * Called when an activity launch animation is finished. This will be called if and only if - * [onLaunchAnimationStart] was called earlier. + * Called when an activity transition animation is finished. This will be called if and only + * if [onTransitionAnimationStart] was called earlier. */ - fun onLaunchAnimationEnd() {} + fun onTransitionAnimationEnd() {} /** - * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after - * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called - * before the cancellation. + * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called + * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was + * called before the cancellation. */ - fun onLaunchAnimationCancelled() {} + fun onTransitionAnimationCancelled() {} - /** Called when an activity launch animation made progress. */ - fun onLaunchAnimationProgress(linearProgress: Float) {} + /** Called when an activity transition animation made progress. */ + fun onTransitionAnimationProgress(linearProgress: Float) {} } /** @@ -383,9 +383,10 @@ class ActivityLaunchAnimator( // issues. if (view !is LaunchableView) { throw IllegalArgumentException( - "An ActivityLaunchAnimator.Controller was created from a View that does " + - "not implement LaunchableView. This can lead to subtle bugs where the" + - " visibility of the View we are launching from is not what we expected." + "An ActivityTransitionAnimator.Controller was created from a View that " + + "does not implement LaunchableView. This can lead to subtle bugs " + + "where the visibility of the View we are launching from is not what " + + "we expected." ) } @@ -411,11 +412,11 @@ class ActivityLaunchAnimator( get() = false /** - * Whether the expandable controller by this [Controller] is below the launching window that - * is going to be animated. + * Whether the expandable controller by this [Controller] is below the window that is going + * to be animated. * - * This should be `false` when launching an app from the shade or status bar, given that - * they are drawn above all apps. This is usually `true` when using this launcher in a + * This should be `false` when animating an app from or to the shade or status bar, given + * that they are drawn above all apps. This is usually `true` when using this animator in a * normal app or a launcher, that are drawn below the animating activity/window. */ val isBelowAnimatingWindow: Boolean @@ -432,10 +433,11 @@ class ActivityLaunchAnimator( * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was * called before the cancellation. * - * If this launch animation affected the occlusion state of the keyguard, WM will provide us - * with [newKeyguardOccludedState] so that we can set the occluded state appropriately. + * If this transition animation affected the occlusion state of the keyguard, WM will + * provide us with [newKeyguardOccludedState] so that we can set the occluded state + * appropriately. */ - fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} + fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {} } /** @@ -449,24 +451,24 @@ class ActivityLaunchAnimator( ) : Listener { var cancelled = false - override fun onLaunchAnimationStart() { - delegate?.onLaunchAnimationStart() + override fun onTransitionAnimationStart() { + delegate?.onTransitionAnimationStart() } - override fun onLaunchAnimationProgress(linearProgress: Float) { - delegate?.onLaunchAnimationProgress(linearProgress) + override fun onTransitionAnimationProgress(linearProgress: Float) { + delegate?.onTransitionAnimationProgress(linearProgress) } - override fun onLaunchAnimationEnd() { - delegate?.onLaunchAnimationEnd() + override fun onTransitionAnimationEnd() { + delegate?.onTransitionAnimationEnd() if (!cancelled) { onAnimationComplete.invoke() } } - override fun onLaunchAnimationCancelled() { + override fun onTransitionAnimationCancelled() { cancelled = true - delegate?.onLaunchAnimationCancelled() + delegate?.onTransitionAnimationCancelled() onAnimationComplete.invoke() } } @@ -475,7 +477,7 @@ class ActivityLaunchAnimator( inner class Runner( controller: Controller, callback: Callback, - /** The animator to use to animate the window launch. */ + /** The animator to use to animate the window transition. */ transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR, /** Listener for animation lifecycle events. */ listener: Listener? = null @@ -543,7 +545,7 @@ class ActivityLaunchAnimator( private val callback: Callback, /** Listener for animation lifecycle events. */ private val listener: Listener? = null, - /** The animator to use to animate the window launch. */ + /** The animator to use to animate the window transition. */ private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR, /** @@ -574,8 +576,8 @@ class ActivityLaunchAnimator( private var animation: TransitionAnimator.Animation? = null /** - * A timeout to cancel the launch animation if the remote animation is not started or - * cancelled within [LAUNCH_TIMEOUT] milliseconds after the intent was started. + * A timeout to cancel the transition animation if the remote animation is not started or + * cancelled within [TRANSITION_TIMEOUT] milliseconds after the intent was started. * * Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise * it will be automatically converted when posted and we wouldn't be able to remove it after @@ -585,21 +587,22 @@ class ActivityLaunchAnimator( /** * A long timeout to Log.wtf (signaling a bug in WM) when the remote animation wasn't - * started or cancelled within [LONG_LAUNCH_TIMEOUT] milliseconds after the intent was + * started or cancelled within [LONG_TRANSITION_TIMEOUT] milliseconds after the intent was * started. */ private var onLongTimeout = Runnable { Log.wtf( TAG, - "The remote animation was neither cancelled or started within $LONG_LAUNCH_TIMEOUT" + "The remote animation was neither cancelled or started within " + + "$LONG_TRANSITION_TIMEOUT" ) } @UiThread internal fun postTimeouts() { if (timeoutHandler != null) { - timeoutHandler.postDelayed(onTimeout, LAUNCH_TIMEOUT) - timeoutHandler.postDelayed(onLongTimeout, LONG_LAUNCH_TIMEOUT) + timeoutHandler.postDelayed(onTimeout, TRANSITION_TIMEOUT) + timeoutHandler.postDelayed(onLongTimeout, LONG_TRANSITION_TIMEOUT) } } @@ -670,14 +673,14 @@ class ActivityLaunchAnimator( Log.i(TAG, "Aborting the animation as no window is opening") iCallback?.invoke() - if (DEBUG_LAUNCH_ANIMATION) { + if (DEBUG_TRANSITION_ANIMATION) { Log.d( TAG, - "Calling controller.onLaunchAnimationCancelled() [no window opening]" + "Calling controller.onTransitionAnimationCancelled() [no window opening]" ) } - controller.onLaunchAnimationCancelled() - listener?.onLaunchAnimationCancelled() + controller.onTransitionAnimationCancelled() + listener?.onTransitionAnimationCancelled() return } @@ -720,27 +723,29 @@ class ActivityLaunchAnimator( val controller = object : Controller by delegate { override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - listener?.onLaunchAnimationStart() + listener?.onTransitionAnimationStart() - if (DEBUG_LAUNCH_ANIMATION) { + if (DEBUG_TRANSITION_ANIMATION) { Log.d( TAG, - "Calling controller.onLaunchAnimationStart(isExpandingFullyAbove=" + - "$isExpandingFullyAbove) [controller=$delegate]" + "Calling controller.onTransitionAnimationStart(" + + "isExpandingFullyAbove=$isExpandingFullyAbove) " + + "[controller=$delegate]" ) } delegate.onTransitionAnimationStart(isExpandingFullyAbove) } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - listener?.onLaunchAnimationEnd() + listener?.onTransitionAnimationEnd() iCallback?.invoke() - if (DEBUG_LAUNCH_ANIMATION) { + if (DEBUG_TRANSITION_ANIMATION) { Log.d( TAG, - "Calling controller.onLaunchAnimationEnd(isExpandingFullyAbove=" + - "$isExpandingFullyAbove) [controller=$delegate]" + "Calling controller.onTransitionAnimationEnd(" + + "isExpandingFullyAbove=$isExpandingFullyAbove) " + + "[controller=$delegate]" ) } delegate.onTransitionAnimationEnd(isExpandingFullyAbove) @@ -758,7 +763,7 @@ class ActivityLaunchAnimator( } navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } - listener?.onLaunchAnimationProgress(linearProgress) + listener?.onTransitionAnimationProgress(linearProgress) delegate.onTransitionAnimationProgress(state, progress, linearProgress) } } @@ -904,7 +909,7 @@ class ActivityLaunchAnimator( } private fun onAnimationTimedOut() { - // The remote animation was cancelled by WM, so we already cancelled the launch + // The remote animation was cancelled by WM, so we already cancelled the transition // animation. if (cancelled) { return @@ -913,18 +918,21 @@ class ActivityLaunchAnimator( Log.w(TAG, "Remote animation timed out") timedOut = true - if (DEBUG_LAUNCH_ANIMATION) { - Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]") + if (DEBUG_TRANSITION_ANIMATION) { + Log.d( + TAG, + "Calling controller.onTransitionAnimationCancelled() [animation timed out]" + ) } - controller.onLaunchAnimationCancelled() - listener?.onLaunchAnimationCancelled() + controller.onTransitionAnimationCancelled() + listener?.onTransitionAnimationCancelled() } @UiThread override fun onAnimationCancelled() { removeTimeouts() - // The short timeout happened, so we already cancelled the launch animation. + // The short timeout happened, so we already cancelled the transition animation. if (timedOut) { return } @@ -934,14 +942,15 @@ class ActivityLaunchAnimator( animation?.cancel() - if (DEBUG_LAUNCH_ANIMATION) { + if (DEBUG_TRANSITION_ANIMATION) { Log.d( TAG, - "Calling controller.onLaunchAnimationCancelled() [remote animation cancelled]", + "Calling controller.onTransitionAnimationCancelled() [remote animation " + + "cancelled]", ) } - controller.onLaunchAnimationCancelled() - listener?.onLaunchAnimationCancelled() + controller.onTransitionAnimationCancelled() + listener?.onTransitionAnimationCancelled() } private fun IRemoteAnimationFinishedCallback.invoke() { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt index b879ba0b1ece..a53ab62ef077 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt @@ -17,10 +17,10 @@ package com.android.systemui.animation /** - * A base class to easily create an implementation of [ActivityLaunchAnimator.Controller] which + * A base class to easily create an implementation of [ActivityTransitionAnimator.Controller] which * delegates most of its call to [delegate]. This is mostly useful for Java code which can't easily * create such a delegated class. */ open class DelegateLaunchAnimatorController( - protected val delegate: ActivityLaunchAnimator.Controller -) : ActivityLaunchAnimator.Controller by delegate + protected val delegate: ActivityTransitionAnimator.Controller +) : ActivityTransitionAnimator.Controller by delegate diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 9a36960996b0..ed7f31cd2c0c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -62,13 +62,14 @@ constructor( private val isForTesting: Boolean = false, ) { private companion object { - private val TIMINGS = ActivityLaunchAnimator.TIMINGS + private val TIMINGS = ActivityTransitionAnimator.TIMINGS // We use the same interpolator for X and Y axis to make sure the dialog does not move out // of the screen bounds during the animation. private val INTERPOLATORS = - ActivityLaunchAnimator.INTERPOLATORS.copy( - positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator + ActivityTransitionAnimator.INTERPOLATORS.copy( + positionXInterpolator = + ActivityTransitionAnimator.INTERPOLATORS.positionInterpolator ) } @@ -319,9 +320,9 @@ constructor( } /** - * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the - * dialog that contains [View]. Note that the dialog must have been shown using this animator, - * otherwise this method will return null. + * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from + * the dialog that contains [View]. Note that the dialog must have been shown using this + * animator, otherwise this method will return null. * * The returned controller will take care of dismissing the dialog at the right time after the * activity started, when the dialog to app animation is done (or when it is cancelled). If this @@ -333,7 +334,7 @@ constructor( fun createActivityLaunchController( view: View, cujType: Int? = null, - ): ActivityLaunchAnimator.Controller? { + ): ActivityTransitionAnimator.Controller? { val animatedDialog = openedDialogs.firstOrNull { it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl @@ -343,7 +344,7 @@ constructor( } /** - * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from + * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from * [dialog]. Note that the dialog must have been shown using this animator, otherwise this * method will return null. * @@ -357,7 +358,7 @@ constructor( fun createActivityLaunchController( dialog: Dialog, cujType: Int? = null, - ): ActivityLaunchAnimator.Controller? { + ): ActivityTransitionAnimator.Controller? { val animatedDialog = openedDialogs.firstOrNull { it.dialog == dialog } ?: return null return createActivityLaunchController(animatedDialog, cujType) } @@ -365,7 +366,7 @@ constructor( private fun createActivityLaunchController( animatedDialog: AnimatedDialog, cujType: Int? = null - ): ActivityLaunchAnimator.Controller? { + ): ActivityTransitionAnimator.Controller? { // At this point, we know that the intent of the caller is to dismiss the dialog to show // an app, so we disable the exit animation into the source because we will never want to // run it anyways. @@ -384,12 +385,12 @@ constructor( val dialogContentWithBackground = animatedDialog.dialogContentWithBackground ?: return null val controller = - ActivityLaunchAnimator.Controller.fromView(dialogContentWithBackground, cujType) + ActivityTransitionAnimator.Controller.fromView(dialogContentWithBackground, cujType) ?: return null // Wrap the controller into one that will instantly dismiss the dialog when the animation is // done or dismiss it normally (fading it out) if the animation is cancelled. - return object : ActivityLaunchAnimator.Controller by controller { + return object : ActivityTransitionAnimator.Controller by controller { override val isDialogLaunch = true override fun onIntentStarted(willAnimate: Boolean) { @@ -400,8 +401,8 @@ constructor( } } - override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { - controller.onLaunchAnimationCancelled() + override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { + controller.onTransitionAnimationCancelled() enableDialogDismiss() dialog.dismiss() } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt index c49a487c6766..2ba5948c50cc 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt @@ -21,14 +21,14 @@ import android.view.View /** A piece of UI that can be expanded into a Dialog or an Activity. */ interface Expandable { /** - * Create an [ActivityLaunchAnimator.Controller] that can be used to expand this [Expandable] - * into an Activity, or return `null` if this [Expandable] should not be animated (e.g. if it is - * currently not attached or visible). + * Create an [ActivityTransitionAnimator.Controller] that can be used to expand this + * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated + * (e.g. if it is currently not attached or visible). * * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor] * associated to the launch that will use this controller. */ - fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller? + fun activityLaunchController(cujType: Int? = null): ActivityTransitionAnimator.Controller? /** * Create a [DialogLaunchAnimator.Controller] that can be used to expand this [Expandable] into @@ -49,8 +49,8 @@ interface Expandable { return object : Expandable { override fun activityLaunchController( cujType: Int?, - ): ActivityLaunchAnimator.Controller? { - return ActivityLaunchAnimator.Controller.fromView(view, cujType) + ): ActivityTransitionAnimator.Controller? { + return ActivityTransitionAnimator.Controller.fromView(view, cujType) } override fun dialogLaunchController( diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 03f10f9ac7e3..f7148d779828 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -42,15 +42,15 @@ import kotlin.math.roundToInt private const val TAG = "GhostedViewLaunchAnimatorController" /** - * A base implementation of [ActivityLaunchAnimator.Controller] which creates a [ghost][GhostView] - * of [ghostedView] as well as an expandable background view, which are drawn and animated instead - * of the ghosted view. + * A base implementation of [ActivityTransitionAnimator.Controller] which creates a + * [ghost][GhostView] of [ghostedView] as well as an expandable background view, which are drawn and + * animated instead of the ghosted view. * * Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown * during this controller instantiation. * - * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView] + * Note: Avoid instantiating this directly and call [ActivityTransitionAnimator.Controller.fromView] * whenever possible instead. */ open class GhostedViewLaunchAnimatorController @@ -63,7 +63,7 @@ constructor( private val cujType: Int? = null, private var interactionJankMonitor: InteractionJankMonitor = InteractionJankMonitor.getInstance(), -) : ActivityLaunchAnimator.Controller { +) : ActivityTransitionAnimator.Controller { /** The container to which we will add the ghost view and expanding background. */ override var transitionContainer = ghostedView.rootView as ViewGroup diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt index 1020263ee7f6..84e5725b6ada 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt @@ -40,7 +40,7 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable @@ -135,7 +135,7 @@ internal class ExpandableControllerImpl( object : Expandable { override fun activityLaunchController( cujType: Int?, - ): ActivityLaunchAnimator.Controller? { + ): ActivityTransitionAnimator.Controller? { if (!isComposed.value) { return null } @@ -176,7 +176,7 @@ internal class ExpandableControllerImpl( linearProgress: Float ) { // We copy state given that it's always the same object that is mutated by - // ActivityLaunchAnimator. + // ActivityTransitionAnimator. animatorState.value = TransitionAnimator.State( state.top, @@ -256,11 +256,11 @@ internal class ExpandableControllerImpl( } } - /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */ - private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller { + /** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */ + private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller { val delegate = transitionController() return object : - ActivityLaunchAnimator.Controller, TransitionAnimator.Controller by delegate { + ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate { override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { delegate.onTransitionAnimationStart(isExpandingFullyAbove) overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index ff53ff256931..378a1e4858e8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -34,6 +34,7 @@ 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.map import kotlinx.coroutines.flow.stateIn @@ -43,23 +44,21 @@ class LockscreenScene @Inject constructor( @Application private val applicationScope: CoroutineScope, - private val viewModel: LockscreenSceneViewModel, + viewModel: LockscreenSceneViewModel, private val lockscreenContent: Lazy<LockscreenContent>, ) : ComposableScene { override val key = SceneKey.Lockscreen override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = - viewModel.upDestinationSceneKey - .map { pageKey -> - destinationScenes(up = pageKey, left = viewModel.leftDestinationSceneKey) - } + combine(viewModel.upDestinationSceneKey, viewModel.leftDestinationSceneKey, ::Pair) + .map { (upKey, leftKey) -> destinationScenes(up = upKey, left = leftKey) } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, initialValue = destinationScenes( up = viewModel.upDestinationSceneKey.value, - left = viewModel.leftDestinationSceneKey, + left = viewModel.leftDestinationSceneKey.value, ) ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index d70f82fe8ab7..ef6ae2ecfec9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -19,6 +19,7 @@ package com.android.systemui.notifications.ui.composable import android.util.Log import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -140,6 +141,8 @@ fun SceneScope.NotificationScrollingStack( ) { val density = LocalDensity.current val screenCornerRadius = LocalScreenCornerRadius.current + val scrollState = rememberScrollState() + val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f) val expansionFraction by viewModel.expandFraction.collectAsState(0f) val navBarHeight = @@ -180,11 +183,28 @@ fun SceneScope.NotificationScrollingStack( // if contentHeight drops below minimum visible scrim height while scrim is // expanded, reset scrim offset. - LaunchedEffect(contentHeight, screenHeight, maxScrimTop, scrimOffset) { + LaunchedEffect(contentHeight, scrimOffset) { snapshotFlow { contentHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f } .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f } } + // if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly. + LaunchedEffect(syntheticScroll, scrimOffset, scrollState) { + snapshotFlow { syntheticScroll.value } + .collect { delta -> + val minOffset = minScrimOffset() + if (scrimOffset.value > minOffset) { + val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f) + scrimOffset.value = (scrimOffset.value - delta).coerceAtLeast(minOffset) + if (remainingDelta > 0f) { + scrollState.scrollBy(remainingDelta) + } + } else { + scrollState.scrollTo(delta.roundToInt()) + } + } + } + Box( modifier = modifier @@ -260,7 +280,7 @@ fun SceneScope.NotificationScrollingStack( ) } ) - .verticalScroll(rememberScrollState()) + .verticalScroll(scrollState) .fillMaxWidth() .height { (contentHeight.value + navBarHeight).roundToInt() }, ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index b3d2bc994c08..c8fbad4f4eef 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -46,7 +46,7 @@ internal class SceneGestureHandler( val draggable: DraggableHandler = SceneDraggableHandler(this) private var _swipeTransition: SwipeTransition? = null - internal var swipeTransition: SwipeTransition + private var swipeTransition: SwipeTransition get() = _swipeTransition ?: error("SwipeTransition needs to be initialized") set(value) { _swipeTransition = value @@ -92,10 +92,6 @@ internal class SceneGestureHandler( /** The [Swipes] associated to the current gesture. */ private var swipes: Swipes? = null - /** The [UserActionResult] associated to up and down swipes. */ - private var upOrLeftResult: UserActionResult? = null - private var downOrRightResult: UserActionResult? = null - /** * Whether we should immediately intercept a gesture. * @@ -128,7 +124,7 @@ internal class SceneGestureHandler( // This [transition] was already driving the animation: simply take over it. // Stop animating and start from where the current offset. swipeTransition.cancelOffsetAnimation() - updateSwipesResults(swipeTransition._fromScene) + swipes!!.updateSwipesResults(swipeTransition._fromScene) return } @@ -144,16 +140,24 @@ internal class SceneGestureHandler( } val fromScene = layoutImpl.scene(transitionState.currentScene) - updateSwipes(fromScene, startedPosition, pointersDown) - - val result = - findUserActionResult(fromScene, directionOffset = overSlop, updateSwipesResults = true) - ?: return - updateTransition(SwipeTransition(fromScene, result), force = true) - } + val newSwipes = computeSwipes(fromScene, startedPosition, pointersDown) + swipes = newSwipes + val result = newSwipes.findUserActionResult(fromScene, overSlop, true) + + // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be + // defined. + if (result == null) return + + val newSwipeTransition = + SwipeTransition( + fromScene = fromScene, + result = result, + swipes = newSwipes, + layoutImpl = layoutImpl, + orientation = orientation + ) - private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) { - this.swipes = computeSwipes(fromScene, startedPosition, pointersDown) + updateTransition(newSwipeTransition, force = true) } private fun computeSwipes( @@ -210,13 +214,6 @@ internal class SceneGestureHandler( } } - private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float { - val targetSize = this.targetSize - return with(distance ?: DefaultSwipeDistance) { - layoutImpl.density.absoluteDistance(targetSize, orientation) - } - } - internal fun onDrag(delta: Float) { if (delta == 0f || !isDrivingTransition) return swipeTransition.dragOffset += delta @@ -226,15 +223,17 @@ internal class SceneGestureHandler( val isNewFromScene = fromScene.key != swipeTransition.fromScene val result = - findUserActionResult( - fromScene, - swipeTransition.dragOffset, - updateSwipesResults = isNewFromScene, + swipes!!.findUserActionResult( + fromScene = fromScene, + directionOffset = swipeTransition.dragOffset, + updateSwipesResults = isNewFromScene ) - ?: run { - onDragStopped(delta, true) - return - } + + if (result == null) { + onDragStopped(velocity = delta, canChangeScene = true) + return + } + swipeTransition.dragOffset += acceleratedOffset if ( @@ -242,25 +241,20 @@ internal class SceneGestureHandler( result.toScene != swipeTransition.toScene || result.transitionKey != swipeTransition.key ) { - updateTransition( - SwipeTransition(fromScene, result).apply { - this.dragOffset = swipeTransition.dragOffset - } - ) + val newSwipeTransition = + SwipeTransition( + fromScene = fromScene, + result = result, + swipes = swipes!!, + layoutImpl = layoutImpl, + orientation = orientation + ) + .apply { dragOffset = swipeTransition.dragOffset } + + updateTransition(newSwipeTransition) } } - private fun updateSwipesResults(fromScene: Scene) { - val (upOrLeftResult, downOrRightResult) = - computeSwipesResults( - fromScene, - this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()") - ) - - this.upOrLeftResult = upOrLeftResult - this.downOrRightResult = downOrRightResult - } - private fun computeSwipesResults( fromScene: Scene, swipes: Swipes @@ -295,74 +289,20 @@ internal class SceneGestureHandler( // If the swipe was not committed, don't do anything. if (swipeTransition._currentScene != toScene) { - return Pair(fromScene, 0f) + return fromScene to 0f } // If the offset is past the distance then let's change fromScene so that the user can swipe // to the next screen or go back to the previous one. val offset = swipeTransition.dragOffset - return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) { - Pair(toScene, absoluteDistance) - } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) { - Pair(toScene, -absoluteDistance) - } else { - Pair(fromScene, 0f) - } - } - - /** - * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset]. - * - * @param fromScene the scene from which we look for the target - * @param directionOffset signed float that indicates the direction. Positive is down or right - * negative is up or left. - * @param updateSwipesResults whether the target scenes should be updated to the current values - * held in the Scenes map. Usually we don't want to update them while doing a drag, because - * this could change the target scene (jump cutting) to a different scene, when some system - * state changed the targets the background. However, an update is needed any time we - * calculate the targets for a new fromScene. - * @return null when there are no targets in either direction. If one direction is null and you - * drag into the null direction this function will return the opposite direction, assuming - * that the users intention is to start the drag into the other direction eventually. If - * [directionOffset] is 0f and both direction are available, it will default to - * [upOrLeftResult]. - */ - private fun findUserActionResult( - fromScene: Scene, - directionOffset: Float, - updateSwipesResults: Boolean, - ): UserActionResult? { - if (updateSwipesResults) updateSwipesResults(fromScene) - - return when { - upOrLeftResult == null && downOrRightResult == null -> null - (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null -> - upOrLeftResult - else -> downOrRightResult - } - } - - /** - * A strict version of [findUserActionResult] that will return null when there is no Scene in - * [directionOffset] direction - */ - private fun findUserActionResultStrict(directionOffset: Float): UserActionResult? { - return when { - directionOffset > 0f -> upOrLeftResult - directionOffset < 0f -> downOrRightResult - else -> null - } - } - - private fun computeAbsoluteDistance( - fromScene: Scene, - result: UserActionResult, - ): Float { - return if (result == upOrLeftResult) { - -fromScene.getAbsoluteDistance(result.distance) + return if (offset <= -absoluteDistance && swipes!!.upOrLeftResult?.toScene == toScene.key) { + toScene to absoluteDistance + } else if ( + offset >= absoluteDistance && swipes!!.downOrRightResult?.toScene == toScene.key + ) { + toScene to -absoluteDistance } else { - check(result == downOrRightResult) - fromScene.getAbsoluteDistance(result.distance) + fromScene to 0f } } @@ -430,19 +370,24 @@ internal class SceneGestureHandler( if (startFromIdlePosition) { // If there is a target scene, we start the overscroll animation. - val result = - findUserActionResultStrict(velocity) - ?: run { - // We will not animate - layoutState.finishTransition(swipeTransition, idleScene = fromScene.key) - return - } + val result = swipes!!.findUserActionResultStrict(velocity) + if (result == null) { + // We will not animate + layoutState.finishTransition(swipeTransition, idleScene = fromScene.key) + return + } - updateTransition( - SwipeTransition(fromScene, result).apply { - _currentScene = swipeTransition._currentScene - } - ) + val newSwipeTransition = + SwipeTransition( + fromScene = fromScene, + result = result, + swipes = swipes!!, + layoutImpl = layoutImpl, + orientation = orientation + ) + .apply { _currentScene = swipeTransition._currentScene } + + updateTransition(newSwipeTransition) animateTo(targetScene = fromScene, targetOffset = 0f) } else { // We were between two scenes: animate to the initial scene. @@ -486,134 +431,220 @@ internal class SceneGestureHandler( } } - private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition { - return SwipeTransition( - result.transitionKey, - fromScene, - layoutImpl.scene(result.toScene), - computeAbsoluteDistance(fromScene, result), - ) + companion object { + private const val TAG = "SceneGestureHandler" } +} - internal class SwipeTransition( - val key: TransitionKey?, - val _fromScene: Scene, - val _toScene: Scene, - /** - * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is - * above or to the left of [toScene]. - */ - val distance: Float, - ) : TransitionState.Transition(_fromScene.key, _toScene.key) { - var _currentScene by mutableStateOf(_fromScene) - override val currentScene: SceneKey - get() = _currentScene.key - - override val progress: Float - get() { - val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset - return offset / distance - } +private fun SwipeTransition( + fromScene: Scene, + result: UserActionResult, + swipes: Swipes, + layoutImpl: SceneTransitionLayoutImpl, + orientation: Orientation, +): SwipeTransition { + val upOrLeftResult = swipes.upOrLeftResult + val downOrRightResult = swipes.downOrRightResult + val userActionDistance = result.distance ?: DefaultSwipeDistance + val absoluteDistance = + with(userActionDistance) { + layoutImpl.density.absoluteDistance(fromScene.targetSize, orientation) + } + + return SwipeTransition( + key = result.transitionKey, + _fromScene = fromScene, + _toScene = layoutImpl.scene(result.toScene), + distance = + when (result) { + upOrLeftResult -> -absoluteDistance + downOrRightResult -> absoluteDistance + else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)") + }, + ) +} + +private class SwipeTransition( + val key: TransitionKey?, + val _fromScene: Scene, + val _toScene: Scene, + /** + * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above + * or to the left of [toScene] + */ + val distance: Float, +) : TransitionState.Transition(_fromScene.key, _toScene.key) { + var _currentScene by mutableStateOf(_fromScene) + override val currentScene: SceneKey + get() = _currentScene.key + + override val progress: Float + get() { + val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset + return offset / distance + } - override val isInitiatedByUserInput = true + override val isInitiatedByUserInput = true - /** The current offset caused by the drag gesture. */ - var dragOffset by mutableFloatStateOf(0f) + /** The current offset caused by the drag gesture. */ + var dragOffset by mutableFloatStateOf(0f) - /** - * Whether the offset is animated (the user lifted their finger) or if it is driven by - * gesture. - */ - var isAnimatingOffset by mutableStateOf(false) + /** + * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture. + */ + var isAnimatingOffset by mutableStateOf(false) - // If we are not animating offset, it means the offset is being driven by the user's finger. - override val isUserInputOngoing: Boolean - get() = !isAnimatingOffset + // If we are not animating offset, it means the offset is being driven by the user's finger. + override val isUserInputOngoing: Boolean + get() = !isAnimatingOffset - /** The animatable used to animate the offset once the user lifted its finger. */ - val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold) + /** The animatable used to animate the offset once the user lifted its finger. */ + val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold) - /** Job to check that there is at most one offset animation in progress. */ - private var offsetAnimationJob: Job? = null + /** Job to check that there is at most one offset animation in progress. */ + private var offsetAnimationJob: Job? = null - /** The spec to use when animating this transition to either [fromScene] or [toScene]. */ - lateinit var swipeSpec: SpringSpec<Float> + /** The spec to use when animating this transition to either [fromScene] or [toScene]. */ + lateinit var swipeSpec: SpringSpec<Float> - /** Ends any previous [offsetAnimationJob] and runs the new [job]. */ - private fun startOffsetAnimation(job: () -> Job) { - cancelOffsetAnimation() - offsetAnimationJob = job() - } + /** Ends any previous [offsetAnimationJob] and runs the new [job]. */ + private fun startOffsetAnimation(job: () -> Job) { + cancelOffsetAnimation() + offsetAnimationJob = job() + } + + /** Cancel any ongoing offset animation. */ + // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at + // the same time. + fun cancelOffsetAnimation() { + offsetAnimationJob?.cancel() + finishOffsetAnimation() + } - /** Cancel any ongoing offset animation. */ - // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at - // the same time. - fun cancelOffsetAnimation() { - offsetAnimationJob?.cancel() - finishOffsetAnimation() + fun finishOffsetAnimation() { + if (isAnimatingOffset) { + isAnimatingOffset = false + dragOffset = offsetAnimatable.value } + } - fun finishOffsetAnimation() { - if (isAnimatingOffset) { - isAnimatingOffset = false - dragOffset = offsetAnimatable.value + fun animateOffset( + // TODO(b/317063114) The CoroutineScope should be removed. + coroutineScope: CoroutineScope, + initialVelocity: Float, + targetOffset: Float, + onAnimationCompleted: () -> Unit, + ) { + startOffsetAnimation { + coroutineScope.launch { + animateOffset(targetOffset, initialVelocity) + onAnimationCompleted() } } + } - fun animateOffset( - // TODO(b/317063114) The CoroutineScope should be removed. - coroutineScope: CoroutineScope, - initialVelocity: Float, - targetOffset: Float, - onAnimationCompleted: () -> Unit, - ) { - startOffsetAnimation { - coroutineScope.launch { - animateOffset(targetOffset, initialVelocity) - onAnimationCompleted() - } - } + private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) { + if (!isAnimatingOffset) { + offsetAnimatable.snapTo(dragOffset) } + isAnimatingOffset = true - private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) { - if (!isAnimatingOffset) { - offsetAnimatable.snapTo(dragOffset) - } - isAnimatingOffset = true + offsetAnimatable.animateTo( + targetValue = targetOffset, + animationSpec = swipeSpec, + initialVelocity = initialVelocity, + ) - offsetAnimatable.animateTo( - targetValue = targetOffset, - animationSpec = swipeSpec, - initialVelocity = initialVelocity, - ) + finishOffsetAnimation() + } +} + +private object DefaultSwipeDistance : UserActionDistance { + override fun Density.absoluteDistance( + fromSceneSize: IntSize, + orientation: Orientation, + ): Float { + return when (orientation) { + Orientation.Horizontal -> fromSceneSize.width + Orientation.Vertical -> fromSceneSize.height + }.toFloat() + } +} - finishOffsetAnimation() +/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */ +private class Swipes( + val upOrLeft: Swipe?, + val downOrRight: Swipe?, + val upOrLeftNoSource: Swipe?, + val downOrRightNoSource: Swipe?, +) { + /** The [UserActionResult] associated to up and down swipes. */ + var upOrLeftResult: UserActionResult? = null + var downOrRightResult: UserActionResult? = null + + fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> { + val userActions = fromScene.userActions + fun result(swipe: Swipe?): UserActionResult? { + return userActions[swipe ?: return null] } + + val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource) + val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource) + return upOrLeftResult to downOrRightResult } - companion object { - private const val TAG = "SceneGestureHandler" + fun updateSwipesResults(fromScene: Scene) { + val (upOrLeftResult, downOrRightResult) = computeSwipesResults(fromScene) + + this.upOrLeftResult = upOrLeftResult + this.downOrRightResult = downOrRightResult } - private object DefaultSwipeDistance : UserActionDistance { - override fun Density.absoluteDistance( - fromSceneSize: IntSize, - orientation: Orientation, - ): Float { - return when (orientation) { - Orientation.Horizontal -> fromSceneSize.width - Orientation.Vertical -> fromSceneSize.height - }.toFloat() + /** + * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset]. + * + * @param fromScene the scene from which we look for the target + * @param directionOffset signed float that indicates the direction. Positive is down or right + * negative is up or left. + * @param updateSwipesResults whether the target scenes should be updated to the current values + * held in the Scenes map. Usually we don't want to update them while doing a drag, because + * this could change the target scene (jump cutting) to a different scene, when some system + * state changed the targets the background. However, an update is needed any time we + * calculate the targets for a new fromScene. + * @return null when there are no targets in either direction. If one direction is null and you + * drag into the null direction this function will return the opposite direction, assuming + * that the users intention is to start the drag into the other direction eventually. If + * [directionOffset] is 0f and both direction are available, it will default to + * [upOrLeftResult]. + */ + fun findUserActionResult( + fromScene: Scene, + directionOffset: Float, + updateSwipesResults: Boolean, + ): UserActionResult? { + if (updateSwipesResults) { + updateSwipesResults(fromScene) + } + + return when { + upOrLeftResult == null && downOrRightResult == null -> null + (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null -> + upOrLeftResult + else -> downOrRightResult } } - /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */ - private class Swipes( - val upOrLeft: Swipe?, - val downOrRight: Swipe?, - val upOrLeftNoSource: Swipe?, - val downOrRightNoSource: Swipe?, - ) + /** + * A strict version of [findUserActionResult] that will return null when there is no Scene in + * [directionOffset] direction + */ + fun findUserActionResultStrict(directionOffset: Float): UserActionResult? { + return when { + directionOffset > 0f -> upOrLeftResult + directionOffset < 0f -> downOrRightResult + else -> null + } + } } private class SceneDraggableHandler( diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index dacbdb484d0c..c91d29880ffb 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -127,6 +127,9 @@ class SceneGestureHandlerTest { val progress: Float get() = (transitionState as Transition).progress + val isUserInputOngoing: Boolean + get() = (transitionState as Transition).isUserInputOngoing + fun advanceUntilIdle() { testScope.testScheduler.advanceUntilIdle() } @@ -538,12 +541,11 @@ class SceneGestureHandlerTest { onDragStopped(velocity = velocityThreshold) assertTransition(currentScene = SceneC) - assertThat(sceneGestureHandler.isDrivingTransition).isTrue() - assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue() + assertThat(isUserInputOngoing).isFalse() // Start a new gesture while the offset is animating onDragStartedImmediately() - assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse() + assertThat(isUserInputOngoing).isTrue() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index f7743e2814f0..259f3498d4c3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -37,7 +37,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Flags import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel @@ -106,7 +106,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsController: UdfpsController @Mock private lateinit var udfpsView: UdfpsView @Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy - @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator + @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @@ -167,7 +167,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { reason, controllerCallback, onTouch, - activityLaunchAnimator, + mActivityTransitionAnimator, primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 90c3c14bbc4f..529403a710b5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -75,7 +75,7 @@ import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; import com.android.systemui.biometrics.udfps.InteractionEvent; @@ -203,7 +203,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private SystemUIDialogManager mSystemUIDialogManager; @Mock - private ActivityLaunchAnimator mActivityLaunchAnimator; + private ActivityTransitionAnimator mActivityTransitionAnimator; @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock @@ -331,7 +331,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, - mActivityLaunchAnimator, + mActivityTransitionAnimator, mBiometricExecutor, mPrimaryBouncerInteractor, mShadeInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java index 7d9c2f96ef58..324534fb766c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java @@ -26,7 +26,7 @@ import android.content.Context; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; @@ -70,7 +70,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; protected @Mock SystemUIDialogManager mDialogManager; protected @Mock UdfpsController mUdfpsController; - protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator; + protected @Mock ActivityTransitionAnimator mActivityTransitionAnimator; protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; protected @Mock ShadeInteractor mShadeInteractor; protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor; @@ -148,7 +148,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, mDialogManager, mUdfpsController, - mActivityLaunchAnimator, + mActivityTransitionAnimator, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsKeyguardAccessibilityDelegate, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index bd9ca3035a07..b4e2eab22c88 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -16,26 +16,18 @@ package com.android.systemui.communal.data.repository -import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.Flags -import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.user.data.repository.fakeUserRepository -import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest @@ -48,37 +40,20 @@ import org.junit.runner.RunWith class CommunalRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: CommunalRepositoryImpl - private lateinit var secureSettings: FakeSettings - private lateinit var userRepository: FakeUserRepository - private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneContainerRepository = kosmos.sceneContainerRepository @Before fun setUp() { - secureSettings = FakeSettings() - userRepository = kosmos.fakeUserRepository - - val listOfUserInfo = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(listOfUserInfo) - - kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) } - mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) - underTest = createRepositoryImpl(false) } private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl { return CommunalRepositoryImpl( testScope.backgroundScope, - testScope.backgroundScope, - kosmos.testDispatcher, - kosmos.fakeFeatureFlagsClassic, kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled }, sceneContainerRepository, - kosmos.fakeUserRepository, - secureSettings, ) } @@ -159,29 +134,4 @@ class CommunalRepositoryImplTest : SysuiTestCase() { assertThat(transitionState) .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)) } - - @Test - fun communalEnabledState_false_whenGlanceableHubSettingFalse() = - testScope.runTest { - userRepository.setSelectedUserInfo(MAIN_USER_INFO) - secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id) - - val communalEnabled by collectLastValue(underTest.communalEnabledState) - assertThat(communalEnabled).isFalse() - } - - @Test - fun communalEnabledState_true_whenGlanceableHubSettingTrue() = - testScope.runTest { - userRepository.setSelectedUserInfo(MAIN_USER_INFO) - secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id) - - val communalEnabled by collectLastValue(underTest.communalEnabledState) - assertThat(communalEnabled).isTrue() - } - - companion object { - private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" - private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt new file mode 100644 index 000000000000..0aca16d9aeaa --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -0,0 +1,153 @@ +/* + * 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.communal.data.repository + +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL +import android.app.admin.devicePolicyManager +import android.content.Intent +import android.content.pm.UserInfo +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.communal.data.model.DisabledReason +import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_ENABLED +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalSettingsRepositoryImplTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private lateinit var underTest: CommunalSettingsRepository + + @Before + fun setUp() { + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE) + setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE) + underTest = kosmos.communalSettingsRepository + } + + @EnableFlags(FLAG_COMMUNAL_HUB) + @Test + fun secondaryUserIsInvalid() = + testScope.runTest { + val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER)) + + assertThat(enabledState?.enabled).isFalse() + assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER) + } + + @EnableFlags(FLAG_COMMUNAL_HUB) + @Test + fun classicFlagIsDisabled() = + testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) + assertThat(enabledState?.enabled).isFalse() + assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG) + } + + @DisableFlags(FLAG_COMMUNAL_HUB) + @Test + fun communalHubFlagIsDisabled() = + testScope.runTest { + val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) + assertThat(enabledState?.enabled).isFalse() + assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG) + } + + @EnableFlags(FLAG_COMMUNAL_HUB) + @Test + fun hubIsDisabledByUser() = + testScope.runTest { + kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id) + val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) + assertThat(enabledState?.enabled).isFalse() + assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING) + + kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id) + assertThat(enabledState?.enabled).isFalse() + + kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id) + assertThat(enabledState?.enabled).isTrue() + } + + @EnableFlags(FLAG_COMMUNAL_HUB) + @Test + fun hubIsDisabledByDevicePolicy() = + testScope.runTest { + val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) + assertThat(enabledState?.enabled).isTrue() + + setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL) + assertThat(enabledState?.enabled).isFalse() + assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_DEVICE_POLICY) + } + + @EnableFlags(FLAG_COMMUNAL_HUB) + @Test + fun hubIsDisabledByUserAndDevicePolicy() = + testScope.runTest { + val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) + assertThat(enabledState?.enabled).isTrue() + + kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id) + setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL) + + assertThat(enabledState?.enabled).isFalse() + assertThat(enabledState) + .containsExactly( + DisabledReason.DISABLED_REASON_DEVICE_POLICY, + DisabledReason.DISABLED_REASON_USER_SETTING, + ) + } + + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { + whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) + .thenReturn(disabledFlags) + kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + ) + } + + private companion object { + val PRIMARY_USER = + UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN) + val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt index 6a3fc2a060eb..824733bcc47b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -59,7 +60,7 @@ class CommunalInteractorCommunalDisabledTest : SysuiTestCase() { widgetRepository = kosmos.fakeCommunalWidgetRepository keyguardRepository = kosmos.fakeKeyguardRepository - communalRepository.setIsCommunalEnabled(false) + mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB) underTest = kosmos.communalInteractor } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index c5485c5c5c33..3ac19e4672c3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -23,6 +23,7 @@ import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED import android.widget.RemoteViews import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository @@ -41,6 +42,8 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.testScope @@ -109,12 +112,19 @@ class CommunalInteractorTest : SysuiTestCase() { whenever(secondaryUser.isMain).thenReturn(false) userRepository.setUserInfos(listOf(mainUser, secondaryUser)) + kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) + mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) + underTest = kosmos.communalInteractor } @Test fun communalEnabled_true() = - testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() } + testScope.runTest { + userRepository.setSelectedUserInfo(mainUser) + runCurrent() + assertThat(underTest.isCommunalEnabled).isTrue() + } @Test fun isCommunalAvailable_storageUnlockedAndMainUser_true() = @@ -125,7 +135,6 @@ class CommunalInteractorTest : SysuiTestCase() { keyguardRepository.setIsEncryptedOrLockdown(false) userRepository.setSelectedUserInfo(mainUser) keyguardRepository.setKeyguardShowing(true) - communalRepository.setCommunalEnabledState(true) assertThat(isAvailable).isTrue() } @@ -139,7 +148,6 @@ class CommunalInteractorTest : SysuiTestCase() { keyguardRepository.setIsEncryptedOrLockdown(true) userRepository.setSelectedUserInfo(mainUser) keyguardRepository.setKeyguardShowing(true) - communalRepository.setCommunalEnabledState(true) assertThat(isAvailable).isFalse() } @@ -153,7 +161,6 @@ class CommunalInteractorTest : SysuiTestCase() { keyguardRepository.setIsEncryptedOrLockdown(false) userRepository.setSelectedUserInfo(secondaryUser) keyguardRepository.setKeyguardShowing(true) - communalRepository.setCommunalEnabledState(true) assertThat(isAvailable).isFalse() } @@ -167,7 +174,6 @@ class CommunalInteractorTest : SysuiTestCase() { keyguardRepository.setIsEncryptedOrLockdown(false) userRepository.setSelectedUserInfo(mainUser) keyguardRepository.setDreaming(true) - communalRepository.setCommunalEnabledState(true) assertThat(isAvailable).isTrue() } @@ -175,13 +181,14 @@ class CommunalInteractorTest : SysuiTestCase() { @Test fun isCommunalAvailable_communalDisabled_false() = testScope.runTest { + mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB) + val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() keyguardRepository.setIsEncryptedOrLockdown(false) userRepository.setSelectedUserInfo(mainUser) keyguardRepository.setKeyguardShowing(true) - communalRepository.setCommunalEnabledState(false) assertThat(isAvailable).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt index 6c87e0f2eb23..ceb7fac1046f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -22,12 +22,15 @@ import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.testScope @@ -35,11 +38,13 @@ import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi 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) class CommunalTutorialInteractorTest : SysuiTestCase() { @@ -62,6 +67,8 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { userRepository = kosmos.fakeUserRepository userRepository.setUserInfos(listOf(MAIN_USER_INFO)) + kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) + mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) underTest = kosmos.communalTutorialInteractor } @@ -127,6 +134,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { testScope.runTest { val tutorialSettingState by collectLastValue(communalTutorialRepository.tutorialSettingState) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) communalRepository.setIsCommunalHubShowing(true) @@ -139,6 +147,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { testScope.runTest { val tutorialSettingState by collectLastValue(communalTutorialRepository.tutorialSettingState) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) communalRepository.setIsCommunalHubShowing(true) @@ -151,6 +160,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { testScope.runTest { val tutorialSettingState by collectLastValue(communalTutorialRepository.tutorialSettingState) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) communalRepository.setIsCommunalHubShowing(true) @@ -163,6 +173,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { testScope.runTest { val tutorialSettingState by collectLastValue(communalTutorialRepository.tutorialSettingState) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) communalRepository.setIsCommunalHubShowing(false) @@ -175,6 +186,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { testScope.runTest { val tutorialSettingState by collectLastValue(communalTutorialRepository.tutorialSettingState) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) communalRepository.setIsCommunalHubShowing(true) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) @@ -188,6 +200,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { testScope.runTest { val tutorialSettingState by collectLastValue(communalTutorialRepository.tutorialSettingState) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) communalRepository.setIsCommunalHubShowing(true) communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) @@ -198,14 +211,11 @@ class CommunalTutorialInteractorTest : SysuiTestCase() { private suspend fun setCommunalAvailable(available: Boolean) { if (available) { - communalRepository.setIsCommunalEnabled(true) - communalRepository.setCommunalEnabledState(true) keyguardRepository.setIsEncryptedOrLockdown(false) userRepository.setSelectedUserInfo(MAIN_USER_INFO) keyguardRepository.setKeyguardShowing(true) } else { - communalRepository.setIsCommunalEnabled(false) - communalRepository.setCommunalEnabledState(false) + keyguardRepository.setIsEncryptedOrLockdown(true) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 73d309118548..f70b6a5a170f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -22,12 +22,12 @@ import android.provider.Settings import android.widget.RemoteViews import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.communalInteractor @@ -37,6 +37,8 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.testScope @@ -92,7 +94,8 @@ class CommunalViewModelTest : SysuiTestCase() { mediaRepository = kosmos.fakeCommunalMediaRepository userRepository = kosmos.fakeUserRepository - kosmos.fakeCommunalRepository.setCommunalEnabledState(true) + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) underTest = CommunalViewModel( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt index 032d76f0dceb..8488843905f7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt @@ -19,12 +19,15 @@ package com.android.systemui.communal.widgets import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase -import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_ENABLED import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher @@ -33,6 +36,7 @@ import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow @@ -62,6 +66,8 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO)) + kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) + mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) appWidgetIdToRemove = MutableSharedFlow() whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove) @@ -169,7 +175,8 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { fakeKeyguardRepository.setIsEncryptedOrLockdown(false) fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) fakeKeyguardRepository.setKeyguardShowing(true) - fakeCommunalRepository.setCommunalEnabledState(available) + val settingsValue = if (available) 1 else 0 + fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, settingsValue, MAIN_USER_INFO.id) } private companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt index a613ad8852f6..076834097717 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt @@ -23,14 +23,14 @@ import android.service.quickaccesswallet.QuickAccessWalletClient import android.service.quickaccesswallet.WalletCard import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -76,26 +76,28 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun affordance_keyguardShowing_hasWalletCard_visibleModel() = testScope.runTest { - setUpState() - - val latest by collectLastValue(underTest.lockScreenState) - - val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible - assertThat(visibleModel.icon) - .isEqualTo( - Icon.Loaded( - drawable = ICON, - contentDescription = - ContentDescription.Resource( - res = R.string.accessibility_wallet_button, - ), + fun affordance_keyguardShowing_hasWalletCard_visibleModel() = + testScope.runTest { + setUpState() + + val latest by collectLastValue(underTest.lockScreenState) + + val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible + assertThat(visibleModel.icon) + .isEqualTo( + Icon.Loaded( + drawable = ICON, + contentDescription = + ContentDescription.Resource( + res = R.string.accessibility_wallet_button, + ), + ) ) - ) - } + } @Test - fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() = testScope.runTest { + fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() = + testScope.runTest { setUpState(cardType = WalletCard.CARD_TYPE_NON_PAYMENT) val latest by collectLastValue(underTest.lockScreenState) @@ -104,54 +106,58 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun affordance_keyguardShowing_hasPaymentCard_visibleModel() = testScope.runTest { - setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT) - - val latest by collectLastValue(underTest.lockScreenState) - - val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible - assertThat(visibleModel.icon) - .isEqualTo( - Icon.Loaded( - drawable = ICON, - contentDescription = - ContentDescription.Resource( - res = R.string.accessibility_wallet_button, - ), + fun affordance_keyguardShowing_hasPaymentCard_visibleModel() = + testScope.runTest { + setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT) + + val latest by collectLastValue(underTest.lockScreenState) + + val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible + assertThat(visibleModel.icon) + .isEqualTo( + Icon.Loaded( + drawable = ICON, + contentDescription = + ContentDescription.Resource( + res = R.string.accessibility_wallet_button, + ), + ) ) - ) - } + } @Test - fun affordance_walletFeatureNotEnabled_modelIsNone() = testScope.runTest { - setUpState(isWalletFeatureAvailable = false) + fun affordance_walletFeatureNotEnabled_modelIsNone() = + testScope.runTest { + setUpState(isWalletFeatureAvailable = false) - val latest by collectLastValue(underTest.lockScreenState) + val latest by collectLastValue(underTest.lockScreenState) - assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) - } + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } @Test - fun affordance_queryNotSuccessful_modelIsNone() = testScope.runTest { - setUpState(isWalletQuerySuccessful = false) + fun affordance_queryNotSuccessful_modelIsNone() = + testScope.runTest { + setUpState(isWalletQuerySuccessful = false) - val latest by collectLastValue(underTest.lockScreenState) + val latest by collectLastValue(underTest.lockScreenState) - assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) - } + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } @Test - fun affordance_noSelectedCard_modelIsNone() = testScope.runTest { - setUpState(hasSelectedCard = false) + fun affordance_noSelectedCard_modelIsNone() = + testScope.runTest { + setUpState(hasSelectedCard = false) - val latest by collectLastValue(underTest.lockScreenState) + val latest by collectLastValue(underTest.lockScreenState) - assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) - } + assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } @Test fun onQuickAffordanceTriggered() { - val animationController: ActivityLaunchAnimator.Controller = mock() + val animationController: ActivityTransitionAnimator.Controller = mock() val expandable: Expandable = mock { whenever(this.activityLaunchController()).thenReturn(animationController) } @@ -167,42 +173,46 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun getPickerScreenState_default() = testScope.runTest { - setUpState() + fun getPickerScreenState_default() = + testScope.runTest { + setUpState() - assertThat(underTest.getPickerScreenState()) - .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default()) - } + assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default()) + } @Test - fun getPickerScreenState_unavailable() = testScope.runTest { - setUpState( - isWalletServiceAvailable = false, - ) + fun getPickerScreenState_unavailable() = + testScope.runTest { + setUpState( + isWalletServiceAvailable = false, + ) - assertThat(underTest.getPickerScreenState()) - .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) - } + assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice) + } @Test - fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = testScope.runTest { - setUpState( - isWalletFeatureAvailable = false, - ) + fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = + testScope.runTest { + setUpState( + isWalletFeatureAvailable = false, + ) - assertThat(underTest.getPickerScreenState()) - .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) - } + assertThat(underTest.getPickerScreenState()) + .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) + } @Test - fun getPickerScreenState_disabledWhenThereIsNoCard() = testScope.runTest { - setUpState( - hasSelectedCard = false, - ) + fun getPickerScreenState_disabledWhenThereIsNoCard() = + testScope.runTest { + setUpState( + hasSelectedCard = false, + ) - assertThat(underTest.getPickerScreenState()) - .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) - } + assertThat(underTest.getPickerScreenState()) + .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java) + } private fun setUpState( isWalletFeatureAvailable: Boolean = true, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 4595fbfab0d7..72617238cbb1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -18,22 +18,28 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.content.pm.UserInfo +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.communal.data.repository.fakeCommunalRepository -import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -49,9 +55,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } - private val underTest by lazy { - createLockscreenSceneViewModel() - } + private val underTest by lazy { createLockscreenSceneViewModel() } @Test fun upTransitionSceneKey_canSwipeToUnlock_gone() = @@ -80,29 +84,37 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer) } + @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun leftTransitionSceneKey_communalIsEnabled_communal() = testScope.runTest { - kosmos.fakeCommunalRepository.setIsCommunalEnabled(true) - val underTest = createLockscreenSceneViewModel() - - assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal) + with(kosmos.fakeUserRepository) { + setUserInfos(listOf(PRIMARY_USER)) + setSelectedUserInfo(PRIMARY_USER) + } + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey) + assertThat(leftDestinationSceneKey).isEqualTo(SceneKey.Communal) } + @DisableFlags(FLAG_COMMUNAL_HUB) @Test fun leftTransitionSceneKey_communalIsDisabled_null() = testScope.runTest { - kosmos.fakeCommunalRepository.setIsCommunalEnabled(false) - val underTest = createLockscreenSceneViewModel() - - assertThat(underTest.leftDestinationSceneKey).isNull() + with(kosmos.fakeUserRepository) { + setUserInfos(listOf(PRIMARY_USER)) + setSelectedUserInfo(PRIMARY_USER) + } + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey) + assertThat(leftDestinationSceneKey).isNull() } private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { return LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = kosmos.deviceEntryInteractor, - communalInteractor = kosmos.communalInteractor, + communalSettingsInteractor = kosmos.communalSettingsInteractor, longPress = KeyguardLongPressViewModel( interactor = mock(), @@ -110,4 +122,9 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { notifications = kosmos.notificationsPlaceholderViewModel, ) } + + private companion object { + val PRIMARY_USER = + UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 9d3f0d6a93f8..006f429ab98a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -40,7 +40,7 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.classifier.falsingCollector -import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor @@ -130,7 +130,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val sceneInteractor by lazy { kosmos.sceneInteractor } private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } - private val communalInteractor by lazy { kosmos.communalInteractor } + private val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor } private val transitionState by lazy { MutableStateFlow<ObservableTransitionState>( @@ -155,7 +155,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, - communalInteractor = communalInteractor, + communalSettingsInteractor = communalSettingsInteractor, longPress = KeyguardLongPressViewModel( interactor = mock(), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt new file mode 100644 index 000000000000..bec8cfec399b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt @@ -0,0 +1,237 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.domain.interactor + +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.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ActiveNotificationsInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val activeNotificationListRepository = kosmos.activeNotificationListRepository + + private val underTest = kosmos.activeNotificationsInteractor + + @Test + fun testAllNotificationsCount() = + testScope.runTest { + val count by collectLastValue(underTest.allNotificationsCount) + + activeNotificationListRepository.setActiveNotifs(5) + runCurrent() + + assertThat(count).isEqualTo(5) + assertThat(underTest.allNotificationsCountValue).isEqualTo(5) + } + + @Test + fun areAnyNotificationsPresent_isTrue() = + testScope.runTest { + val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) + + activeNotificationListRepository.setActiveNotifs(2) + runCurrent() + + assertThat(areAnyNotificationsPresent).isTrue() + assertThat(underTest.areAnyNotificationsPresentValue).isTrue() + } + + @Test + fun areAnyNotificationsPresent_isFalse() = + testScope.runTest { + val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) + + activeNotificationListRepository.setActiveNotifs(0) + runCurrent() + + assertThat(areAnyNotificationsPresent).isFalse() + assertThat(underTest.areAnyNotificationsPresentValue).isFalse() + } + + @Test + fun testActiveNotificationRanks_sizeMatches() { + testScope.runTest { + val activeNotificationRanks by collectLastValue(underTest.activeNotificationRanks) + + activeNotificationListRepository.setActiveNotifs(5) + runCurrent() + + assertThat(activeNotificationRanks!!.size).isEqualTo(5) + } + } + + @Test + fun clearableNotifications_whenHasClearableAlertingNotifs() = + testScope.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun hasClearableNotifications_whenHasClearableSilentNotifs() = + testScope.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun testHasClearableNotifications_whenHasNoNotifs() = + testScope.runTest { + val hasClearable by collectLastValue(underTest.hasClearableNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 0, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isFalse() + } + + @Test + fun hasClearableAlertingNotifications_whenHasClearableSilentNotifs() = + testScope.runTest { + val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(hasClearable).isFalse() + } + + @Test + fun hasClearableAlertingNotifications_whenHasNoClearableNotifs() = + testScope.runTest { + val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = true, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = true, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isFalse() + } + + @Test + fun hasClearableAlertingNotifications_whenHasAlertingNotifs() = + testScope.runTest { + val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = true, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasClearable).isTrue() + } + + @Test + fun hasNonClearableSilentNotifications_whenHasNonClearableSilentNotifs() = + testScope.runTest { + val hasNonClearable by collectLastValue(underTest.hasNonClearableSilentNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = true, + hasClearableSilentNotifs = false, + ) + runCurrent() + + assertThat(hasNonClearable).isTrue() + } + + @Test + fun testHasNonClearableSilentNotifications_whenHasClearableSilentNotifs() = + testScope.runTest { + val hasNonClearable by collectLastValue(underTest.hasNonClearableSilentNotifications) + + activeNotificationListRepository.notifStats.value = + NotifStats( + numActiveNotifs = 2, + hasNonClearableAlertingNotifs = false, + hasClearableAlertingNotifs = false, + hasNonClearableSilentNotifs = false, + hasClearableSilentNotifs = true, + ) + runCurrent() + + assertThat(hasNonClearable).isFalse() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index cc4ebd4aa4c3..c01f1c71fdd3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -27,7 +27,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.LaunchableView import com.android.systemui.assist.AssistManager import com.android.systemui.keyguard.KeyguardViewMediator @@ -78,7 +78,7 @@ class ActivityStarterImplTest : SysuiTestCase() { @Mock private lateinit var shadeController: ShadeController @Mock private lateinit var shadeViewController: ShadeViewController @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager - @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator + @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager @Mock private lateinit var statusBarWindowController: StatusBarWindowController @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController @@ -109,7 +109,7 @@ class ActivityStarterImplTest : SysuiTestCase() { shadeAnimationInteractor, Lazy { statusBarKeyguardViewManager }, Lazy { notifShadeWindowController }, - activityLaunchAnimator, + mActivityTransitionAnimator, context, DISPLAY_ID, lockScreenUserManager, @@ -149,7 +149,7 @@ class ActivityStarterImplTest : SysuiTestCase() { override fun setShouldBlockVisibilityChanges(block: Boolean) {} } parent.addView(view) - val controller = ActivityLaunchAnimator.Controller.fromView(view) + val controller = ActivityTransitionAnimator.Controller.fromView(view) whenever(pendingIntent.isActivity).thenReturn(true) whenever(keyguardStateController.isShowing).thenReturn(true) whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) @@ -163,7 +163,7 @@ class ActivityStarterImplTest : SysuiTestCase() { ) mainExecutor.runAllReady() - verify(activityLaunchAnimator) + verify(mActivityTransitionAnimator) .startPendingIntentWithAnimation( nullable(), eq(true), diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 643420989d1a..1126ec3382a4 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -20,7 +20,7 @@ import android.content.Intent; import android.os.UserHandle; import android.view.View; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.plugins.annotations.ProvidesInterface; /** @@ -54,7 +54,7 @@ public interface ActivityStarter { */ void startPendingIntentDismissingKeyguard(PendingIntent intent, Runnable intentSentUiThreadCallback, - @Nullable ActivityLaunchAnimator.Controller animationController); + @Nullable ActivityTransitionAnimator.Controller animationController); /** * Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching @@ -64,7 +64,7 @@ public interface ActivityStarter { */ void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent, @Nullable Runnable intentSentUiThreadCallback, - @Nullable ActivityLaunchAnimator.Controller animationController); + @Nullable ActivityTransitionAnimator.Controller animationController); /** * The intent flag can be specified in startActivity(). @@ -72,26 +72,26 @@ public interface ActivityStarter { void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags); void startActivity(Intent intent, boolean dismissShade); default void startActivity(Intent intent, boolean dismissShade, - @Nullable ActivityLaunchAnimator.Controller animationController) { + @Nullable ActivityTransitionAnimator.Controller animationController) { startActivity(intent, dismissShade, animationController, false /* showOverLockscreenWhenLocked */); } void startActivity(Intent intent, boolean dismissShade, - @Nullable ActivityLaunchAnimator.Controller animationController, + @Nullable ActivityTransitionAnimator.Controller animationController, boolean showOverLockscreenWhenLocked); void startActivity(Intent intent, boolean dismissShade, - @Nullable ActivityLaunchAnimator.Controller animationController, + @Nullable ActivityTransitionAnimator.Controller animationController, boolean showOverLockscreenWhenLocked, UserHandle userHandle); void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade); void startActivity(Intent intent, boolean dismissShade, Callback callback); void postStartActivityDismissingKeyguard(Intent intent, int delay); void postStartActivityDismissingKeyguard(Intent intent, int delay, - @Nullable ActivityLaunchAnimator.Controller animationController); + @Nullable ActivityTransitionAnimator.Controller animationController); /** Posts a start activity intent that dismisses keyguard. */ void postStartActivityDismissingKeyguard(Intent intent, int delay, - @Nullable ActivityLaunchAnimator.Controller animationController, + @Nullable ActivityTransitionAnimator.Controller animationController, @Nullable String customMessage); void postStartActivityDismissingKeyguard(PendingIntent intent); @@ -100,7 +100,7 @@ public interface ActivityStarter { * animation controller that should be used for the activity launch animation. */ void postStartActivityDismissingKeyguard(PendingIntent intent, - @Nullable ActivityLaunchAnimator.Controller animationController); + @Nullable ActivityTransitionAnimator.Controller animationController); void postQSRunnableDismissingKeyguard(Runnable runnable); @@ -123,7 +123,7 @@ public interface ActivityStarter { boolean disallowEnterPictureInPictureWhileLaunching, Callback callback, int flags, - @Nullable ActivityLaunchAnimator.Controller animationController, + @Nullable ActivityTransitionAnimator.Controller animationController, UserHandle userHandle); /** Execute a runnable after dismissing keyguard. */ diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml index dec9930959a0..a80d3b4d0fa8 100644 --- a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml +++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml @@ -20,6 +20,7 @@ android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" + android:alpha="0.3" > <path android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 15688c5202ac..b30e4a2786a6 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1932,7 +1932,7 @@ <!-- User visible title for the keyboard shortcut that locks screen. [CHAR LIMIT=70] --> <string name="group_system_lock_screen">Lock screen</string> <!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] --> - <string name="group_system_quick_memo">Open notes</string> + <string name="group_system_quick_memo">Take a note</string> <!-- User visible title for the system multitasking keyboard shortcuts list. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_system_multitasking">System multitasking</string> diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java index 4c9782cdf36c..39e1c4150167 100644 --- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java +++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java @@ -36,7 +36,7 @@ import java.io.PrintWriter; * If your CoreStartable depends on different CoreStartables starting before it, use a * {@link com.android.systemui.startable.Dependencies} annotation to list out those dependencies. * - * @see SystemUIApplication#startServicesIfNeeded() + * @see SystemUIApplication#startSystemUserServicesIfNeeded() */ public interface CoreStartable extends Dumpable { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 8aae20651a10..15ef61ed5934 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -42,6 +42,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dump.DumpManager; +import com.android.systemui.process.ProcessWrapper; import com.android.systemui.res.R; import com.android.systemui.startable.Dependencies; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -77,6 +78,7 @@ public class SystemUIApplication extends Application implements private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback; private SysUIComponent mSysUIComponent; private SystemUIInitializer mInitializer; + private ProcessWrapper mProcessWrapper; public SystemUIApplication() { super(); @@ -115,6 +117,7 @@ public class SystemUIApplication extends Application implements // Enable Looper trace points. // This allows us to see Handler callbacks on traces. rootComponent.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP); + mProcessWrapper = rootComponent.getProcessWrapper(); // Set the application theme that is inherited by all services. Note that setting the // application theme in the manifest does only work for activities. Keep this in sync with @@ -132,7 +135,7 @@ public class SystemUIApplication extends Application implements View.setTraceLayoutSteps(true); } - if (rootComponent.getProcessWrapper().isSystemUser()) { + if (mProcessWrapper.isSystemUser()) { IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED); bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); @@ -199,7 +202,11 @@ public class SystemUIApplication extends Application implements * <p>This method must only be called from the main thread.</p> */ - public void startServicesIfNeeded() { + public void startSystemUserServicesIfNeeded() { + if (!mProcessWrapper.isSystemUser()) { + Log.wtf(TAG, "Tried starting SystemUser services on non-SystemUser"); + return; // Per-user startables are handled in #startSystemUserServicesIfNeeded. + } final String vendorComponent = mInitializer.getVendorComponent(getResources()); // Sort the startables so that we get a deterministic ordering. @@ -219,6 +226,9 @@ public class SystemUIApplication extends Application implements * <p>This method must only be called from the main thread.</p> */ void startSecondaryUserServicesIfNeeded() { + if (mProcessWrapper.isSystemUser()) { + return; // Per-user startables are handled in #startSystemUserServicesIfNeeded. + } // Sort the startables so that we get a deterministic ordering. Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>( Comparator.comparing(Class::getName)); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index 872b00518d62..1a9b01fd4996 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -22,10 +22,10 @@ import android.os.Handler; import android.os.HandlerThread; import android.util.Log; -import com.android.systemui.res.R; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dagger.WMComponent; +import com.android.systemui.res.R; import com.android.systemui.util.InitializationChecker; import com.android.wm.shell.dagger.WMShellConcurrencyModule; import com.android.wm.shell.keyguard.KeyguardTransitions; @@ -124,9 +124,6 @@ public abstract class SystemUIInitializer { .setDesktopMode(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); - if (initializeComponents) { - mSysUIComponent.init(); - } // Every other part of our codebase currently relies on Dependency, so we // really need to ensure the Dependency gets initialized early on. diff --git a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java index f4ec6f75b06b..407f7643d9d8 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java @@ -19,12 +19,31 @@ package com.android.systemui; import android.app.Service; import android.content.Intent; import android.os.IBinder; +import android.util.Log; + +import com.android.systemui.process.ProcessWrapper; + +import javax.inject.Inject; public class SystemUISecondaryUserService extends Service { + private static final String TAG = "SysUISecondaryService"; + + private final ProcessWrapper mProcessWrapper; + + @Inject + SystemUISecondaryUserService(ProcessWrapper processWrapper) { + mProcessWrapper = processWrapper; + } + @Override public void onCreate() { super.onCreate(); + if (mProcessWrapper.isSystemUser()) { + Log.w(TAG, "SecondaryServices started for System User. Stopping it."); + stopSelf(); + return; + } ((SystemUIApplication) getApplication()).startSecondaryUserServicesIfNeeded(); } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index 76c228252484..b26be0c74ece 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -77,7 +77,7 @@ public class SystemUIService extends Service { super.onCreate(); // Start all of SystemUI - ((SystemUIApplication) getApplication()).startServicesIfNeeded(); + ((SystemUIApplication) getApplication()).startSystemUserServicesIfNeeded(); // Finish initializing dump logic mLogBufferFreezer.attach(mBroadcastDispatcher); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java index bf121fb8c752..e57323b81490 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java @@ -26,6 +26,7 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; +import android.text.Layout; import android.view.Gravity; import android.view.View; import android.view.ViewTreeObserver; @@ -162,6 +163,7 @@ class MenuMessageView extends LinearLayout implements mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding); mTextView.setTextSize(COMPLEX_UNIT_PX, textSize); mTextView.setTextColor(textColor); + mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL); final ColorStateList colorAccent = Utils.getColorAccent(getContext()); mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo)); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 66fe4b36567d..716209d18a01 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -66,7 +66,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.biometrics.dagger.BiometricsBackground; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; @@ -162,7 +162,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mUnlockedScreenOffAnimationController; @NonNull private final LatencyTracker mLatencyTracker; @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; - @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator; + @NonNull private final ActivityTransitionAnimator mActivityTransitionAnimator; @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; @NonNull private final ShadeInteractor mShadeInteractor; @Nullable private final TouchProcessor mTouchProcessor; @@ -287,7 +287,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { event, fromUdfpsView ), - mActivityLaunchAnimator, + mActivityTransitionAnimator, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsKeyguardAccessibilityDelegate, @@ -663,7 +663,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @NonNull SystemUIDialogManager dialogManager, @NonNull LatencyTracker latencyTracker, - @NonNull ActivityLaunchAnimator activityLaunchAnimator, + @NonNull ActivityTransitionAnimator activityTransitionAnimator, @NonNull @BiometricsBackground Executor biometricsExecutor, @NonNull PrimaryBouncerInteractor primaryBouncerInteractor, @NonNull ShadeInteractor shadeInteractor, @@ -706,7 +706,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mSystemClock = systemClock; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mLatencyTracker = latencyTracker; - mActivityLaunchAnimator = activityLaunchAnimator; + mActivityTransitionAnimator = activityTransitionAnimator; mSensorProps = new FingerprintSensorPropertiesInternal( -1 /* sensorId */, SensorProperties.STRENGTH_CONVENIENCE, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 4ea5f4c7af0f..a209eae5673b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -44,7 +44,7 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.biometrics.ui.binder.UdfpsTouchOverlayBinder @@ -100,7 +100,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( @RequestReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, - private val activityLaunchAnimator: ActivityLaunchAnimator, + private val activityTransitionAnimator: ActivityTransitionAnimator, private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, @@ -304,7 +304,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( unlockedScreenOffAnimationController, dialogManager, controller, - activityLaunchAnimator, + activityTransitionAnimator, primaryBouncerInteractor, alternateBouncerInteractor, udfpsKeyguardAccessibilityDelegate, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 7020d05350da..018d92e4e932 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -25,7 +25,7 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor @@ -69,7 +69,7 @@ open class UdfpsKeyguardViewControllerLegacy( private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, systemUIDialogManager: SystemUIDialogManager, private val udfpsController: UdfpsController, - private val activityLaunchAnimator: ActivityLaunchAnimator, + private val activityTransitionAnimator: ActivityTransitionAnimator, private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, @@ -137,20 +137,20 @@ open class UdfpsKeyguardViewControllerLegacy( } } - private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener = - object : ActivityLaunchAnimator.Listener { - override fun onLaunchAnimationStart() { + private val mActivityTransitionAnimatorListener: ActivityTransitionAnimator.Listener = + object : ActivityTransitionAnimator.Listener { + override fun onTransitionAnimationStart() { isLaunchingActivity = true activityLaunchProgress = 0f updateAlpha() } - override fun onLaunchAnimationEnd() { + override fun onTransitionAnimationEnd() { isLaunchingActivity = false updateAlpha() } - override fun onLaunchAnimationProgress(linearProgress: Float) { + override fun onTransitionAnimationProgress(linearProgress: Float) { activityLaunchProgress = linearProgress updateAlpha() } @@ -370,7 +370,7 @@ open class UdfpsKeyguardViewControllerLegacy( updatePauseAuth() keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this - activityLaunchAnimator.addListener(activityLaunchAnimatorListener) + activityTransitionAnimator.addListener(mActivityTransitionAnimatorListener) view.startIconAsyncInflate { val animationViewInternal: View = view.requireViewById(R.id.udfps_animation_view_internal) @@ -389,7 +389,7 @@ open class UdfpsKeyguardViewControllerLegacy( if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) { lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null } - activityLaunchAnimator.removeListener(activityLaunchAnimatorListener) + activityTransitionAnimator.removeListener(mActivityTransitionAnimatorListener) keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback) } @@ -536,7 +536,7 @@ open class UdfpsKeyguardViewControllerLegacy( val udfpsActivityLaunchAlphaMultiplier = 1f - (activityLaunchProgress * - (ActivityLaunchAnimator.TIMINGS.totalDuration / 83)) + (ActivityTransitionAnimator.TIMINGS.totalDuration / 83)) .coerceIn(0f, 1f) alpha = (alpha * udfpsActivityLaunchAlphaMultiplier).toInt() } diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index dc07c1b25678..0bad33b8f920 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -20,6 +20,7 @@ import com.android.systemui.communal.data.db.CommunalDatabaseModule import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule import com.android.systemui.communal.data.repository.CommunalRepositoryModule +import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule import com.android.systemui.communal.widgets.EditWidgetsActivityStarter @@ -36,6 +37,7 @@ import dagger.Module CommunalWidgetRepositoryModule::class, CommunalDatabaseModule::class, CommunalPrefsRepositoryModule::class, + CommunalSettingsRepositoryModule::class, ] ) interface CommunalModule { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt new file mode 100644 index 000000000000..83a5bdb14ebd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt @@ -0,0 +1,69 @@ +/* + * 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.communal.data.model + +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger +import java.util.EnumSet + +/** Reasons that communal is disabled, primarily for logging. */ +enum class DisabledReason(val loggingString: String) { + /** Communal should be disabled due to invalid current user */ + DISABLED_REASON_INVALID_USER("invalidUser"), + /** Communal should be disabled due to the flag being off */ + DISABLED_REASON_FLAG("flag"), + /** Communal should be disabled because the user has turned off the setting */ + DISABLED_REASON_USER_SETTING("userSetting"), + /** Communal is disabled by the device policy app */ + DISABLED_REASON_DEVICE_POLICY("devicePolicy"), +} + +/** + * Model representing the reasons communal hub should be disabled. Allows logging reasons separately + * for debugging. + */ +@JvmInline +value class CommunalEnabledState( + private val disabledReasons: EnumSet<DisabledReason> = + EnumSet.noneOf(DisabledReason::class.java) +) : Diffable<CommunalEnabledState>, Set<DisabledReason> by disabledReasons { + + /** Creates [CommunalEnabledState] with a single reason for being disabled */ + constructor(reason: DisabledReason) : this(EnumSet.of(reason)) + + /** Checks if there are any reasons communal should be disabled. If none, returns true. */ + val enabled: Boolean + get() = isEmpty() + + override fun logDiffs(prevVal: CommunalEnabledState, row: TableRowLogger) { + for (reason in DisabledReason.entries) { + val newVal = contains(reason) + if (newVal != prevVal.contains(reason)) { + row.logChange( + columnName = reason.loggingString, + value = newVal, + ) + } + } + } + + override fun logFull(row: TableRowLogger) { + for (reason in DisabledReason.entries) { + row.logChange(columnName = reason.loggingString, value = contains(reason)) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index addd880f2079..4a06585f5a70 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -16,22 +16,14 @@ package com.android.systemui.communal.data.repository -import com.android.systemui.Flags.communalHub import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.user.data.repository.UserRepository -import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -39,26 +31,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.withContext /** Encapsulates the state of communal mode. */ interface CommunalRepository { - /** Whether communal features are enabled. */ - val isCommunalEnabled: Boolean - - /** - * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in - * settings). - */ - val communalEnabledState: StateFlow<Boolean> - /** Whether the communal hub is showing. */ val isCommunalHubShowing: Flow<Boolean> @@ -87,37 +66,11 @@ interface CommunalRepository { class CommunalRepositoryImpl @Inject constructor( - @Application private val applicationScope: CoroutineScope, @Background backgroundScope: CoroutineScope, - @Background private val backgroundDispatcher: CoroutineDispatcher, - private val featureFlagsClassic: FeatureFlagsClassic, sceneContainerFlags: SceneContainerFlags, sceneContainerRepository: SceneContainerRepository, - userRepository: UserRepository, - private val secureSettings: SecureSettings ) : CommunalRepository { - private val communalEnabledSettingState: Flow<Boolean> = - userRepository.selectedUserInfo - .flatMapLatest { userInfo -> observeSettings(userInfo.id) } - .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed()) - - override val communalEnabledState: StateFlow<Boolean> = - if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) { - communalEnabledSettingState - .filterNotNull() - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = true - ) - } else { - MutableStateFlow(false) - } - - override val isCommunalEnabled: Boolean - get() = communalEnabledState.value - private val _desiredScene: MutableStateFlow<CommunalSceneKey> = MutableStateFlow(CommunalSceneKey.DEFAULT) override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow() @@ -153,26 +106,4 @@ constructor( } else { desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal } } - - private fun observeSettings(userId: Int): Flow<Boolean> = - secureSettings - .observerFlow( - userId = userId, - names = - arrayOf( - GLANCEABLE_HUB_ENABLED, - ) - ) - // Force an update - .onStart { emit(Unit) } - .map { readFromSettings(userId) } - - private suspend fun readFromSettings(userId: Int): Boolean = - withContext(backgroundDispatcher) { - secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1 - } - - companion object { - private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" - } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt new file mode 100644 index 000000000000..201b04927ea3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -0,0 +1,126 @@ +/* + * 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.communal.data.repository + +import android.app.admin.DevicePolicyManager +import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL +import android.content.IntentFilter +import android.content.pm.UserInfo +import com.android.systemui.Flags.communalHub +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.communal.data.model.CommunalEnabledState +import com.android.systemui.communal.data.model.DisabledReason +import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_DEVICE_POLICY +import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG +import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER +import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.util.kotlin.emitOnStart +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import java.util.EnumSet +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +interface CommunalSettingsRepository { + /** A [CommunalEnabledState] for the specified user. */ + fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> +} + +@SysUISingleton +class CommunalSettingsRepositoryImpl +@Inject +constructor( + @Background private val bgDispatcher: CoroutineDispatcher, + private val featureFlagsClassic: FeatureFlagsClassic, + private val secureSettings: SecureSettings, + private val broadcastDispatcher: BroadcastDispatcher, + private val devicePolicyManager: DevicePolicyManager, +) : CommunalSettingsRepository { + + private val flagEnabled: Boolean by lazy { + featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub() + } + + override fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> { + if (!user.isMain) { + return flowOf(CommunalEnabledState(DISABLED_REASON_INVALID_USER)) + } + if (!flagEnabled) { + return flowOf(CommunalEnabledState(DISABLED_REASON_FLAG)) + } + return combine( + getEnabledByUser(user).mapToReason(DISABLED_REASON_USER_SETTING), + getAllowedByDevicePolicy(user).mapToReason(DISABLED_REASON_DEVICE_POLICY), + ) { reasons -> + reasons.filterNotNull() + } + .map { reasons -> + if (reasons.isEmpty()) { + EnumSet.noneOf(DisabledReason::class.java) + } else { + EnumSet.copyOf(reasons) + } + } + .map { reasons -> CommunalEnabledState(reasons) } + .flowOn(bgDispatcher) + } + + private fun getEnabledByUser(user: UserInfo): Flow<Boolean> = + secureSettings + .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_ENABLED)) + // Force an update + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser( + GLANCEABLE_HUB_ENABLED, + ENABLED_SETTING_DEFAULT, + user.id, + ) == 1 + } + + private fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = + broadcastDispatcher + .broadcastFlow( + filter = + IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + user = user.userHandle + ) + .emitOnStart() + .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) } + + companion object { + const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled" + private const val ENABLED_SETTING_DEFAULT = 1 + } +} + +private fun DevicePolicyManager.areKeyguardWidgetsAllowed(userId: Int): Boolean = + (getKeyguardDisabledFeatures(null, userId) and KEYGUARD_DISABLE_WIDGETS_ALL) == 0 + +private fun Flow<Boolean>.mapToReason(reason: DisabledReason) = map { enabled -> + if (enabled) null else reason +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.kt new file mode 100644 index 000000000000..a931d3f7ec5b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.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.communal.data.repository + +import dagger.Binds +import dagger.Module + +@Module +interface CommunalSettingsRepositoryModule { + @Binds + fun communalSettingsRepository(impl: CommunalSettingsRepositoryImpl): CommunalSettingsRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 950ac3c3aae6..23f590e30235 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -42,7 +42,6 @@ import com.android.systemui.log.dagger.CommunalTableLog import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.smartspace.data.repository.SmartspaceRepository -import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.BooleanFlowOperators.and import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.BooleanFlowOperators.or @@ -74,8 +73,8 @@ constructor( private val communalPrefsRepository: CommunalPrefsRepository, mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, - userRepository: UserRepository, keyguardInteractor: KeyguardInteractor, + private val communalSettingsInteractor: CommunalSettingsInteractor, private val appWidgetHost: CommunalAppWidgetHost, private val editWidgetsActivityStarter: EditWidgetsActivityStarter, @CommunalLog logBuffer: LogBuffer, @@ -90,13 +89,12 @@ constructor( /** Whether communal features are enabled. */ val isCommunalEnabled: Boolean - get() = communalRepository.isCommunalEnabled + get() = communalSettingsInteractor.isCommunalEnabled.value /** Whether communal features are enabled and available. */ val isCommunalAvailable: Flow<Boolean> = and( - communalRepository.communalEnabledState, - userRepository.selectedUserInfo.map { it.isMain }, + communalSettingsInteractor.isCommunalEnabled, not(keyguardInteractor.isEncryptedOrLockdown), or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming) ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt new file mode 100644 index 000000000000..0b096ce67fc5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -0,0 +1,58 @@ +/* + * 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.communal.domain.interactor + +import com.android.systemui.communal.data.model.CommunalEnabledState +import com.android.systemui.communal.data.repository.CommunalSettingsRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.dagger.CommunalTableLog +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalSettingsInteractor +@Inject +constructor( + @Background private val bgScope: CoroutineScope, + private val repository: CommunalSettingsRepository, + userInteractor: SelectedUserInteractor, + @CommunalTableLog tableLogBuffer: TableLogBuffer, +) { + /** Whether or not communal is enabled for the currently selected user. */ + val isCommunalEnabled: StateFlow<Boolean> = + userInteractor.selectedUserInfo + .flatMapLatest { user -> repository.getEnabledState(user) } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnPrefix = "disabledReason", + initialValue = CommunalEnabledState() + ) + .map { model -> model.enabled } + // Start this eagerly since the value is accessed synchronously in many places. + .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt index 1404ee20cb72..25dfc02a5d98 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt @@ -28,17 +28,18 @@ import com.android.systemui.log.table.logDiffsForTable import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transformWhile import kotlinx.coroutines.launch /** Encapsulates business-logic related to communal tutorial state. */ @@ -51,6 +52,7 @@ constructor( private val communalTutorialRepository: CommunalTutorialRepository, keyguardInteractor: KeyguardInteractor, private val communalRepository: CommunalRepository, + private val communalSettingsInteractor: CommunalSettingsInteractor, communalInteractor: CommunalInteractor, @CommunalTableLog tableLogBuffer: TableLogBuffer, ) { @@ -110,20 +112,24 @@ constructor( return null } - private var job: Job? = null private fun listenForTransitionToUpdateTutorialState() { - if (!communalRepository.isCommunalEnabled) { - return - } - job = - scope.launch { - tutorialStateToUpdate.collect { - communalTutorialRepository.setTutorialState(it) - if (it == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) { - job?.cancel() + scope.launch { + communalSettingsInteractor.isCommunalEnabled + .flatMapLatest { enabled -> + if (!enabled) { + emptyFlow() + } else { + tutorialStateToUpdate } } - } + .transformWhile { tutorialState -> + emit(tutorialState) + tutorialState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED + } + .collect { tutorialState -> + communalTutorialRepository.setTutorialState(tutorialState) + } + } } init { diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index afa7fa9e8f77..4c1e77bc47f8 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -19,7 +19,7 @@ package com.android.systemui.communal.widgets import android.app.PendingIntent import android.view.View import android.widget.RemoteViews -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.ui.view.getNearestParent import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject @@ -42,7 +42,7 @@ constructor( private fun startActivity(view: View, pendingIntent: PendingIntent): Boolean { val hostView = view.getNearestParent<CommunalAppWidgetHostView>() - val animationController = hostView?.let(ActivityLaunchAnimator.Controller::fromView) + val animationController = hostView?.let(ActivityTransitionAnimator.Controller::fromView) activityStarter.startPendingIntentMaybeDismissingKeyguard( pendingIntent, diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java index 9d4ed20ca582..afa23755d937 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java @@ -35,7 +35,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.Utils; import com.android.systemui.CoreStartable; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent; import com.android.systemui.controls.ControlsServiceInfo; import com.android.systemui.controls.dagger.ControlsComponent; @@ -275,8 +275,9 @@ public class DreamHomeControlsComplication implements Complication { .putExtra(ControlsUiController.EXTRA_ANIMATE, true) .putExtra(ControlsUiController.EXIT_TO_DREAM, true); - final ActivityLaunchAnimator.Controller controller = - v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */) + final ActivityTransitionAnimator.Controller controller = + v != null + ? ActivityTransitionAnimator.Controller.fromView(v, null /* cujType */) : null; if (mControlsComponent.getVisibility() == AVAILABLE) { // Controls can be made visible. diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index dd186d624c84..f7bc5cdc69c2 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -26,10 +26,13 @@ import com.android.keyguard.KeyguardViewController; import com.android.systemui.ScreenDecorationsModule; import com.android.systemui.accessibility.SystemActionsModule; import com.android.systemui.battery.BatterySaverModule; +import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.doze.DozeHost; import com.android.systemui.media.dagger.MediaModule; +import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; +import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.navigationbar.NavigationBarControllerModule; import com.android.systemui.navigationbar.gestural.GestureModule; import com.android.systemui.plugins.qs.QSFactory; @@ -63,6 +66,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerIm import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; import com.android.systemui.toast.ToastModule; +import com.android.systemui.unfold.SysUIUnfoldStartableModule; import com.android.systemui.unfold.UnfoldTransitionModule; import com.android.systemui.volume.dagger.VolumeModule; import com.android.systemui.wallpapers.dagger.WallpaperModule; @@ -92,12 +96,15 @@ import javax.inject.Named; AospPolicyModule.class, BatterySaverModule.class, CollapsedStatusBarFragmentStartableModule.class, + ConnectingDisplayViewModel.StartableModule.class, GestureModule.class, HeadsUpModule.class, KeyboardShortcutsModule.class, MediaModule.class, + MediaMuteAwaitConnectionCli.StartableModule.class, MultiUserUtilsModule.class, NavigationBarControllerModule.class, + NearbyMediaDevicesManager.StartableModule.class, PowerModule.class, QSModule.class, RearDisplayModule.class, @@ -108,6 +115,7 @@ import javax.inject.Named; ShadeModule.class, StartCentralSurfacesModule.class, SceneContainerFrameworkModule.class, + SysUIUnfoldStartableModule.class, UnfoldTransitionModule.Startables.class, ToastModule.class, VolumeModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 7bf67d768e91..3b0c281a7057 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -19,25 +19,15 @@ package com.android.systemui.dagger; import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; -import com.android.systemui.Flags; import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactoryBase; import com.android.systemui.dagger.qualifiers.PerUser; -import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; -import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; -import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.people.PeopleProvider; import com.android.systemui.statusbar.NotificationInsetsModule; import com.android.systemui.statusbar.QsFrameTranslateModule; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.unfold.FoldStateLogger; -import com.android.systemui.unfold.FoldStateLoggingProvider; -import com.android.systemui.unfold.SysUIUnfoldComponent; -import com.android.systemui.unfold.UnfoldTransitionProgressProvider; -import com.android.systemui.unfold.dagger.UnfoldBg; -import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.desktopmode.DesktopMode; @@ -126,42 +116,6 @@ public interface SysUIComponent { } /** - * Initializes all the SysUI components. - */ - default void init() { - // Initialize components that have no direct tie to the dagger dependency graph, - // but are critical to this component's operation - getSysUIUnfoldComponent() - .ifPresent( - c -> { - c.getFullScreenLightRevealAnimations().forEach(it -> it.init()); - c.getUnfoldTransitionWallpaperController().init(); - c.getUnfoldHapticsPlayer(); - c.getNaturalRotationUnfoldProgressProvider().init(); - c.getUnfoldLatencyTracker().init(); - }); - // No init method needed, just needs to be gotten so that it's created. - getMediaMuteAwaitConnectionCli(); - getNearbyMediaDevicesManager(); - getConnectingDisplayViewModel().init(); - getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init); - getFoldStateLogger().ifPresent(FoldStateLogger::init); - - Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider; - - if (Flags.unfoldAnimationBackgroundProgress()) { - unfoldTransitionProgressProvider = getBgUnfoldTransitionProgressProvider(); - } else { - unfoldTransitionProgressProvider = getUnfoldTransitionProgressProvider(); - } - unfoldTransitionProgressProvider - .ifPresent( - (progressProvider) -> - getUnfoldTransitionProgressForwarder() - .ifPresent(progressProvider::addCallback)); - } - - /** * Provides a BootCompleteCache. */ @SysUISingleton @@ -180,37 +134,6 @@ public interface SysUIComponent { ContextComponentHelper getContextComponentHelper(); /** - * Creates a UnfoldTransitionProgressProvider that calculates progress in the background. - */ - @SysUISingleton - @UnfoldBg - Optional<UnfoldTransitionProgressProvider> getBgUnfoldTransitionProgressProvider(); - - /** - * Creates a UnfoldTransitionProgressProvider that calculates progress in the main thread. - */ - @SysUISingleton - Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider(); - - /** - * Creates a UnfoldTransitionProgressForwarder. - */ - @SysUISingleton - Optional<UnfoldTransitionProgressForwarder> getUnfoldTransitionProgressForwarder(); - - /** - * Creates a FoldStateLoggingProvider. - */ - @SysUISingleton - Optional<FoldStateLoggingProvider> getFoldStateLoggingProvider(); - - /** - * Creates a FoldStateLogger. - */ - @SysUISingleton - Optional<FoldStateLogger> getFoldStateLogger(); - - /** * Main dependency providing module. */ @SysUISingleton @@ -227,22 +150,6 @@ public interface SysUIComponent { InitController getInitController(); /** - * For devices with a hinge: access objects within this component - */ - Optional<SysUIUnfoldComponent> getSysUIUnfoldComponent(); - - /** */ - MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli(); - - /** */ - NearbyMediaDevicesManager getNearbyMediaDevicesManager(); - - /** - * Creates a ConnectingDisplayViewModel - */ - ConnectingDisplayViewModel getConnectingDisplayViewModel(); - - /** * Returns {@link CoreStartable}s that should be started with the application. */ Map<Class<?>, Provider<CoreStartable>> getStartables(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 28fd9a994f8a..1720de880cbd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -17,6 +17,7 @@ package com.android.systemui.dagger; import android.app.INotificationManager; +import android.app.Service; import android.content.Context; import android.service.dreams.IDreamManager; @@ -28,6 +29,7 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.BootCompleteCache; import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.CameraProtectionModule; +import com.android.systemui.SystemUISecondaryUserService; import com.android.systemui.accessibility.AccessibilityModule; import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule; import com.android.systemui.appops.dagger.AppOpsModule; @@ -150,6 +152,8 @@ import dagger.Binds; import dagger.BindsOptionalOf; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ClassKey; +import dagger.multibindings.IntoMap; import java.util.Collections; import java.util.Optional; @@ -384,4 +388,9 @@ public abstract class SystemUIModule { @Binds abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator( LargeScreenShadeInterpolatorImpl impl); + + @Binds + @IntoMap + @ClassKey(SystemUISecondaryUserService.class) + abstract Service bindsSystemUISecondaryUserService(SystemUISecondaryUserService service); } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt index 684627ba27bf..2461c26a56ca 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt @@ -39,4 +39,6 @@ constructor( /** Provide the current status of fingerprint authentication. */ val authenticationStatus: Flow<FingerprintAuthenticationStatus> = repository.authenticationStatus + + val isLockedOut: Flow<Boolean> = repository.isLockedOut } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt index 98130eb10f3d..cf91e147d1b3 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt @@ -36,7 +36,6 @@ import com.android.systemui.deviceentry.shared.FaceAuthUiEvent import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository -import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger @@ -78,7 +77,7 @@ constructor( private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val faceAuthenticationLogger: FaceAuthenticationLogger, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, private val userRepository: UserRepository, private val facePropertyRepository: FacePropertyRepository, private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig, @@ -149,14 +148,24 @@ constructor( } .launchIn(applicationScope) - deviceEntryFingerprintAuthRepository.isLockedOut - .onEach { - if (it) { + deviceEntryFingerprintAuthInteractor.isLockedOut + .sample(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled, ::Pair) + .filter { (_, faceEnabledAndEnrolled) -> + // We don't care about this if face auth is not enabled. + faceEnabledAndEnrolled + } + .map { (fpLockedOut, _) -> fpLockedOut } + .sample(userRepository.selectedUser, ::Pair) + .onEach { (fpLockedOut, currentUser) -> + if (fpLockedOut) { faceAuthenticationLogger.faceLockedOut("Fingerprint locked out") - // We don't care about this if face auth is not enabled. if (isFaceAuthEnabledAndEnrolled()) { repository.setLockedOut(true) } + } else { + // Fingerprint is not locked out anymore, revert face lockout state back to + // previous value. + resetLockedOutState(currentUser.userInfo.id) } } .launchIn(applicationScope) @@ -169,10 +178,7 @@ constructor( val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS if (wasSwitching && !isSwitching) { - val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id) - repository.setLockedOut( - lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED - ) + resetLockedOutState(curr.userInfo.id) yield() runFaceAuth( FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING, @@ -185,6 +191,13 @@ constructor( .launchIn(applicationScope) } + private suspend fun resetLockedOutState(currentUserId: Int) { + val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId) + repository.setLockedOut( + lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED + ) + } + override fun onSwipeUpOnBouncer() { runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false) } diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt index 10aa70391f01..190062cdcca1 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.display.ui.viewmodel import android.app.Dialog import android.content.Context import com.android.server.policy.feature.flags.Flags +import com.android.systemui.CoreStartable import com.android.systemui.biometrics.Utils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -26,6 +27,10 @@ import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.display.ui.view.MirroringConfirmationDialog import com.android.systemui.statusbar.policy.ConfigurationController +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -47,12 +52,12 @@ constructor( @Application private val scope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, private val configurationController: ConfigurationController, -) { +) : CoreStartable { private var dialog: Dialog? = null /** Starts listening for pending displays. */ - fun init() { + override fun start() { val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay val concurrentDisplaysInProgessFlow = if (Flags.enableDualDisplayBlocking()) { @@ -96,4 +101,12 @@ constructor( dialog?.hide() dialog = null } + + @Module + interface StartableModule { + @Binds + @IntoMap + @ClassKey(ConnectingDisplayViewModel::class) + fun bindsConnectingDisplayViewModel(impl: ConnectingDisplayViewModel): CoreStartable + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 4cabd70cb142..2e233d8e5dd2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -327,7 +327,7 @@ public class KeyguardService extends Service { @Override public void onCreate() { - ((SystemUIApplication) getApplication()).startServicesIfNeeded(); + ((SystemUIApplication) getApplication()).startSystemUserServicesIfNeeded(); if (mShellTransitions == null || !Transitions.ENABLE_SHELL_TRANSITIONS) { RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 39bbf077656e..0ee924dc6829 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -130,7 +130,7 @@ import com.android.systemui.CoreStartable; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.EventLogTags; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.TransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; @@ -957,8 +957,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * Animation launch controller for activities that occlude the keyguard. */ @VisibleForTesting - final ActivityLaunchAnimator.Controller mOccludeAnimationController = - new ActivityLaunchAnimator.Controller() { + final ActivityTransitionAnimator.Controller mOccludeAnimationController = + new ActivityTransitionAnimator.Controller() { @Override public void onTransitionAnimationStart(boolean isExpandingFullyAbove) { mOccludeAnimationPlaying = true; @@ -966,7 +966,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } @Override - public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) { + public void onTransitionAnimationCancelled( + @Nullable Boolean newKeyguardOccludedState) { Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded); mOccludeAnimationPlaying = false; @@ -1003,7 +1004,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void setTransitionContainer(@NonNull ViewGroup transitionContainer) { // No-op, launch container is always the shade. Log.wtf(TAG, "Someone tried to change the launch container for the " - + "ActivityLaunchAnimator, which should never happen."); + + "ActivityTransitionAnimator, which should never happen."); } @NonNull @@ -1167,8 +1168,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, /** * Animation controller for activities that unocclude the keyguard. This does not use the - * ActivityLaunchAnimator since we're just translating down, rather than emerging from a view - * or the power button. + * ActivityTransitionAnimator since we're just translating down, rather than emerging from a + * view or the power button. */ private final IRemoteAnimationRunner mUnoccludeAnimationRunner = new IRemoteAnimationRunner.Stub() { @@ -1347,7 +1348,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private boolean mWallpaperSupportsAmbientMode; private final KeyguardTransitions mKeyguardTransitions; - private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator; + private final Lazy<ActivityTransitionAnimator> mActivityTransitionAnimator; private final Lazy<ScrimController> mScrimControllerLazy; private final IActivityTaskManager mActivityTaskManagerService; @@ -1394,7 +1395,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, WallpaperRepository wallpaperRepository, Lazy<ShadeController> shadeControllerLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, - Lazy<ActivityLaunchAnimator> activityLaunchAnimator, + Lazy<ActivityTransitionAnimator> activityTransitionAnimator, Lazy<ScrimController> scrimControllerLazy, IActivityTaskManager activityTaskManagerService, FeatureFlags featureFlags, @@ -1459,7 +1460,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mJavaAdapter = javaAdapter; mWallpaperRepository = wallpaperRepository; - mActivityLaunchAnimator = activityLaunchAnimator; + mActivityTransitionAnimator = activityTransitionAnimator; mScrimControllerLazy = scrimControllerLazy; mActivityTaskManagerService = activityTaskManagerService; @@ -3812,15 +3813,15 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, /** * Implementation of RemoteAnimationRunner that creates a new - * {@link ActivityLaunchAnimator.Runner} whenever onAnimationStart is called, delegating the + * {@link ActivityTransitionAnimator.Runner} whenever onAnimationStart is called, delegating the * remote animation methods to that runner. */ private class ActivityLaunchRemoteAnimationRunner extends IRemoteAnimationRunner.Stub { - private final ActivityLaunchAnimator.Controller mActivityLaunchController; - @Nullable private ActivityLaunchAnimator.Runner mRunner; + private final ActivityTransitionAnimator.Controller mActivityLaunchController; + @Nullable private ActivityTransitionAnimator.Runner mRunner; - ActivityLaunchRemoteAnimationRunner(ActivityLaunchAnimator.Controller controller) { + ActivityLaunchRemoteAnimationRunner(ActivityTransitionAnimator.Controller controller) { mActivityLaunchController = controller; } @@ -3837,7 +3838,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { - mRunner = mActivityLaunchAnimator.get().createRunner(mActivityLaunchController); + mRunner = mActivityTransitionAnimator.get().createRunner(mActivityLaunchController); mRunner.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); } } @@ -3850,7 +3851,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, extends ActivityLaunchRemoteAnimationRunner { OccludeActivityLaunchRemoteAnimationRunner( - ActivityLaunchAnimator.Controller controller) { + ActivityTransitionAnimator.Controller controller) { super(controller); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 70da3e7ad1fa..0b227fa7f5a6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -35,7 +35,7 @@ import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.CoreStartable; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingModule; @@ -150,7 +150,7 @@ public interface KeyguardModule { WallpaperRepository wallpaperRepository, Lazy<ShadeController> shadeController, Lazy<NotificationShadeWindowController> notificationShadeWindowController, - Lazy<ActivityLaunchAnimator> activityLaunchAnimator, + Lazy<ActivityTransitionAnimator> activityTransitionAnimator, Lazy<ScrimController> scrimControllerLazy, IActivityTaskManager activityTaskManagerService, FeatureFlags featureFlags, @@ -196,7 +196,7 @@ public interface KeyguardModule { wallpaperRepository, shadeController, notificationShadeWindowController, - activityLaunchAnimator, + activityTransitionAnimator, scrimControllerLazy, activityTaskManagerService, featureFlags, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 96e83b0ca0f8..3630b4038357 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -32,7 +32,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators import com.android.settingslib.Utils -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableLinearLayout import com.android.systemui.common.shared.model.Icon @@ -534,7 +534,7 @@ object KeyguardBottomAreaViewBinder { activityStarter.postStartActivityDismissingKeyguard( WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD), /* delay= */ 0, - /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view), + /* animationController= */ ActivityTransitionAnimator.Controller.fromView(view), /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt index f67cb684b7a6..b1adef46e782 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt @@ -22,7 +22,7 @@ import android.view.View import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.common.ui.binder.TextViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel @@ -115,7 +115,7 @@ object KeyguardSettingsViewBinder { activityStarter.postStartActivityDismissingKeyguard( WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD), /* delay= */ 0, - /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view), + /* animationController= */ ActivityTransitionAnimator.Controller.fromView(view), /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt index 9cf3c955b35c..d4ea728bbffb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart /** Models UI state for the alpha of the AOD (always-on display). */ @SysUISingleton @@ -42,13 +43,15 @@ constructor( /** The alpha level for the entire lockscreen while in AOD. */ val alpha: Flow<Float> = combine( - keyguardTransitionInteractor.currentKeyguardState, + keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart { + emit(0f) + }, merge( keyguardInteractor.keyguardAlpha, occludedToLockscreenTransitionViewModel.lockscreenAlpha, ) - ) { currentKeyguardState, alpha -> - if (currentKeyguardState == KeyguardState.GONE) { + ) { transitionToGone, alpha -> + if (transitionToGone == 1f) { // Ensures content is not visible when in GONE state 0f } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 2b28a71b4a3d..fa185570e7fc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -16,7 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -36,7 +36,7 @@ class LockscreenSceneViewModel constructor( @Application applicationScope: CoroutineScope, deviceEntryInteractor: DeviceEntryInteractor, - communalInteractor: CommunalInteractor, + communalSettingsInteractor: CommunalSettingsInteractor, val longPress: KeyguardLongPressViewModel, val notifications: NotificationsPlaceholderViewModel, ) { @@ -55,10 +55,12 @@ constructor( } /** The key of the scene we should switch to when swiping left. */ - val leftDestinationSceneKey: SceneKey? = - if (communalInteractor.isCommunalEnabled) { - SceneKey.Communal - } else { - null - } + val leftDestinationSceneKey: StateFlow<SceneKey?> = + communalSettingsInteractor.isCommunalEnabled + .map { available -> if (available) SceneKey.Communal else null } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/OWNERS b/packages/SystemUI/src/com/android/systemui/media/OWNERS index b2d00df252ff..69ea57bfd397 100644 --- a/packages/SystemUI/src/com/android/systemui/media/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/media/OWNERS @@ -1,5 +1 @@ per-file MediaProjectionPermissionActivity.java = michaelwr@google.com - -# Haptics team also works on Ringtone -per-file NotificationPlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file RingtonePlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS 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 5720cc74002b..eae77898a5e8 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 @@ -81,7 +81,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.widget.CachingIconView; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.ActivityIntentHelper; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.GhostedViewLaunchAnimatorController; import com.android.systemui.bluetooth.BroadcastDialogController; import com.android.systemui.broadcast.BroadcastSender; @@ -1309,7 +1309,7 @@ public class MediaControlPanel { } @Nullable - private ActivityLaunchAnimator.Controller buildLaunchAnimatorController( + private ActivityTransitionAnimator.Controller buildLaunchAnimatorController( TransitionLayout player) { if (!(player.getParent() instanceof ViewGroup)) { // TODO(b/192194319): Throw instead of just logging. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index 523414cfddbf..35e0271c1b8f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -1155,7 +1155,7 @@ constructor( onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS // TODO(b/311234666): revisit logic once interactions between the hub and // shade/keyguard state are finalized - isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB + isCommunalShowing -> LOCATION_COMMUNAL_HUB onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN else -> LOCATION_QQS } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 375a0ce55ac0..687f26871119 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -78,7 +78,7 @@ import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.utils.ThreadUtils; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.flags.FeatureFlags; @@ -400,7 +400,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, void tryToLaunchInAppRoutingIntent(String routeId, View view) { ComponentName componentName = mLocalMediaManager.getLinkedItemComponentName(); if (componentName != null) { - ActivityLaunchAnimator.Controller controller = + ActivityTransitionAnimator.Controller controller = mDialogLaunchAnimator.createActivityLaunchController(view); Intent launchIntent = new Intent(ACTION_TRANSFER_MEDIA); launchIntent.setComponent(componentName); @@ -412,7 +412,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } void tryToLaunchMediaApplication(View view) { - ActivityLaunchAnimator.Controller controller = + ActivityTransitionAnimator.Controller controller = mDialogLaunchAnimator.createActivityLaunchController(view); Intent launchIntent = getAppLaunchIntent(); if (launchIntent != null) { @@ -881,7 +881,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } void launchBluetoothPairing(View view) { - ActivityLaunchAnimator.Controller controller = + ActivityTransitionAnimator.Controller controller = mDialogLaunchAnimator.createActivityLaunchController(view); if (controller == null || (mKeyGuardManager != null diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt index 2ae3a631fa5a..e47564730c4d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt @@ -16,13 +16,18 @@ package com.android.systemui.media.muteawait -import android.content.Context +import android.annotation.SuppressLint import android.media.AudioAttributes.USAGE_MEDIA import android.media.AudioDeviceAttributes import android.media.AudioManager +import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import java.io.PrintWriter import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -30,14 +35,15 @@ import javax.inject.Inject /** A command line interface to manually test [MediaMuteAwaitConnectionManager]. */ @SysUISingleton class MediaMuteAwaitConnectionCli @Inject constructor( - commandRegistry: CommandRegistry, - private val context: Context -) { - init { + private val commandRegistry: CommandRegistry, + private val audioManager: AudioManager, +) : CoreStartable { + override fun start() { commandRegistry.registerCommand(MEDIA_MUTE_AWAIT_COMMAND) { MuteAwaitCommand() } } inner class MuteAwaitCommand : Command { + @SuppressLint("MissingPermission") override fun execute(pw: PrintWriter, args: List<String>) { val device = AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, @@ -49,8 +55,6 @@ class MediaMuteAwaitConnectionCli @Inject constructor( ) val startOrCancel = args[2] - val audioManager: AudioManager = - context.getSystemService(Context.AUDIO_SERVICE) as AudioManager when (startOrCancel) { START -> audioManager.muteAwaitConnection( @@ -65,6 +69,14 @@ class MediaMuteAwaitConnectionCli @Inject constructor( "[type] [name] [$START|$CANCEL]") } } + + @Module + interface StartableModule { + @Binds + @IntoMap + @ClassKey(MediaMuteAwaitConnectionCli::class) + fun bindsMediaMuteAwaitConnectionCli(impl: MediaMuteAwaitConnectionCli): CoreStartable + } } private const val MEDIA_MUTE_AWAIT_COMMAND = "media-mute-await" diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt index 64b772b6a02c..0dc10f6b3f56 100644 --- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt @@ -18,9 +18,14 @@ package com.android.systemui.media.nearby import android.media.INearbyMediaDevicesProvider import android.media.INearbyMediaDevicesUpdateCallback -import com.android.systemui.dagger.SysUISingleton import android.os.IBinder +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.CommandQueue +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import javax.inject.Inject /** @@ -30,9 +35,9 @@ import javax.inject.Inject */ @SysUISingleton class NearbyMediaDevicesManager @Inject constructor( - commandQueue: CommandQueue, + private val commandQueue: CommandQueue, private val logger: NearbyMediaDevicesLogger -) { +) : CoreStartable { private var providers: MutableList<INearbyMediaDevicesProvider> = mutableListOf() private var activeCallbacks: MutableList<INearbyMediaDevicesUpdateCallback> = mutableListOf() @@ -69,7 +74,7 @@ class NearbyMediaDevicesManager @Inject constructor( } } - init { + override fun start() { commandQueue.addCallback(commandQueueCallbacks) } @@ -108,4 +113,12 @@ class NearbyMediaDevicesManager @Inject constructor( } } } + + @Module + interface StartableModule { + @Binds + @IntoMap + @ClassKey(NearbyMediaDevicesManager::class) + fun bindsNearbyMediaDevicesManager(impl: NearbyMediaDevicesManager): CoreStartable + } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 01672875fd44..aa03e6e00859 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -395,7 +395,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } if (displayId == mDisplayId) { mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged, - BarTransitions.MODE_TRANSPARENT, navbarColorManagedByIme); + mTransitionMode, navbarColorManagedByIme); } if (mBehavior != behavior) { mBehavior = behavior; diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java index 27510720ae2f..3671dd439239 100644 --- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java @@ -16,6 +16,9 @@ package com.android.systemui.process; +import android.os.Process; +import android.os.UserHandle; + import javax.inject.Inject; /** @@ -30,6 +33,15 @@ public class ProcessWrapper { * Returns {@code true} if System User is running the current process. */ public boolean isSystemUser() { - return android.os.Process.myUserHandle().isSystem(); + return myUserHandle().isSystem(); + } + + /** + * Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}. + * + * Please strongly consider using {@link com.android.systemui.settings.UserTracker} instead. + */ + public UserHandle myUserHandle() { + return Process.myUserHandle(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt index 0941a2082cfd..3fd9803882ed 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt @@ -11,7 +11,7 @@ import android.view.View import androidx.annotation.WorkerThread import com.android.internal.R import com.android.internal.logging.UiEventLogger -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.appops.AppOpsController import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.FeatureFlags @@ -175,7 +175,7 @@ class HeaderPrivacyIconsController @Inject constructor( startSafetyCenter.flags = Intent.FLAG_ACTIVITY_NEW_TASK uiExecutor.execute { activityStarter.startActivity(startSafetyCenter, true, - ActivityLaunchAnimator.Controller.fromView(privacyChip)) + ActivityTransitionAnimator.Controller.fromView(privacyChip)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index bd13d0686462..b53c2450f4c7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -51,7 +51,7 @@ import androidx.annotation.WorkerThread; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -65,14 +65,14 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.DisplayTracker; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; - import dagger.Lazy; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; + public class CustomTile extends QSTileImpl<State> implements TileChangeListener, CustomTileInterface { public static final String PREFIX = "custom("; @@ -540,9 +540,9 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener, } else { Log.i(TAG, "The activity is starting"); - ActivityLaunchAnimator.Controller controller = + ActivityTransitionAnimator.Controller controller = mViewClicked == null ? null : - ActivityLaunchAnimator.Controller.fromView( + ActivityTransitionAnimator.Controller.fromView( mViewClicked, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE ); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 529d68407ce9..35cac4b2adb2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -57,7 +57,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.systemui.Dumpable; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; @@ -70,7 +70,6 @@ import com.android.systemui.qs.SideLabelTileLayout; import com.android.systemui.qs.logging.QSLogger; import java.io.PrintWriter; -import java.util.ArrayList; /** * Base quick-settings tile, extend this to create a new tile. @@ -412,8 +411,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy * @param view The view from which the opening window will be animated. */ protected void handleLongClick(@Nullable View view) { - ActivityLaunchAnimator.Controller animationController = - view != null ? ActivityLaunchAnimator.Controller.fromView(view, + ActivityTransitionAnimator.Controller animationController = + view != null ? ActivityTransitionAnimator.Controller.fromView(view, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null; mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0, animationController); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index d98141fcd6b9..688f3ca38087 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -14,7 +14,7 @@ import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -75,7 +75,7 @@ class AlarmTile @Inject constructor( override fun handleClick(view: View?) { val animationController = view?.let { - ActivityLaunchAnimator.Controller.fromView( + ActivityTransitionAnimator.Controller.fromView( it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) } val pendingIntent = lastAlarmInfo?.showIntent diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index a69820868838..690b71100846 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -38,7 +38,7 @@ import com.android.internal.app.MediaRouteDialogPresenter; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; @@ -222,7 +222,7 @@ public class CastTile extends QSTileImpl<BooleanState> { mContext, ROUTE_TYPE_REMOTE_DISPLAY, v -> { - ActivityLaunchAnimator.Controller controller = + ActivityTransitionAnimator.Controller controller = mDialogLaunchAnimator.createActivityLaunchController(v); if (controller == null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 91b2d971c004..bb175e2a8534 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -26,7 +26,7 @@ import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE @@ -112,7 +112,7 @@ class DeviceControlsTile @Inject constructor( putExtra(ControlsUiController.EXTRA_ANIMATE, true) } val animationController = view?.let { - ActivityLaunchAnimator.Controller.fromView( + ActivityTransitionAnimator.Controller.fromView( it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java index f70e27d72d69..de9a08e1cda8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java @@ -27,8 +27,7 @@ import androidx.annotation.Nullable; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.res.R; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -40,6 +39,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import javax.inject.Inject; @@ -108,8 +108,8 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { return; } - ActivityLaunchAnimator.Controller animationController = - view == null ? null : ActivityLaunchAnimator.Controller.fromView(view, + ActivityTransitionAnimator.Controller animationController = + view == null ? null : ActivityTransitionAnimator.Controller.fromView(view, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); mActivityStarter.startActivity(intent, true /* dismissShade */, animationController, true /* showOverLockscreenWhenLocked */); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 3b8fb263c27e..1b7322592b41 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -43,7 +43,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -132,8 +132,8 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { @Override protected void handleClick(@Nullable View view) { - ActivityLaunchAnimator.Controller animationController = - view == null ? null : ActivityLaunchAnimator.Controller.fromView(view, + ActivityTransitionAnimator.Controller animationController = + view == null ? null : ActivityTransitionAnimator.Controller.fromView(view, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); mUiHandler.post( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt index fe10eaaec793..7192f58218a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt @@ -22,7 +22,7 @@ import android.content.pm.PackageManager import android.os.UserHandle import android.view.View import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject @@ -53,9 +53,9 @@ constructor( ) : QSTileIntentUserInputHandler { override fun handle(view: View?, intent: Intent) { - val animationController: ActivityLaunchAnimator.Controller? = + val animationController: ActivityTransitionAnimator.Controller? = view?.let { - ActivityLaunchAnimator.Controller.fromView( + ActivityTransitionAnimator.Controller.fromView( it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, ) @@ -70,9 +70,9 @@ constructor( requestLaunchingDefaultActivity: Boolean ) { if (pendingIntent.isActivity) { - val animationController: ActivityLaunchAnimator.Controller? = + val animationController: ActivityTransitionAnimator.Controller? = view?.let { - ActivityLaunchAnimator.Controller.fromView( + ActivityTransitionAnimator.Controller.fromView( it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 211b459471de..41de65c18aaa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -75,7 +75,7 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.settingslib.wifi.WifiUtils; import com.android.settingslib.wifi.dpp.WifiDppIntentHelper; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; @@ -748,7 +748,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi } private void startActivity(Intent intent, View view) { - ActivityLaunchAnimator.Controller controller = + ActivityTransitionAnimator.Controller controller = mDialogLaunchAnimator.createActivityLaunchController(view); if (controller == null && mCallback != null) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 1c7cc007cbc7..df845f559f2e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -35,6 +35,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf /** * Controller that's responsible for the glanceable hub container view and its touch handling. @@ -105,13 +106,9 @@ constructor( */ private var shadeShowing = false - /** Returns true if the glanceable hub is enabled and the container view can be created. */ - fun isEnabled(): Boolean { - return communalInteractor.isCommunalEnabled && isComposeAvailable() - } - - /** Returns a {@link StateFlow} that tracks whether communal hub is available. */ - fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable + /** Returns a flow that tracks whether communal hub is available. */ + fun communalAvailable(): Flow<Boolean> = + if (isComposeAvailable()) communalInteractor.isCommunalAvailable else flowOf(false) /** * Creates the container view containing the glanceable hub UI. @@ -127,9 +124,6 @@ constructor( /** Override for testing. */ @VisibleForTesting internal fun initView(containerView: View): View { - if (!isEnabled()) { - throw RuntimeException("Glanceable hub is not enabled") - } if (communalContainerView != null) { throw RuntimeException("Communal view has already been initialized") } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 73d229eae972..353e14338fbf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -112,7 +112,7 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.Gefingerpoken; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.TransitionAnimator; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -259,7 +259,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** The parallax amount of the quick settings translation when dragging down the panel. */ public static final float QS_PARALLAX_AMOUNT = 0.175f; private static final long ANIMATION_DELAY_ICON_FADE_IN = - ActivityLaunchAnimator.TIMINGS.getTotalDuration() + ActivityTransitionAnimator.TIMINGS.getTotalDuration() - CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY - 48; private static final int NO_FIXED_DURATION = -1; @@ -3231,7 +3231,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void applyLaunchAnimationProgress(float linearProgress) { - boolean hideIcons = TransitionAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, + boolean hideIcons = TransitionAnimator.getProgress(ActivityTransitionAnimator.TIMINGS, linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; if (hideIcons != mHideIconsDuringLaunchAnimation) { mHideIconsDuringLaunchAnimation = hideIcons; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index ef820f3dd9ae..aa2d606c5126 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -34,7 +34,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.LockIconViewController; import com.android.systemui.Dumpable; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.bouncer.ui.binder.BouncerViewBinder; @@ -679,7 +679,7 @@ public class NotificationShadeWindowViewController implements Dumpable { void setExpandAnimationRunning(boolean running) { if (mExpandAnimationRunning != running) { // TODO(b/288507023): Remove this log. - if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) { + if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { Log.d(TAG, "Setting mExpandAnimationRunning=" + running); } if (running) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index b64e0b7d3187..91340be8182b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -26,7 +26,7 @@ import android.util.Log; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.CoreStartable; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.AnimationFeatureFlags; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -190,8 +190,8 @@ public interface CentralSurfacesDependenciesModule { /** */ @Provides @SysUISingleton - static ActivityLaunchAnimator provideActivityLaunchAnimator() { - return new ActivityLaunchAnimator(); + static ActivityTransitionAnimator provideActivityTransitionAnimator() { + return new ActivityTransitionAnimator(); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt index 6f4a7cdaff5e..4af8cb9ba303 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt @@ -2,7 +2,7 @@ package com.android.systemui.statusbar.notification import android.util.MathUtils import com.android.internal.annotations.VisibleForTesting -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.app.animation.Interpolators import com.android.systemui.animation.TransitionAnimator import kotlin.math.min @@ -58,7 +58,11 @@ class LaunchAnimationParameters( } fun getProgress(delay: Long, duration: Long): Float { - return TransitionAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay, - duration) + return TransitionAnimator.getProgress( + ActivityTransitionAnimator.TIMINGS, + linearProgress, + delay, + duration + ) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt index 8fc961936abd..02d1e6fd458a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification import android.util.Log import android.view.ViewGroup import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.TransitionAnimator import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -55,9 +55,9 @@ class NotificationLaunchAnimatorControllerProvider( } /** - * An [ActivityLaunchAnimator.Controller] that animates an [ExpandableNotificationRow]. An instance - * of this class can be passed to [ActivityLaunchAnimator.startIntentWithAnimation] to animate a - * notification expanding into an opening window. + * An [ActivityTransitionAnimator.Controller] that animates an [ExpandableNotificationRow]. An + * instance of this class can be passed to [ActivityTransitionAnimator.startIntentWithAnimation] to + * animate a notification expanding into an opening window. */ class NotificationLaunchAnimatorController( private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, @@ -66,7 +66,7 @@ class NotificationLaunchAnimatorController( private val notification: ExpandableNotificationRow, private val jankMonitor: InteractionJankMonitor, private val onFinishAnimationCallback: Runnable? -) : ActivityLaunchAnimator.Controller { +) : ActivityTransitionAnimator.Controller { companion object { const val ANIMATION_DURATION_TOP_ROUNDING = 100L @@ -140,7 +140,7 @@ class NotificationLaunchAnimatorController( } override fun onIntentStarted(willAnimate: Boolean) { - if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) { + if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)") } notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate) @@ -173,8 +173,8 @@ class NotificationLaunchAnimatorController( headsUpManager.removeNotification(row.entry.key, true /* releaseImmediately */, animate) } - override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { - if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) { + override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { + if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { Log.d(TAG, "onLaunchAnimationCancelled()") } @@ -194,7 +194,7 @@ class NotificationLaunchAnimatorController( } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) { + if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { Log.d(TAG, "onLaunchAnimationEnd()") } jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 8b0b9735754b..c8ca63d9de91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -55,10 +55,9 @@ internal constructor( val notifStats = calculateNotifStats(entries) if (FooterViewRefactor.isEnabled) { activeNotificationsInteractor.setNotifStats(notifStats) + } else { + controller.setNotifStats(notifStats) } - // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the silent - // section clear action is handled in the new stack. - controller.setNotifStats(notifStats) if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) { renderListInteractor.setRenderedList(entries) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index b22e9fd2fb17..0dbc8c024c5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -102,6 +102,18 @@ constructor( .distinctUntilChanged() .flowOn(backgroundDispatcher) + val hasClearableAlertingNotifications: Flow<Boolean> = + repository.notifStats + .map { it.hasClearableAlertingNotifs } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + + val hasNonClearableSilentNotifications: Flow<Boolean> = + repository.notifStats + .map { it.hasNonClearableSilentNotifs } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + fun setNotifStats(notifStats: NotifStats) { repository.notifStats.value = notifStats } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt index 22ce4f11b661..a3189a01bdf7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.domain.interactor import android.util.Log -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository import javax.inject.Inject @@ -40,7 +40,7 @@ constructor(private val repository: NotificationLaunchAnimationRepository) { /** Sets whether the notification expansion launch animation is currently running. */ fun setIsLaunchAnimationRunning(running: Boolean) { - if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) { + if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { Log.d(TAG, "setIsLaunchAnimationRunning(running=$running)") } repository.isLaunchAnimationRunning.value = running diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index aca8b64c05d2..d7fe36f75418 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -223,7 +223,8 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting // info, and NotificationLockscreenUserManagerImpl is already listening for updates // to those - entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET + entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility == + VISIBILITY_SECRET } else { entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET } 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 9312dba36895..ced4085a446e 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 @@ -233,7 +233,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>(); private final ArrayList<View> mSwipedOutViews = new ArrayList<>(); private NotificationStackSizeCalculator mNotificationStackSizeCalculator; - private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); + private final StackStateAnimator mStateAnimator; private boolean mAnimationsEnabled; private boolean mChangePositionInProgress; private boolean mChildTransferInProgress; @@ -670,6 +670,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mExpandHelper.setScrollAdapter(mScrollAdapter); mStackScrollAlgorithm = createStackScrollAlgorithm(context); + mStateAnimator = new StackStateAnimator(context, this); mShouldDrawNotificationBackground = res.getBoolean(R.bool.config_drawNotificationBackground); setOutlineProvider(mOutlineProvider); @@ -1083,6 +1084,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); mStackScrollAlgorithm.initView(context); + mStateAnimator.initView(context); mAmbientState.reload(context); mPaddingBetweenElements = Math.max(1, res.getDimensionPixelSize(R.dimen.notification_divider_height)); @@ -4363,6 +4365,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements; } if (endPosition > layoutEnd) { + // if Scene Container is active, send bottom notification expansion delta + // to it so that it can scroll the stack and scrim accordingly. + if (SceneContainerFlag.isEnabled()) { + float diff = endPosition - layoutEnd; + mController.sendSyntheticScrollToSceneFramework(diff); + } setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd)); mDisallowScrollingInThisMotion = true; } @@ -5081,6 +5089,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) { + // If scene container is active, NSSL should not control its own scrolling. + if (SceneContainerFlag.isEnabled()) { + return; + } // Avoid Flicking during clear all // when the shade finishes closing, onExpansionStopped will call // resetScrollPosition to setOwnScrollY to 0 @@ -5365,23 +5377,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable && (!hasClipBounds || mTmpRect.height() > 0); } - private boolean shouldHideParent(View view, @SelectedRows int selection) { - final boolean silentSectionWillBeGone = - !mController.hasNotifications(ROWS_GENTLE, false /* clearable */); - - // The only SectionHeaderView we have is the silent section header. - if (view instanceof SectionHeaderView && silentSectionWillBeGone) { - return true; - } - if (view instanceof ExpandableNotificationRow row) { - if (isVisible(row) && includeChildInClearAll(row, selection)) { - return true; - } - } - return false; - } - - private boolean isChildrenVisible(ExpandableNotificationRow parent) { + /** Whether the group is expanded to show the child notifications, and they are visible. */ + private boolean areChildrenVisible(ExpandableNotificationRow parent) { List<ExpandableNotificationRow> children = parent.getAttachedChildren(); return isVisible(parent) && children != null @@ -5389,18 +5386,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } // Similar to #getRowsToDismissInBackend, but filters for visible views. - private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) { + private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection, + boolean hideSilentSection) { final int viewCount = getChildCount(); final ArrayList<View> viewsToHide = new ArrayList<>(viewCount); for (int i = 0; i < viewCount; i++) { final View view = getChildAt(i); - if (shouldHideParent(view, selection)) { - viewsToHide.add(view); + if (view instanceof SectionHeaderView) { + // The only SectionHeaderView we have is the silent section header. + if (hideSilentSection) { + viewsToHide.add(view); + } } + if (view instanceof ExpandableNotificationRow parent) { - if (isChildrenVisible(parent)) { + if (isVisible(parent) && includeChildInClearAll(parent, selection)) { + viewsToHide.add(parent); + } + + if (areChildrenVisible(parent)) { for (ExpandableNotificationRow child : parent.getAttachedChildren()) { if (isVisible(child) && includeChildInClearAll(child, selection)) { viewsToHide.add(child); @@ -5438,17 +5444,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } /** Clear all clearable notifications when the user requests it. */ - public void clearAllNotifications() { - clearNotifications(ROWS_ALL, /* closeShade = */ true); + public void clearAllNotifications(boolean hideSilentSection) { + clearNotifications(ROWS_ALL, /* closeShade = */ true, hideSilentSection); + } + + /** Clear all clearable silent notifications when the user requests it. */ + public void clearSilentNotifications(boolean closeShade, + boolean hideSilentSection) { + clearNotifications(ROWS_GENTLE, closeShade, hideSilentSection); + } + + /** Legacy version of clearNotifications below. Uses the old data source for notif stats. */ + void clearNotifications(@SelectedRows int selection, boolean closeShade) { + FooterViewRefactor.assertInLegacyMode(); + final boolean hideSilentSection = !mController.hasNotifications( + ROWS_GENTLE, false /* clearable */); + clearNotifications(selection, closeShade, hideSilentSection); } /** * Collects a list of visible rows, and animates them away in a staggered fashion as if they * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd. */ - void clearNotifications(@SelectedRows int selection, boolean closeShade) { + void clearNotifications(@SelectedRows int selection, boolean closeShade, + boolean hideSilentSection) { // Animate-swipe all dismissable notifications, then animate the shade closed - final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection); + final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection, + hideSilentSection); final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend = getRowsToDismissInBackend(selection); if (mClearAllListener != null) { 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 c744a4346f0c..2354c5276aa8 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 @@ -916,7 +916,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener); - mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications()); + if (!FooterViewRefactor.isEnabled()) { + mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications()); + } mGroupExpansionManager.registerGroupExpansionChangeListener( (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded)); @@ -1151,6 +1153,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { } } + /** Send internal notification expansion to the scene container framework. */ + public void sendSyntheticScrollToSceneFramework(Float delta) { + mStackAppearanceInteractor.setSyntheticScroll(delta); + } + /** Get the y-coordinate of the top bound of the stack. */ public float getPlaceholderTop() { return mStackAppearanceInteractor.getStackBounds().getValue().getTop(); @@ -1518,14 +1525,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { * Return whether there are any clearable notifications */ public boolean hasActiveClearableNotifications(@SelectedRows int selection) { - // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent - // section clear action in the new stack. + FooterViewRefactor.assertInLegacyMode(); return hasNotifications(selection, true /* clearable */); } public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { - // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent - // section clear action in the new stack. + FooterViewRefactor.assertInLegacyMode(); boolean hasAlertingMatchingClearable = isClearable ? mNotifStats.getHasClearableAlertingNotifs() : mNotifStats.getHasNonClearableAlertingNotifs(); @@ -1676,6 +1681,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void clearSilentNotifications() { + FooterViewRefactor.assertInLegacyMode(); // Leave the shade open if there will be other notifs left over to clear final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY); mView.clearNotifications(ROWS_GENTLE, closeShade); @@ -2146,8 +2152,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { private class NotifStackControllerImpl implements NotifStackController { @Override public void setNotifStats(@NonNull NotifStats notifStats) { - // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent - // section clear action in the new stack. + FooterViewRefactor.assertInLegacyMode(); mNotifStats = notifStats; if (!FooterViewRefactor.isEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index b38d619a4434..ab62ed65716e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -23,6 +23,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.content.Context; import android.util.Property; import android.view.View; @@ -66,9 +67,8 @@ public class StackStateAnimator { public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; private static final int MAX_STAGGER_COUNT = 5; - private final int mGoToFullShadeAppearingTranslation; - @VisibleForTesting - float mHeadsUpAppearStartAboveScreen; + @VisibleForTesting int mGoToFullShadeAppearingTranslation; + @VisibleForTesting float mHeadsUpAppearStartAboveScreen; private final ExpandableViewState mTmpState = new ExpandableViewState(); private final AnimationProperties mAnimationProperties; public NotificationStackScrollLayout mHostLayout; @@ -92,14 +92,9 @@ public class StackStateAnimator { private NotificationShelf mShelf; private StackStateLogger mLogger; - public StackStateAnimator(NotificationStackScrollLayout hostLayout) { + public StackStateAnimator(Context context, NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; - // TODO(b/317061579) reload on configuration changes - mGoToFullShadeAppearingTranslation = - hostLayout.getContext().getResources().getDimensionPixelSize( - R.dimen.go_to_full_shade_appearing_translation); - mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources() - .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen); + initView(context); mAnimationProperties = new AnimationProperties() { @Override public AnimationFilter getAnimationFilter() { @@ -118,6 +113,21 @@ public class StackStateAnimator { }; } + /** + * Needs to be called on configuration changes, to update cached resource values. + */ + public void initView(Context context) { + updateResources(context); + } + + private void updateResources(Context context) { + mGoToFullShadeAppearingTranslation = + context.getResources().getDimensionPixelSize( + R.dimen.go_to_full_shade_appearing_translation); + mHeadsUpAppearStartAboveScreen = context.getResources() + .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen); + } + protected void setLogger(StackStateLogger logger) { mLogger = logger; } @@ -460,15 +470,8 @@ public class StackStateAnimator { mHeadsUpAppearChildren.add(changingView); mTmpState.copyFrom(changingView.getViewState()); - if (event.headsUpFromBottom) { - // start from the bottom of the screen - mTmpState.setYTranslation( - mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen); - } else { - // start from the top of the screen - mTmpState.setYTranslation( - -mStackTopMargin - mHeadsUpAppearStartAboveScreen); - } + // translate the HUN in from the top, or the bottom of the screen + mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom)); // set the height and the initial position mTmpState.applyToView(changingView); mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y, @@ -512,12 +515,20 @@ public class StackStateAnimator { || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { mHeadsUpDisappearChildren.add(changingView); Runnable endRunnable = null; + mTmpState.copyFrom(changingView.getViewState()); if (changingView.getParent() == null) { // This notification was actually removed, so we need to add it // transiently mHostLayout.addTransientView(changingView, 0); changingView.setTransientContainer(mHostLayout); - mTmpState.initFrom(changingView); + if (NotificationsImprovedHunAnimation.isEnabled()) { + // StackScrollAlgorithm cannot find this view because it has been removed + // from the NSSL. To correctly translate the view to the top or bottom of + // the screen (where it animated from), we need to update its translation. + mTmpState.setYTranslation( + getHeadsUpYTranslationStart(event.headsUpFromBottom) + ); + } endRunnable = changingView::removeFromTransientContainer; } @@ -565,16 +576,19 @@ public class StackStateAnimator { changingView.setInRemovalAnimation(true); }; } - if (NotificationsImprovedHunAnimation.isEnabled()) { - mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y, - Interpolators.FAST_OUT_SLOW_IN_REVERSE); - } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, startAnimation, postAnimation, getGlobalAnimationFinishedListener()); mAnimationProperties.delay += removeAnimationDelay; + if (NotificationsImprovedHunAnimation.isEnabled()) { + mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR; + mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y, + Interpolators.FAST_OUT_SLOW_IN_REVERSE); + mAnimationProperties.getAnimationFilter().animateY = true; + mTmpState.animateTo(changingView, mAnimationProperties); + } } else if (endRunnable != null) { endRunnable.run(); } @@ -585,6 +599,15 @@ public class StackStateAnimator { return needsCustomAnimation; } + private float getHeadsUpYTranslationStart(boolean headsUpFromBottom) { + if (headsUpFromBottom) { + // start from the bottom of the screen + return mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen; + } + // start from the top of the screen + return -mStackTopMargin - mHeadsUpAppearStartAboveScreen; + } + public void animateOverScrollToAmount(float targetAmount, final boolean onTop, final boolean isRubberbanded) { final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt index 311ba83e85f1..9efe632f5dbb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -47,4 +47,11 @@ class NotificationStackAppearanceRepository @Inject constructor() { * further. */ val scrolledToTop = MutableStateFlow(true) + + /** + * The amount in px that the notification stack should scroll due to internal expansion. This + * should only happen when a notification expansion hits the bottom of the screen, so it is + * necessary to scroll up to keep expanding the notification. + */ + val syntheticScroll = MutableStateFlow(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 9984ba9c32ce..08df47388556 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository import javax.inject.Inject +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -50,6 +51,13 @@ constructor( */ val scrolledToTop: StateFlow<Boolean> = repository.scrolledToTop.asStateFlow() + /** + * The amount in px that the notification stack should scroll due to internal expansion. This + * should only happen when a notification expansion hits the bottom of the screen, so it is + * necessary to scroll up to keep expanding the notification. + */ + val syntheticScroll: Flow<Float> = repository.syntheticScroll.asStateFlow() + /** Sets the position of the notification stack in the current scene. */ fun setStackBounds(bounds: NotificationContainerBounds) { check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } @@ -70,4 +78,9 @@ constructor( fun setScrolledToTop(scrolledToTop: Boolean) { repository.scrolledToTop.value = scrolledToTop } + + /** Sets the amount (px) that the notification stack should scroll due to internal expansion. */ + fun setSyntheticScroll(delta: Float) { + repository.syntheticScroll.value = delta + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 883aa9ba668b..6b30393ebc42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -29,6 +29,8 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.NotificationActivityStarter +import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController +import com.android.systemui.statusbar.notification.dagger.SilentHeader import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder @@ -51,10 +53,14 @@ import java.util.Optional import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */ @@ -71,6 +77,7 @@ constructor( private val nicBinder: NotificationIconContainerShelfViewBinder, // Using a provider to avoid a circular dependency. private val notificationActivityStarter: Provider<NotificationActivityStarter>, + @SilentHeader private val silentHeaderController: SectionHeaderController, private val viewModel: NotificationListViewModel, ) { @@ -89,9 +96,14 @@ constructor( bindHideList(viewController, viewModel, hiderTracker) if (FooterViewRefactor.isEnabled) { - launch { reinflateAndBindFooter(view) } + val hasNonClearableSilentNotifications: StateFlow<Boolean> = + viewModel.hasNonClearableSilentNotifications.stateIn(this) + launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) } launch { bindEmptyShade(view) } launch { + bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) + } + launch { viewModel.isImportantForAccessibility.collect { isImportantForAccessibility -> view.setImportantForAccessibilityYesNo(isImportantForAccessibility) @@ -114,7 +126,10 @@ constructor( ) } - private suspend fun reinflateAndBindFooter(parentView: NotificationStackScrollLayout) { + private suspend fun reinflateAndBindFooter( + parentView: NotificationStackScrollLayout, + hasNonClearableSilentNotifications: StateFlow<Boolean> + ) { viewModel.footer.getOrNull()?.let { footerViewModel -> // The footer needs to be re-inflated every time the theme or the font size changes. configuration @@ -127,7 +142,12 @@ constructor( .collectLatest { footerView: FooterView -> traceAsync("bind FooterView") { parentView.setFooterView(footerView) - bindFooter(footerView, footerViewModel, parentView) + bindFooter( + footerView, + footerViewModel, + parentView, + hasNonClearableSilentNotifications + ) } } } @@ -139,7 +159,8 @@ constructor( private suspend fun bindFooter( footerView: FooterView, footerViewModel: FooterViewModel, - parentView: NotificationStackScrollLayout + parentView: NotificationStackScrollLayout, + hasNonClearableSilentNotifications: StateFlow<Boolean> ): Unit = coroutineScope { val disposableHandle = FooterViewBinder.bindWhileAttached( @@ -147,7 +168,12 @@ constructor( footerViewModel, clearAllNotifications = { metricsLogger.action(MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES) - parentView.clearAllNotifications() + clearAllNotifications( + parentView, + // Hide the silent section header (if present) if there will be + // no remaining silent notifications upon clearing. + hideSilentSection = !hasNonClearableSilentNotifications.value, + ) }, launchNotificationSettings = { view -> notificationActivityStarter @@ -187,6 +213,45 @@ constructor( } } + private suspend fun bindSilentHeaderClickListener( + parentView: NotificationStackScrollLayout, + hasNonClearableSilentNotifications: StateFlow<Boolean>, + ): Unit = coroutineScope { + val hasClearableAlertingNotifications: StateFlow<Boolean> = + viewModel.hasClearableAlertingNotifications.stateIn(this) + silentHeaderController.setOnClearSectionClickListener { + clearSilentNotifications( + view = parentView, + // Leave the shade open if there will be other notifs left over to clear. + closeShade = !hasClearableAlertingNotifications.value, + // Hide the silent section header itself, if there will be no remaining silent + // notifications upon clearing. + hideSilentSection = !hasNonClearableSilentNotifications.value, + ) + } + try { + awaitCancellation() + } finally { + silentHeaderController.setOnClearSectionClickListener {} + } + } + + private fun clearAllNotifications( + view: NotificationStackScrollLayout, + hideSilentSection: Boolean, + ) { + metricsLogger.action(MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES) + view.clearAllNotifications(hideSilentSection) + } + + private fun clearSilentNotifications( + view: NotificationStackScrollLayout, + closeShade: Boolean, + hideSilentSection: Boolean + ) { + view.clearSilentNotifications(closeShade, hideSilentSection) + } + private suspend fun bindLogger(view: NotificationStackScrollLayout) { if (NotificationsLiveDataStoreRefactor.isEnabled) { viewModel.logger.getOrNull()?.let { viewModel -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt index 814146c329a6..a1577854e732 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -69,6 +69,9 @@ object NotificationStackAppearanceViewBinder { controller.setMaxAlphaForExpansion( ((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f) ) + if (expandFraction == 0f || expandFraction == 1f) { + controller.onExpansionStopped() + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index a6c658676687..6321820b0733 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor @@ -54,7 +52,6 @@ constructor( val logger: Optional<NotificationLoggerViewModel>, activeNotificationsInteractor: ActiveNotificationsInteractor, keyguardInteractor: KeyguardInteractor, - keyguardTransitionInteractor: KeyguardTransitionInteractor, powerInteractor: PowerInteractor, remoteInputInteractor: RemoteInputInteractor, seenNotificationsInteractor: SeenNotificationsInteractor, @@ -74,11 +71,9 @@ constructor( } else { combine( activeNotificationsInteractor.areAnyNotificationsPresent, - keyguardTransitionInteractor.isFinishedInStateWhere { - KeyguardState.lockscreenVisibleInState(it) - } - ) { hasNotifications, isOnKeyguard -> - hasNotifications || !isOnKeyguard + isShowingOnLockscreen, + ) { hasNotifications, isShowingOnLockscreen -> + hasNotifications || !isShowingOnLockscreen } .distinctUntilChanged() } @@ -91,26 +86,17 @@ constructor( combine( activeNotificationsInteractor.areAnyNotificationsPresent, shadeInteractor.isQsFullscreen, - // TODO(b/293167744): It looks like we're essentially trying to check the same - // things for the empty shade visibility as we do for the footer, just in a - // slightly different way. We should change this so we also check - // statusBarState and isAwake instead of specific keyguard transitions. - keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart { - emit(false) - }, - keyguardTransitionInteractor - .isFinishedInState(KeyguardState.PRIMARY_BOUNCER) - .onStart { emit(false) } - ) { hasNotifications, isQsFullScreen, transitioningToAOD, isBouncerShowing -> - !hasNotifications && - !isQsFullScreen && - // Hide empty shade view when in transition to AOD. - // That avoids "No Notifications" blinking when transitioning to AOD. - // For more details, see b/228790482. - !transitioningToAOD && - // Don't show any notification content if the bouncer is showing. See - // b/267060171. - !isBouncerShowing + isShowingOnLockscreen, + ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen -> + when { + hasNotifications -> false + isQsFullScreen -> false + // Do not show the empty shade if the lockscreen is visible (including AOD + // b/228790482 and bouncer b/267060171), except if the shade is opened on + // top. + isShowingOnLockscreen -> false + else -> true + } } .distinctUntilChanged() } @@ -123,52 +109,47 @@ constructor( combine( activeNotificationsInteractor.areAnyNotificationsPresent, userSetupInteractor.isUserSetUp, - keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD }, + isShowingOnLockscreen, shadeInteractor.qsExpansion, shadeInteractor.isQsFullscreen, - powerInteractor.isAsleep, remoteInputInteractor.isRemoteInputActive, shadeInteractor.shadeExpansion.map { it == 0f } ) { hasNotifications, isUserSetUp, - isOnKeyguard, + isShowingOnLockscreen, qsExpansion, qsFullScreen, - isAsleep, isRemoteInputActive, isShadeClosed -> - Pair( - // Should the footer be visible? - when { - !hasNotifications -> false - // Hide the footer until the user setup is complete, to prevent access - // to settings (b/193149550). - !isUserSetUp -> false - // Do not show the footer if the lockscreen is visible (incl. AOD), - // except if the shade is opened on top. See also b/219680200. - isOnKeyguard -> false - // Make sure we're not showing the footer in the transition to AOD while - // going to sleep (b/190227875). The StatusBarState is unfortunately not - // updated quickly enough when the power button is pressed, so this is - // necessary in addition to the isOnKeyguard check. - isAsleep -> false - // Do not show the footer if quick settings are fully expanded (except - // for the foldable split shade view). See b/201427195 && b/222699879. - qsExpansion == 1f && qsFullScreen -> false - // Hide the footer if remote input is active (i.e. user is replying to a - // notification). See b/75984847. - isRemoteInputActive -> false - // Never show the footer if the shade is collapsed (e.g. when HUNing). - isShadeClosed -> false - else -> true - }, - // This could in theory be in the .sample below, but it tends to be - // inconsistent, so we're passing it on to make sure we have the same state. - isOnKeyguard - ) + // A pair of (visible, canAnimate) + when { + !hasNotifications -> Pair(false, true) + // Hide the footer until the user setup is complete, to prevent access + // to settings (b/193149550). + !isUserSetUp -> Pair(false, true) + // Do not show the footer if the lockscreen is visible (incl. AOD), + // except if the shade is opened on top. See also b/219680200. + // Do not animate, as that makes the footer appear briefly when + // transitioning between the shade and keyguard. + isShowingOnLockscreen -> Pair(false, false) + // Do not show the footer if quick settings are fully expanded (except + // for the foldable split shade view). See b/201427195 && b/222699879. + qsExpansion == 1f && qsFullScreen -> Pair(false, true) + // Hide the footer if remote input is active (i.e. user is replying to a + // notification). See b/75984847. + isRemoteInputActive -> Pair(false, true) + // Never show the footer if the shade is collapsed (e.g. when HUNing). + isShadeClosed -> Pair(false, false) + else -> Pair(true, true) + } } - .distinctUntilChanged() + .distinctUntilChanged( + // Equivalent unless visibility changes + areEquivalent = { a: Pair<Boolean, Boolean>, b: Pair<Boolean, Boolean> -> + a.first == b.first + } + ) // Should we animate the visibility change? .sample( // TODO(b/322167853): This check is currently duplicated in FooterViewModel, @@ -179,17 +160,40 @@ constructor( ::Pair ) .onStart { emit(Pair(false, false)) } - ) { (visible, isOnKeyguard), (isShadeFullyExpanded, animationsEnabled) -> + ) { (visible, canAnimate), (isShadeFullyExpanded, animationsEnabled) -> // Animate if the shade is interactive, but NOT on the lockscreen. Having // animations enabled while on the lockscreen makes the footer appear briefly // when transitioning between the shade and keyguard. - val shouldAnimate = isShadeFullyExpanded && animationsEnabled && !isOnKeyguard + val shouldAnimate = isShadeFullyExpanded && animationsEnabled && canAnimate AnimatableEvent(visible, shouldAnimate) } .toAnimatedValueFlow() } } + private val isShowingOnLockscreen: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + combine( + // Non-notification UI elements of the notification list should not be visible + // on the lockscreen (incl. AOD and bouncer), except if the shade is opened on + // top. See b/219680200 for the footer and b/228790482, b/267060171 for the + // empty shade. + // TODO(b/323187006): There's a plan to eventually get rid of StatusBarState + // entirely, so this will have to be replaced at some point. + keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD }, + // The StatusBarState is unfortunately not updated quickly enough when the power + // button is pressed, so this is necessary in addition to the KEYGUARD check to + // cover the transition to AOD while going to sleep (b/190227875). + powerInteractor.isAsleep, + ) { (isOnKeyguard, isAsleep) -> + isOnKeyguard || isAsleep + } + .distinctUntilChanged() + } + } + // TODO(b/308591475): This should be tracked separately by the empty shade. val areNotificationsHiddenInShade: Flow<Boolean> by lazy { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { @@ -207,4 +211,20 @@ constructor( seenNotificationsInteractor.hasFilteredOutSeenNotifications } } + + val hasClearableAlertingNotifications: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + activeNotificationsInteractor.hasClearableAlertingNotifications + } + } + + val hasNonClearableSilentNotifications: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + activeNotificationsInteractor.hasNonClearableSilentNotifications + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index bdf1a6431549..3a0f03f70e1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -28,6 +28,7 @@ import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */ @SysUISingleton @@ -45,31 +46,32 @@ constructor( */ val expandFraction: Flow<Float> = combine( - shadeInteractor.shadeExpansion, - sceneInteractor.transitionState, - ) { shadeExpansion, transitionState -> - when (transitionState) { - is ObservableTransitionState.Idle -> { - if (transitionState.scene == SceneKey.Lockscreen) { - 1f - } else { - shadeExpansion + shadeInteractor.shadeExpansion, + sceneInteractor.transitionState, + ) { shadeExpansion, transitionState -> + when (transitionState) { + is ObservableTransitionState.Idle -> { + if (transitionState.scene == SceneKey.Lockscreen) { + 1f + } else { + shadeExpansion + } } - } - is ObservableTransitionState.Transition -> { - if ( - (transitionState.fromScene == SceneKey.Shade && - transitionState.toScene == SceneKey.QuickSettings) || - (transitionState.fromScene == SceneKey.QuickSettings && - transitionState.toScene == SceneKey.Shade) - ) { - 1f - } else { - shadeExpansion + is ObservableTransitionState.Transition -> { + if ( + (transitionState.fromScene == SceneKey.Shade && + transitionState.toScene == SceneKey.QuickSettings) || + (transitionState.fromScene == SceneKey.QuickSettings && + transitionState.toScene == SceneKey.Shade) + ) { + 1f + } else { + shadeExpansion + } } } } - } + .distinctUntilChanged() /** The bounds of the notification stack in the current scene. */ val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 65d9c9f523f8..7ac5cd48f24a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -86,6 +86,13 @@ constructor( */ val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion + /** + * The amount in px that the notification stack should scroll due to internal expansion. This + * should only happen when a notification expansion hits the bottom of the screen, so it is + * necessary to scroll up to keep expanding the notification. + */ + val syntheticScroll: Flow<Float> = interactor.syntheticScroll + /** Sets the y-coord in px of the top of the contents of the notification stack. */ fun onContentTopChanged(padding: Float) { interactor.setContentTop(padding) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index b49af0e64772..80d45fb9a4d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -30,8 +30,8 @@ import android.view.View import android.view.WindowManager import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.ActivityIntentHelper -import com.android.systemui.animation.ActivityLaunchAnimator -import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter +import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.ActivityTransitionAnimator.PendingIntentStarter import com.android.systemui.animation.DelegateLaunchAnimatorController import com.android.systemui.assist.AssistManager import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent @@ -75,7 +75,7 @@ constructor( private val shadeAnimationInteractor: ShadeAnimationInteractor, private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>, private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>, - private val activityLaunchAnimator: ActivityLaunchAnimator, + private val activityTransitionAnimator: ActivityTransitionAnimator, private val context: Context, @DisplayId private val displayId: Int, private val lockScreenUserManager: NotificationLockscreenUserManager, @@ -127,7 +127,7 @@ constructor( override fun startPendingIntentDismissingKeyguard( intent: PendingIntent, intentSentUiThreadCallback: Runnable?, - animationController: ActivityLaunchAnimator.Controller?, + animationController: ActivityTransitionAnimator.Controller?, ) { activityStarterInternal.startPendingIntentDismissingKeyguard( intent = intent, @@ -139,7 +139,7 @@ constructor( override fun startPendingIntentMaybeDismissingKeyguard( intent: PendingIntent, intentSentUiThreadCallback: Runnable?, - animationController: ActivityLaunchAnimator.Controller? + animationController: ActivityTransitionAnimator.Controller? ) { activityStarterInternal.startPendingIntentDismissingKeyguard( intent = intent, @@ -209,7 +209,7 @@ constructor( override fun startActivity( intent: Intent, dismissShade: Boolean, - animationController: ActivityLaunchAnimator.Controller?, + animationController: ActivityTransitionAnimator.Controller?, showOverLockscreenWhenLocked: Boolean, ) { activityStarterInternal.startActivity( @@ -222,7 +222,7 @@ constructor( override fun startActivity( intent: Intent, dismissShade: Boolean, - animationController: ActivityLaunchAnimator.Controller?, + animationController: ActivityTransitionAnimator.Controller?, showOverLockscreenWhenLocked: Boolean, userHandle: UserHandle?, ) { @@ -245,7 +245,7 @@ constructor( override fun postStartActivityDismissingKeyguard( intent: PendingIntent, - animationController: ActivityLaunchAnimator.Controller? + animationController: ActivityTransitionAnimator.Controller? ) { postOnUiThread { activityStarterInternal.startPendingIntentDismissingKeyguard( @@ -268,7 +268,7 @@ constructor( override fun postStartActivityDismissingKeyguard( intent: Intent, delay: Int, - animationController: ActivityLaunchAnimator.Controller?, + animationController: ActivityTransitionAnimator.Controller?, ) { postOnUiThread(delay) { activityStarterInternal.startActivityDismissingKeyguard( @@ -283,7 +283,7 @@ constructor( override fun postStartActivityDismissingKeyguard( intent: Intent, delay: Int, - animationController: ActivityLaunchAnimator.Controller?, + animationController: ActivityTransitionAnimator.Controller?, customMessage: String?, ) { postOnUiThread(delay) { @@ -342,7 +342,7 @@ constructor( disallowEnterPictureInPictureWhileLaunching: Boolean, callback: ActivityStarter.Callback?, flags: Int, - animationController: ActivityLaunchAnimator.Controller?, + animationController: ActivityTransitionAnimator.Controller?, userHandle: UserHandle?, ) { activityStarterInternal.startActivityDismissingKeyguard( @@ -430,7 +430,7 @@ constructor( disallowEnterPictureInPictureWhileLaunching: Boolean = false, callback: ActivityStarter.Callback? = null, flags: Int = 0, - animationController: ActivityLaunchAnimator.Controller? = null, + animationController: ActivityTransitionAnimator.Controller? = null, userHandle: UserHandle? = null, customMessage: String? = null, ) { @@ -464,7 +464,7 @@ constructor( intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP intent.addFlags(flags) val result = intArrayOf(ActivityManager.START_CANCELED) - activityLaunchAnimator.startIntentWithAnimation( + activityTransitionAnimator.startIntentWithAnimation( animController, animate, intent.getPackage() @@ -552,7 +552,7 @@ constructor( intent: PendingIntent, intentSentUiThreadCallback: Runnable? = null, associatedView: View? = null, - animationController: ActivityLaunchAnimator.Controller? = null, + animationController: ActivityTransitionAnimator.Controller? = null, showOverLockscreen: Boolean = false, ) { val animationController = @@ -602,7 +602,7 @@ constructor( val collapse = !animate val runnable = Runnable { try { - activityLaunchAnimator.startPendingIntentWithAnimation( + activityTransitionAnimator.startPendingIntentWithAnimation( controller, animate, intent.creatorPackage, @@ -670,7 +670,7 @@ constructor( fun startActivity( intent: Intent, dismissShade: Boolean = false, - animationController: ActivityLaunchAnimator.Controller? = null, + animationController: ActivityTransitionAnimator.Controller? = null, showOverLockscreenWhenLocked: Boolean = false, userHandle: UserHandle? = null, ) { @@ -698,7 +698,7 @@ constructor( showOverLockscreenWhenLocked ) == true - var controller: ActivityLaunchAnimator.Controller? = null + var controller: ActivityTransitionAnimator.Controller? = null if (animate) { // Wrap the animation controller to dismiss the shade and set // mIsLaunchingActivityOverLockscreen during the animation. @@ -721,7 +721,7 @@ constructor( centralSurfaces?.awakenDreams() } - activityLaunchAnimator.startIntentWithAnimation( + activityTransitionAnimator.startIntentWithAnimation( controller, animate, intent.getPackage(), @@ -815,7 +815,7 @@ constructor( } /** - * Return a [ActivityLaunchAnimator.Controller] wrapping `animationController` so that: + * Return a [ActivityTransitionAnimator.Controller] wrapping `animationController` so that: * - if it launches in the notification shade window and `dismissShade` is true, then the * shade will be instantly dismissed at the end of the animation. * - if it launches in status bar window, it will make the status bar window match the @@ -830,15 +830,15 @@ constructor( * @param isLaunchForActivity whether the launch is for an activity. */ private fun wrapAnimationControllerForShadeOrStatusBar( - animationController: ActivityLaunchAnimator.Controller?, + animationController: ActivityTransitionAnimator.Controller?, dismissShade: Boolean, isLaunchForActivity: Boolean, - ): ActivityLaunchAnimator.Controller? { + ): ActivityTransitionAnimator.Controller? { if (animationController == null) { return null } val rootView = animationController.transitionContainer.rootView - val controllerFromStatusBar: Optional<ActivityLaunchAnimator.Controller> = + val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> = statusBarWindowController.wrapAnimationControllerIfInStatusBar( rootView, animationController @@ -870,8 +870,8 @@ constructor( * lockscreen, the correct flags are set for it to be occluded. */ private fun wrapAnimationControllerForLockscreen( - animationController: ActivityLaunchAnimator.Controller? - ): ActivityLaunchAnimator.Controller? { + animationController: ActivityTransitionAnimator.Controller? + ): ActivityTransitionAnimator.Controller? { return animationController?.let { object : DelegateLaunchAnimatorController(it) { override fun onIntentStarted(willAnimate: Boolean) { @@ -912,7 +912,9 @@ constructor( delegate.onTransitionAnimationEnd(isExpandingFullyAbove) } - override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { + override fun onTransitionAnimationCancelled( + newKeyguardOccludedState: Boolean? + ) { if (newKeyguardOccludedState != null) { keyguardViewMediatorLazy .get() @@ -925,7 +927,7 @@ constructor( // collapse the shade (or at least run the // post collapse // runnables) later on. centralSurfaces?.setIsLaunchingActivityOverLockscreen(false) - delegate.onLaunchAnimationCancelled(newKeyguardOccludedState) + delegate.onTransitionAnimationCancelled(newKeyguardOccludedState) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 40194361e12b..9052409b4d8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -38,7 +38,7 @@ import androidx.lifecycle.LifecycleOwner; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.systemui.Dumpable; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.display.data.repository.DisplayMetricsRepository; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; @@ -334,6 +334,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { /** * Gets an animation controller from a notification row. */ - ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification( + ActivityTransitionAnimator.Controller getAnimatorControllerFromNotification( ExpandableNotificationRow associatedView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt index 60dfaa790ec9..8af7ee8389e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -20,7 +20,7 @@ import android.content.Intent import android.view.MotionEvent import androidx.lifecycle.LifecycleRegistry import com.android.keyguard.AuthKeyguardMessageArea -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.navigationbar.NavigationBarView import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.qs.QSPanelController @@ -99,5 +99,5 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {} override fun getAnimatorControllerFromNotification( associatedView: ExpandableNotificationRow?, - ): ActivityLaunchAnimator.Controller? = null + ): ActivityTransitionAnimator.Controller? = null } 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 3e7089c70deb..35aa3df5b8d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -115,7 +115,7 @@ import com.android.systemui.EventLogTags; import com.android.systemui.InitController; import com.android.systemui.Prefs; import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.back.domain.interactor.BackActionInteractor; import com.android.systemui.biometrics.AuthRippleController; @@ -576,7 +576,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private boolean mNoAnimationOnNextBarModeChange; private final SysuiStatusBarStateController mStatusBarStateController; - private final ActivityLaunchAnimator mActivityLaunchAnimator; + private final ActivityTransitionAnimator mActivityTransitionAnimator; private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider; private final Lazy<NotificationPresenter> mPresenterLazy; private final Lazy<NotificationActivityStarter> mNotificationActivityStarterLazy; @@ -694,7 +694,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Main MessageRouter messageRouter, WallpaperManager wallpaperManager, Optional<StartingSurface> startingSurfaceOptional, - ActivityLaunchAnimator activityLaunchAnimator, + ActivityTransitionAnimator activityTransitionAnimator, DeviceStateManager deviceStateManager, WiredChargingRippleController wiredChargingRippleController, IDreamManager dreamManager, @@ -816,7 +816,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { shadeExpansionListener.onPanelExpansionChanged(currentState); mActivityIntentHelper = new ActivityIntentHelper(mContext); - mActivityLaunchAnimator = activityLaunchAnimator; + mActivityTransitionAnimator = activityTransitionAnimator; // TODO(b/190746471): Find a better home for this. DateTimeView.setReceiverHandler(timeTickHandler); @@ -1424,8 +1424,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void setUpPresenter() { // Set up the initial notification state. - mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback); - mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener); + mActivityTransitionAnimator.setCallback(mActivityTransitionAnimatorCallback); + mActivityTransitionAnimator.addListener(mActivityTransitionAnimatorListener); mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController); mStackScrollerController.setNotificationActivityStarter( mNotificationActivityStarterLazy.get()); @@ -3176,8 +3176,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } }; - private final ActivityLaunchAnimator.Callback mActivityLaunchAnimatorCallback = - new ActivityLaunchAnimator.Callback() { + private final ActivityTransitionAnimator.Callback mActivityTransitionAnimatorCallback = + new ActivityTransitionAnimator.Callback() { @Override public boolean isOnKeyguard() { return mKeyguardStateController.isShowing(); @@ -3205,15 +3205,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } }; - private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener = - new ActivityLaunchAnimator.Listener() { + private final ActivityTransitionAnimator.Listener mActivityTransitionAnimatorListener = + new ActivityTransitionAnimator.Listener() { @Override - public void onLaunchAnimationStart() { + public void onTransitionAnimationStart() { mKeyguardViewMediator.setBlursDisabledForAppLaunch(true); } @Override - public void onLaunchAnimationEnd() { + public void onTransitionAnimationEnd() { mKeyguardViewMediator.setBlursDisabledForAppLaunch(false); } }; @@ -3267,7 +3267,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification( + public ActivityTransitionAnimator.Controller getAnimatorControllerFromNotification( ExpandableNotificationRow associatedView) { return mNotificationAnimationProvider.getAnimatorController(associatedView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index be5c6b3ba069..8e3d678c152a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -349,8 +349,12 @@ public class NotificationIconContainer extends ViewGroup { } } if (child instanceof StatusBarIconView) { - ((StatusBarIconView) child).updateIconDimens(); - if (!NotificationIconContainerRefactor.isEnabled()) { + if (NotificationIconContainerRefactor.isEnabled()) { + if (!mChangingViewPositions) { + ((StatusBarIconView) child).updateIconDimens(); + } + } else { + ((StatusBarIconView) child).updateIconDimens(); ((StatusBarIconView) child).setDozing(mDozing, false, 0); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index d43f4709a32d..7ff5f6e357db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -1,7 +1,7 @@ package com.android.systemui.statusbar.phone import android.view.View -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.TransitionAnimator import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController @@ -9,17 +9,17 @@ import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor import com.android.systemui.statusbar.NotificationShadeWindowController /** - * A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right + * A [ActivityTransitionAnimator.Controller] that takes care of collapsing the status bar at the right * time. */ class StatusBarLaunchAnimatorController( - private val delegate: ActivityLaunchAnimator.Controller, + private val delegate: ActivityTransitionAnimator.Controller, private val shadeViewController: ShadeViewController, private val shadeAnimationInteractor: ShadeAnimationInteractor, private val shadeController: ShadeController, private val notificationShadeWindowController: NotificationShadeWindowController, private val isLaunchForActivity: Boolean = true -) : ActivityLaunchAnimator.Controller by delegate { +) : ActivityTransitionAnimator.Controller by delegate { // Always sync the opening window with the shade, given that we draw a hole punch in the shade // of the same size and position as the opening app to make it visible. override val openingWindowSyncView: View? @@ -39,7 +39,7 @@ class StatusBarLaunchAnimatorController( shadeAnimationInteractor.setIsLaunchingActivity(true) if (!isExpandingFullyAbove) { shadeViewController.collapseWithDuration( - ActivityLaunchAnimator.TIMINGS.totalDuration.toInt()) + ActivityTransitionAnimator.TIMINGS.totalDuration.toInt()) } } @@ -58,8 +58,8 @@ class StatusBarLaunchAnimatorController( shadeViewController.applyLaunchAnimationProgress(linearProgress) } - override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { - delegate.onLaunchAnimationCancelled() + override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { + delegate.onTransitionAnimationCancelled() shadeAnimationInteractor.setIsLaunchingActivity(false) shadeController.onLaunchAnimationCancelled(isLaunchForActivity) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 4ee061d05a3b..273758044a56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -52,7 +52,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.EventLogTags; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.DisplayId; @@ -125,7 +125,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final NotificationPresenter mPresenter; private final ShadeViewController mShadeViewController; private final NotificationShadeWindowController mNotificationShadeWindowController; - private final ActivityLaunchAnimator mActivityLaunchAnimator; + private final ActivityTransitionAnimator mActivityTransitionAnimator; private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider; private final PowerInteractor mPowerInteractor; private final UserTracker mUserTracker; @@ -161,7 +161,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit NotificationPresenter presenter, ShadeViewController shadeViewController, NotificationShadeWindowController notificationShadeWindowController, - ActivityLaunchAnimator activityLaunchAnimator, + ActivityTransitionAnimator activityTransitionAnimator, ShadeAnimationInteractor shadeAnimationInteractor, NotificationLaunchAnimatorControllerProvider notificationAnimationProvider, LaunchFullScreenIntentProvider launchFullScreenIntentProvider, @@ -194,7 +194,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mOnUserInteractionCallback = onUserInteractionCallback; mPresenter = presenter; mShadeViewController = shadeViewController; - mActivityLaunchAnimator = activityLaunchAnimator; + mActivityTransitionAnimator = activityTransitionAnimator; mNotificationAnimationProvider = notificationAnimationProvider; mPowerInteractor = powerInteractor; mUserTracker = userTracker; @@ -440,7 +440,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit boolean isActivityIntent) { mLogger.logStartNotificationIntent(entry); try { - ActivityLaunchAnimator.Controller animationController = + ActivityTransitionAnimator.Controller animationController = new StatusBarLaunchAnimatorController( mNotificationAnimationProvider.getAnimatorController(row, null), mShadeViewController, @@ -448,7 +448,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mShadeController, mNotificationShadeWindowController, isActivityIntent); - mActivityLaunchAnimator.startPendingIntentWithAnimation( + mActivityTransitionAnimator.startPendingIntentWithAnimation( animationController, animate, intent.getCreatorPackage(), @@ -482,7 +482,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit @Override public boolean onDismiss() { AsyncTask.execute(() -> { - ActivityLaunchAnimator.Controller animationController = + ActivityTransitionAnimator.Controller animationController = new StatusBarLaunchAnimatorController( mNotificationAnimationProvider.getAnimatorController(row), mShadeViewController, @@ -491,7 +491,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mNotificationShadeWindowController, true /* isActivityIntent */); - mActivityLaunchAnimator.startIntentWithAnimation( + mActivityTransitionAnimator.startIntentWithAnimation( animationController, animate, intent.getPackage(), (adapter) -> TaskStackBuilder.create(mContext) .addNextIntentWithParentStack(intent) @@ -528,11 +528,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit tsb.addNextIntent(intent); } - ActivityLaunchAnimator.Controller viewController = - ActivityLaunchAnimator.Controller.fromView(view, + ActivityTransitionAnimator.Controller viewController = + ActivityTransitionAnimator.Controller.fromView(view, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON ); - ActivityLaunchAnimator.Controller animationController = + ActivityTransitionAnimator.Controller animationController = viewController == null ? null : new StatusBarLaunchAnimatorController( viewController, @@ -542,8 +542,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mNotificationShadeWindowController, true /* isActivityIntent */); - mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate, - intent.getPackage(), + mActivityTransitionAnimator.startIntentWithAnimation( + animationController, animate, intent.getPackage(), (adapter) -> tsb.startActivities( getActivityOptions(mDisplayId, adapter), mUserTracker.getUserHandle())); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 0bdd1a5b4d5f..a20468f9e3ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -30,7 +30,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.CoreStartable import com.android.systemui.Dumpable import com.android.systemui.res.R -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -233,7 +233,7 @@ class OngoingCallController @Inject constructor( logger.logChipClicked() activityStarter.postStartActivityDismissingKeyguard( intent, - ActivityLaunchAnimator.Controller.fromView( + ActivityTransitionAnimator.Controller.fromView( backgroundView, InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP) ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index 21d3fa47f51c..65c2e20350ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -47,7 +47,7 @@ import android.view.WindowInsets; import android.view.WindowManager; import com.android.internal.policy.SystemBarUtils; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DelegateLaunchAnimatorController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -188,8 +188,8 @@ public class StatusBarWindowController { * updated animation controller that handles status-bar-related animation details. Returns an * empty optional if the animation is *not* on a view in the status bar. */ - public Optional<ActivityLaunchAnimator.Controller> wrapAnimationControllerIfInStatusBar( - View rootView, ActivityLaunchAnimator.Controller animationController) { + public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar( + View rootView, ActivityTransitionAnimator.Controller animationController) { if (rootView != mStatusBarWindowView) { return Optional.empty(); } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 3376e232e035..147e158bd393 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -15,6 +15,8 @@ */ package com.android.systemui.theme; +import static com.android.systemui.shared.Flags.enableHomeDelay; + import android.annotation.AnyThread; import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; @@ -32,6 +34,7 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.google.android.collect.Lists; @@ -142,6 +145,7 @@ public class ThemeOverlayApplier implements Dumpable { private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>(); private final OverlayManager mOverlayManager; private final Executor mBgExecutor; + private final Executor mMainExecutor; private final String mLauncherPackage; private final String mThemePickerPackage; @@ -150,9 +154,11 @@ public class ThemeOverlayApplier implements Dumpable { @Background Executor bgExecutor, @Named(ThemeModule.LAUNCHER_PACKAGE) String launcherPackage, @Named(ThemeModule.THEME_PICKER_PACKAGE) String themePickerPackage, - DumpManager dumpManager) { + DumpManager dumpManager, + @Main Executor mainExecutor) { mOverlayManager = overlayManager; mBgExecutor = bgExecutor; + mMainExecutor = mainExecutor; mLauncherPackage = launcherPackage; mThemePickerPackage = themePickerPackage; mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet( @@ -184,12 +190,21 @@ public class ThemeOverlayApplier implements Dumpable { /** * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that * affect sysui will also be applied to the system user. + * + * @param categoryToPackage Overlay packages to be applied + * @param pendingCreation Overlays yet to be created + * @param currentUser Current User ID + * @param managedProfiles Profiles get overlays + * @param onComplete Callback for when resources are ready. Runs in the main thread. */ public void applyCurrentUserOverlays( Map<String, OverlayIdentifier> categoryToPackage, FabricatedOverlay[] pendingCreation, int currentUser, - Set<UserHandle> managedProfiles) { + Set<UserHandle> managedProfiles, + Runnable onComplete + ) { + mBgExecutor.execute(() -> { // Disable all overlays that have not been specified in the user setting. @@ -236,6 +251,10 @@ public class ThemeOverlayApplier implements Dumpable { try { mOverlayManager.commit(transaction.build()); + if (enableHomeDelay() && onComplete != null) { + Log.d(TAG, "Executing onComplete runnable"); + mMainExecutor.execute(onComplete); + } } catch (SecurityException | IllegalStateException e) { Log.e(TAG, "setEnabled failed", e); } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 2b9ad50c1257..585ab72dd963 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -20,6 +20,7 @@ import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; +import static com.android.systemui.shared.Flags.enableHomeDelay; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET; @@ -31,6 +32,7 @@ import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_INDEX import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE; import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD; +import android.app.ActivityManager; import android.app.UiModeManager; import android.app.WallpaperColors; import android.app.WallpaperManager; @@ -140,6 +142,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { // Current wallpaper colors associated to a user. private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>(); private final WallpaperManager mWallpaperManager; + private final ActivityManager mActivityManager; @VisibleForTesting protected ColorScheme mColorScheme; // If fabricated overlays were already created for the current theme. @@ -414,7 +417,8 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { WakefulnessLifecycle wakefulnessLifecycle, JavaAdapter javaAdapter, KeyguardTransitionInteractor keyguardTransitionInteractor, - UiModeManager uiModeManager) { + UiModeManager uiModeManager, + ActivityManager activityManager) { mContext = context; mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET); mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY); @@ -433,6 +437,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { mJavaAdapter = javaAdapter; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mUiModeManager = uiModeManager; + mActivityManager = activityManager; dumpManager.registerDumpable(TAG, this); } @@ -806,8 +811,16 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { } } + final Runnable onCompleteCallback = !enableHomeDelay() + ? () -> {} + : () -> { + Log.d(TAG, "ThemeHomeDelay: ThemeOverlayController ready"); + mActivityManager.setThemeOverlayReady(true); + }; + if (colorSchemeIsApplied(managedProfiles)) { Log.d(TAG, "Skipping overlay creation. Theme was already: " + mColorScheme); + onCompleteCallback.run(); return; } @@ -816,15 +829,19 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { .map(key -> key + " -> " + categoryToPackage.get(key)).collect( Collectors.joining(", "))); } + + FabricatedOverlay[] fOverlays = null; + if (mNeedsOverlayCreation) { mNeedsOverlayCreation = false; - mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[]{ + fOverlays = new FabricatedOverlay[]{ mSecondaryOverlay, mNeutralOverlay, mDynamicOverlay - }, currentUser, managedProfiles); - } else { - mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser, - managedProfiles); + }; } + + mThemeManager.applyCurrentUserOverlays(categoryToPackage, fOverlays, currentUser, + managedProfiles, onCompleteCallback); + } private Style fetchThemeStyleFromSetting() { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index a54284708be8..139ac7e4f687 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.unfold import com.android.keyguard.KeyguardUnfoldTransition +import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shade.NotificationPanelUnfoldAnimationController import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController @@ -30,6 +31,8 @@ import dagger.BindsInstance import dagger.Module import dagger.Provides import dagger.Subcomponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet import java.util.Optional import javax.inject.Named @@ -73,6 +76,14 @@ class SysUIUnfoldModule { } @Module +interface SysUIUnfoldStartableModule { + @Binds + @IntoMap + @ClassKey(UnfoldInitializationStartable::class) + fun bindsUnfoldInitializationStartable(impl: UnfoldInitializationStartable): CoreStartable +} + +@Module abstract class SysUIUnfoldInternalModule { @Binds @IntoSet diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt new file mode 100644 index 000000000000..75d8a58102ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt @@ -0,0 +1,67 @@ +/* + * 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.unfold + +import com.android.systemui.CoreStartable +import com.android.systemui.Flags +import com.android.systemui.unfold.dagger.UnfoldBg +import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder +import java.util.Optional +import javax.inject.Inject + +class UnfoldInitializationStartable +@Inject +constructor( + private val unfoldComponentOptional: Optional<SysUIUnfoldComponent>, + private val foldStateLoggingProviderOptional: Optional<FoldStateLoggingProvider>, + private val foldStateLoggerOptional: Optional<FoldStateLogger>, + @UnfoldBg + private val unfoldBgTransitionProgressProviderOptional: + Optional<UnfoldTransitionProgressProvider>, + private val unfoldTransitionProgressProviderOptional: + Optional<UnfoldTransitionProgressProvider>, + private val unfoldTransitionProgressForwarder: Optional<UnfoldTransitionProgressForwarder> +) : CoreStartable { + override fun start() { + unfoldComponentOptional.ifPresent { c: SysUIUnfoldComponent -> + c.getFullScreenLightRevealAnimations().forEach { it: FullscreenLightRevealAnimation -> + it.init() + } + c.getUnfoldTransitionWallpaperController().init() + c.getUnfoldHapticsPlayer() + c.getNaturalRotationUnfoldProgressProvider().init() + c.getUnfoldLatencyTracker().init() + } + + foldStateLoggingProviderOptional.ifPresent { obj: FoldStateLoggingProvider -> obj.init() } + foldStateLoggerOptional.ifPresent { obj: FoldStateLogger -> obj.init() } + + val unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> = + if (Flags.unfoldAnimationBackgroundProgress()) { + unfoldBgTransitionProgressProviderOptional + } else { + unfoldTransitionProgressProviderOptional + } + unfoldTransitionProgressProvider.ifPresent { + progressProvider: UnfoldTransitionProgressProvider -> + unfoldTransitionProgressForwarder.ifPresent { + listener: UnfoldTransitionProgressForwarder -> + progressProvider.addCallback(listener) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt index 0fb4b43865aa..38b381ac543e 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt @@ -1,6 +1,7 @@ package com.android.systemui.user.domain.interactor import android.annotation.UserIdInt +import android.content.pm.UserInfo import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Flags.refactorGetCurrentUser import com.android.systemui.dagger.SysUISingleton @@ -16,6 +17,9 @@ class SelectedUserInteractor @Inject constructor(private val repository: UserRep /** Flow providing the ID of the currently selected user. */ val selectedUser = repository.selectedUserInfo.map { it.id }.distinctUntilChanged() + /** Flow providing the [UserInfo] of the currently selected user. */ + val selectedUserInfo = repository.selectedUserInfo + /** * Returns the ID of the currently-selected user. * diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt index c170eb567344..a122311e3b34 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt @@ -27,7 +27,6 @@ import android.content.pm.UserInfo import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.Icon -import android.os.Process import android.os.RemoteException import android.os.UserHandle import android.os.UserManager @@ -50,6 +49,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.process.ProcessWrapper import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.res.R import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -108,6 +108,7 @@ constructor( private val guestUserInteractor: GuestUserInteractor, private val uiEventLogger: UiEventLogger, private val userRestrictionChecker: UserRestrictionChecker, + private val processWrapper: ProcessWrapper ) { /** * Defines interface for classes that can be notified when the state of users on the device is @@ -669,7 +670,7 @@ constructor( // Connect to the new secondary user's service (purely to ensure that a persistent // SystemUI application is created for that user) - if (userId != Process.myUserHandle().identifier) { + if (userId != processWrapper.myUserHandle().identifier && !processWrapper.isSystemUser) { applicationContext.startServiceAsUser( intent, UserHandle.of(userId), diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt index 278ffc6b8412..1af5c46cd14b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -18,6 +18,9 @@ package com.android.systemui.volume.dagger import android.content.Context import android.media.AudioManager +import com.android.settingslib.media.data.repository.SpatializerRepository +import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl +import com.android.settingslib.media.domain.interactor.SpatializerInteractor import com.android.settingslib.volume.data.repository.AudioRepository import com.android.settingslib.volume.data.repository.AudioRepositoryImpl import com.android.settingslib.volume.domain.interactor.AudioModeInteractor @@ -54,5 +57,16 @@ interface AudioModule { @Provides fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor = AudioModeInteractor(repository) + + @Provides + fun provdieSpatializerRepository( + audioManager: AudioManager, + @Background backgroundContext: CoroutineContext, + ): SpatializerRepository = + SpatializerRepositoryImpl(audioManager.spatializer, backgroundContext) + + @Provides + fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor = + SpatializerInteractor(repository) } } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java index e0228d913977..1d9b90ac67af 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -34,7 +34,7 @@ import android.service.quickaccesswallet.QuickAccessWalletClient; import android.service.quickaccesswallet.QuickAccessWalletClientImpl; import android.util.Log; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -236,12 +236,12 @@ public class QuickAccessWalletController { * that too is null, then fall back to {@link WalletActivity}. * * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent. - * @param animationController an {@link ActivityLaunchAnimator.Controller} to provide a + * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a * smooth animation for the activity launch. * @param hasCard whether the service returns any cards. */ public void startQuickAccessUiIntent(ActivityStarter activityStarter, - ActivityLaunchAnimator.Controller animationController, + ActivityTransitionAnimator.Controller animationController, boolean hasCard) { mQuickAccessWalletClient.getWalletPendingIntent(mExecutor, walletPendingIntent -> { @@ -271,7 +271,7 @@ public class QuickAccessWalletController { private void startQuickAccessViaIntent(Intent intent, boolean hasCard, ActivityStarter activityStarter, - ActivityLaunchAnimator.Controller animationController) { + ActivityTransitionAnimator.Controller animationController) { if (hasCard) { activityStarter.startActivity(intent, true /* dismissShade */, animationController, true /* showOverLockscreenWhenLocked */); @@ -285,7 +285,7 @@ public class QuickAccessWalletController { private void startQuickAccessViaPendingIntent(PendingIntent pendingIntent, ActivityStarter activityStarter, - ActivityLaunchAnimator.Controller animationController) { + ActivityTransitionAnimator.Controller animationController) { activityStarter.postStartActivityDismissingKeyguard( pendingIntent, animationController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt index 202d9ce27a34..e157fc508f87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt @@ -91,7 +91,7 @@ class SystemUIApplicationTest : SysuiTestCase() { whenever(sysuiComponent.startables) .thenReturn(mutableMapOf(StartableA::class.java to Provider { startableA })) app.onCreate() - app.startServicesIfNeeded() + app.startSystemUserServicesIfNeeded() assertThat(startableA.started).isTrue() } @@ -105,7 +105,7 @@ class SystemUIApplicationTest : SysuiTestCase() { ) ) app.onCreate() - app.startServicesIfNeeded() + app.startSystemUserServicesIfNeeded() assertThat(startableA.started).isTrue() assertThat(startableB.started).isTrue() } @@ -121,7 +121,7 @@ class SystemUIApplicationTest : SysuiTestCase() { ) ) app.onCreate() - app.startServicesIfNeeded() + app.startSystemUserServicesIfNeeded() assertThat(startableA.started).isTrue() assertThat(startableB.started).isTrue() assertThat(startableC.started).isTrue() @@ -141,7 +141,7 @@ class SystemUIApplicationTest : SysuiTestCase() { ) ) app.onCreate() - app.startServicesIfNeeded() + app.startSystemUserServicesIfNeeded() assertThat(startableA.started).isTrue() assertThat(startableB.started).isTrue() assertThat(startableC.started).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt index 722107c7a1b5..75a49d73cb59 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt @@ -44,37 +44,37 @@ import org.mockito.junit.MockitoJUnit @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper -class ActivityLaunchAnimatorTest : SysuiTestCase() { +class ActivityTransitionAnimatorTest : SysuiTestCase() { private val transitionContainer = LinearLayout(mContext) private val testTransitionAnimator = fakeTransitionAnimator() - @Mock lateinit var callback: ActivityLaunchAnimator.Callback - @Mock lateinit var listener: ActivityLaunchAnimator.Listener - @Spy private val controller = TestLaunchAnimatorController(transitionContainer) + @Mock lateinit var callback: ActivityTransitionAnimator.Callback + @Mock lateinit var listener: ActivityTransitionAnimator.Listener + @Spy private val controller = TestTransitionAnimatorController(transitionContainer) @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback - private lateinit var activityLaunchAnimator: ActivityLaunchAnimator + private lateinit var activityTransitionAnimator: ActivityTransitionAnimator @get:Rule val rule = MockitoJUnit.rule() @Before fun setup() { - activityLaunchAnimator = - ActivityLaunchAnimator( + activityTransitionAnimator = + ActivityTransitionAnimator( testTransitionAnimator, testTransitionAnimator, disableWmTimeout = true ) - activityLaunchAnimator.callback = callback - activityLaunchAnimator.addListener(listener) + activityTransitionAnimator.callback = callback + activityTransitionAnimator.addListener(listener) } @After fun tearDown() { - activityLaunchAnimator.removeListener(listener) + activityTransitionAnimator.removeListener(listener) } private fun startIntentWithAnimation( - animator: ActivityLaunchAnimator = this.activityLaunchAnimator, - controller: ActivityLaunchAnimator.Controller? = this.controller, + animator: ActivityTransitionAnimator = this.activityTransitionAnimator, + controller: ActivityTransitionAnimator.Controller? = this.controller, animate: Boolean = true, intentStarter: (RemoteAnimationAdapter?) -> Int ) { @@ -138,7 +138,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java) var animationAdapter: RemoteAnimationAdapter? = null - startIntentWithAnimation(activityLaunchAnimator) { adapter -> + startIntentWithAnimation(activityTransitionAnimator) { adapter -> animationAdapter = adapter ActivityManager.START_DELIVERED_TO_TOP } @@ -163,50 +163,50 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { @Test fun doesNotStartIfAnimationIsCancelled() { - val runner = activityLaunchAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createRunner(controller) runner.onAnimationCancelled() runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) waitForIdleSync() - verify(controller).onLaunchAnimationCancelled() + verify(controller).onTransitionAnimationCancelled() verify(controller, never()).onTransitionAnimationStart(anyBoolean()) - verify(listener).onLaunchAnimationCancelled() - verify(listener, never()).onLaunchAnimationStart() + verify(listener).onTransitionAnimationCancelled() + verify(listener, never()).onTransitionAnimationStart() assertNull(runner.delegate) } @Test fun cancelsIfNoOpeningWindowIsFound() { - val runner = activityLaunchAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createRunner(controller) runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) waitForIdleSync() - verify(controller).onLaunchAnimationCancelled() + verify(controller).onTransitionAnimationCancelled() verify(controller, never()).onTransitionAnimationStart(anyBoolean()) - verify(listener).onLaunchAnimationCancelled() - verify(listener, never()).onLaunchAnimationStart() + verify(listener).onTransitionAnimationCancelled() + verify(listener, never()).onTransitionAnimationStart() assertNull(runner.delegate) } @Test fun startsAnimationIfWindowIsOpening() { - val runner = activityLaunchAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createRunner(controller) runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback) waitForIdleSync() - verify(listener).onLaunchAnimationStart() + verify(listener).onTransitionAnimationStart() verify(controller).onTransitionAnimationStart(anyBoolean()) } @Test fun creatingControllerFromNormalViewThrows() { assertThrows(IllegalArgumentException::class.java) { - ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext)) + ActivityTransitionAnimator.Controller.fromView(FrameLayout(mContext)) } } @Test fun disposeRunner_delegateDereferenced() { - val runner = activityLaunchAnimator.createRunner(controller) + val runner = activityTransitionAnimator.createRunner(controller) assertNotNull(runner.delegate) runner.dispose() waitForIdleSync() @@ -241,11 +241,11 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { } /** - * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called + * A simple implementation of [ActivityTransitionAnimator.Controller] which throws if it is called * outside of the main thread. */ -private class TestLaunchAnimatorController(override var transitionContainer: ViewGroup) : - ActivityLaunchAnimator.Controller { +private class TestTransitionAnimatorController(override var transitionContainer: ViewGroup) : + ActivityTransitionAnimator.Controller { override fun createAnimatorState() = TransitionAnimator.State( top = 100, @@ -282,7 +282,7 @@ private class TestLaunchAnimatorController(override var transitionContainer: Vie assertOnMainThread() } - override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) { + override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { assertOnMainThread() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt index b28d0c802c18..e30dd35d74c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt @@ -33,8 +33,7 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig -import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig +import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig import com.android.systemui.deviceentry.shared.FaceAuthUiEvent import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository @@ -56,10 +55,9 @@ import com.android.systemui.testKosmos import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -68,37 +66,33 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { - val kosmos = - testKosmos().apply { this.faceWakeUpTriggersConfig = mock<FaceWakeUpTriggersConfig>() } + private val kosmos = testKosmos() + private val testScope: TestScope = kosmos.testScope private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor - private val testScope = kosmos.testScope private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository private val fakeUserRepository = kosmos.fakeUserRepository private val facePropertyRepository = kosmos.facePropertyRepository - private val fakeDeviceEntryFingerprintAuthRepository = - kosmos.fakeDeviceEntryFingerprintAuthRepository + private val fakeDeviceEntryFingerprintAuthInteractor = + kosmos.deviceEntryFingerprintAuthInteractor private val powerInteractor = kosmos.powerInteractor private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor - private val faceWakeUpTriggersConfig = kosmos.faceWakeUpTriggersConfig + private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig private val trustManager = kosmos.trustManager @Before fun setup() { - MockitoAnnotations.initMocks(this) fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser)) - underTest = SystemUIDeviceEntryFaceAuthInteractor( mContext, @@ -110,7 +104,7 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { keyguardTransitionInteractor, FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")), keyguardUpdateMonitor, - fakeDeviceEntryFingerprintAuthRepository, + fakeDeviceEntryFingerprintAuthInteractor, fakeUserRepository, facePropertyRepository, faceWakeUpTriggersConfig, @@ -126,10 +120,9 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { underTest.start() powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID) - whenever( - faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID) - ) - .thenReturn(true) + faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom( + setOf(WakeSleepReason.LID.powerManagerWakeReason) + ) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -168,10 +161,9 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { underTest.start() powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID) - whenever( - faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID) - ) - .thenReturn(true) + faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom( + setOf(WakeSleepReason.LID.powerManagerWakeReason) + ) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -194,10 +186,9 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { underTest.start() powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LIFT) - whenever( - faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LIFT) - ) - .thenReturn(false) + faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom( + setOf(WakeSleepReason.LID.powerManagerWakeReason) + ) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -217,10 +208,9 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { underTest.start() powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID) - whenever( - faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID) - ) - .thenReturn(true) + faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom( + setOf(WakeSleepReason.LID.powerManagerWakeReason) + ) keyguardTransitionRepository.sendTransitionStep( TransitionStep( @@ -440,7 +430,45 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { underTest.start() fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) - fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true) + runCurrent() + + assertThat(faceAuthRepository.isLockedOut.value).isTrue() + } + + @Test + fun faceLockoutStateIsResetWheneverFingerprintIsNotLockedOut() = + testScope.runTest { + underTest.start() + fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE) + fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true) + runCurrent() + + assertThat(faceAuthRepository.isLockedOut.value).isTrue() + facePropertyRepository.setLockoutMode(primaryUserId, LockoutMode.NONE) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false) + runCurrent() + + assertThat(faceAuthRepository.isLockedOut.value).isFalse() + } + + @Test + fun faceLockoutStateIsSetToUsersLockoutStateWheneverFingerprintIsNotLockedOut() = + testScope.runTest { + underTest.start() + fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE) + fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true) + runCurrent() + + assertThat(faceAuthRepository.isLockedOut.value).isTrue() + facePropertyRepository.setLockoutMode(primaryUserId, LockoutMode.TIMED) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false) runCurrent() assertThat(faceAuthRepository.isLockedOut.value).isTrue() 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 24cf16479188..2732047b4eba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -86,7 +86,7 @@ import com.android.keyguard.TestScopeProvider; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.DejankUtils; import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; @@ -186,7 +186,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock ShadeController mShadeController; private NotificationShadeWindowController mNotificationShadeWindowController; private @Mock DreamOverlayStateController mDreamOverlayStateController; - private @Mock ActivityLaunchAnimator mActivityLaunchAnimator; + private @Mock ActivityTransitionAnimator mActivityTransitionAnimator; private @Mock ScrimController mScrimController; private @Mock IActivityTaskManager mActivityTaskManagerService; private @Mock SysuiColorExtractor mColorExtractor; @@ -763,7 +763,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test public void testUpdateIsKeyguardAfterOccludeAnimationIsCancelled() { - mViewMediator.mOccludeAnimationController.onLaunchAnimationCancelled( + mViewMediator.mOccludeAnimationController.onTransitionAnimationCancelled( null /* newKeyguardOccludedState */); // Since the updateIsKeyguard call is delayed during the animation, ensure it's called if @@ -1231,7 +1231,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mWallpaperRepository, () -> mShadeController, () -> mNotificationShadeWindowController, - () -> mActivityLaunchAnimator, + () -> mActivityTransitionAnimator, () -> mScrimController, mActivityTaskManagerService, mFeatureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index b4ae7e3a7ca9..798c7f757b58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -24,7 +24,7 @@ import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription @@ -225,7 +225,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var activityStarter: ActivityStarter - @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller + @Mock private lateinit var animationController: ActivityTransitionAnimator.Controller @Mock private lateinit var expandable: Expandable @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var devicePolicyManager: DevicePolicyManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS deleted file mode 100644 index 142862d7e480..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# Haptics team also works on Ringtones (RingtonePlayer/NotificationPlayer) -file:/services/core/java/com/android/server/vibrator/OWNERS diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index c6cfabc61300..32b6f3854bfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -72,7 +72,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; @@ -110,7 +110,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Mock private DialogLaunchAnimator mDialogLaunchAnimator; @Mock - private ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController; + private ActivityTransitionAnimator.Controller mActivityTransitionAnimatorController; @Mock private NearbyMediaDevicesManager mNearbyMediaDevicesManager; // Mock @@ -143,7 +143,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Mock private KeyguardManager mKeyguardManager; @Mock - private ActivityLaunchAnimator.Controller mController; + private ActivityTransitionAnimator.Controller mController; @Mock private PowerExemptionManager mPowerExemptionManager; @Mock @@ -1122,7 +1122,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { @Test public void launchBluetoothPairing_isKeyguardLocked_dismissDialog() { when(mDialogLaunchAnimator.createActivityLaunchController(mDialogLaunchView)).thenReturn( - mActivityLaunchAnimatorController); + mActivityTransitionAnimatorController); when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); mMediaOutputController.mCallback = this.mCallback; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt index 301d887b7474..d9453d67848f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt @@ -33,6 +33,7 @@ class NearbyMediaDevicesManagerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) manager = NearbyMediaDevicesManager(commandQueue, logger) + manager.start() val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) verify(commandQueue).addCallback(callbackCaptor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index ae47a7bfa63d..33f8f1fd9087 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -38,7 +38,7 @@ import android.view.IWindowManager import android.view.View import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.view.LaunchableFrameLayout import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter @@ -372,7 +372,7 @@ class CustomTileTest : SysuiTestCase() { verify(activityStarter) .startPendingIntentMaybeDismissingKeyguard( - eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>()) + eq(pi), nullable(), nullable<ActivityTransitionAnimator.Controller>()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt index 23466cc20f44..720c25a3f719 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt @@ -26,7 +26,7 @@ import com.android.internal.logging.nano.MetricsProto import com.android.internal.logging.testing.FakeMetricsLogger import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter @@ -127,7 +127,7 @@ class FooterActionsInteractorTest : SysuiTestCase() { .startActivity( intentCaptor.capture(), /* dismissShade= */ eq(true), - nullable() as? ActivityLaunchAnimator.Controller, + nullable() as? ActivityTransitionAnimator.Controller, ) assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SETTINGS) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index 3bf59ca62024..874368bd2bd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -29,7 +29,7 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlInfo @@ -348,7 +348,7 @@ class DeviceControlsTileTest : SysuiTestCase() { verify(activityStarter).startActivity( intentCaptor.capture(), eq(true) /* dismissShade */, - nullable(ActivityLaunchAnimator.Controller::class.java), + nullable(ActivityTransitionAnimator.Controller::class.java), eq(true) /* showOverLockscreenWhenLocked */) assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME) } @@ -379,7 +379,7 @@ class DeviceControlsTileTest : SysuiTestCase() { verify(activityStarter).startActivity( intentCaptor.capture(), anyBoolean() /* dismissShade */, - nullable(ActivityLaunchAnimator.Controller::class.java), + nullable(ActivityTransitionAnimator.Controller::class.java), eq(false) /* showOverLockscreenWhenLocked */) assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 9517f823cf10..1dc5f7dbf6fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -103,8 +103,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ) testableLooper = TestableLooper.get(this) - communalRepository.setIsCommunalEnabled(true) - whenever(keyguardTransitionInteractor.isFinishedInStateWhere(any())) .thenReturn(bouncerShowingFlow) whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(shadeShowingFlow) @@ -125,36 +123,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test - fun isEnabled_communalEnabled_returnsTrue() { - communalRepository.setIsCommunalEnabled(true) - - assertThat(underTest.isEnabled()).isTrue() - } - - @Test - fun isEnabled_communalDisabled_returnsFalse() { - communalRepository.setIsCommunalEnabled(false) - - assertThat(underTest.isEnabled()).isFalse() - } - - @Test - fun initView_notEnabled_throwsException() { - communalRepository.setIsCommunalEnabled(false) - - underTest = - GlanceableHubContainerController( - communalInteractor, - communalViewModel, - keyguardTransitionInteractor, - shadeInteractor, - powerManager, - ) - - assertThrows(RuntimeException::class.java) { underTest.initView(context) } - } - - @Test fun initView_calledTwice_throwsException() { underTest = GlanceableHubContainerController( 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 22b05be507c6..248ed249c213 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -488,7 +488,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { return } - whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(true) + whenever(mGlanceableHubContainerController.communalAvailable()) + .thenReturn(MutableStateFlow(true)) val mockCommunalView = mock(View::class.java) whenever(mGlanceableHubContainerController.initView(any<Context>())) @@ -513,7 +514,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { return } - whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false) whenever(mGlanceableHubContainerController.communalAvailable()) .thenReturn(MutableStateFlow(false)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt index f58ff0adb2b2..3315e68ab9af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt @@ -97,7 +97,7 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() { @Test fun testHunIsRemovedAndCallbackIsInvokedWhenAnimationIsCancelled() { flagNotificationAsHun() - controller.onLaunchAnimationCancelled() + controller.onTransitionAnimationCancelled() assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification)) assertFalse(notification.entry.isExpandAnimationRunning) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index a1daff14eea6..b4dadaf83e94 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -102,6 +102,7 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test + @DisableFlags(FooterViewRefactor.FLAG_NAME) fun testSetNotificationStats_clearableAlerting() { whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) @@ -109,6 +110,7 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test + @DisableFlags(FooterViewRefactor.FLAG_NAME) fun testSetNotificationStats_clearableSilent() { whenever(section.bucket).thenReturn(BUCKET_SILENT) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt deleted file mode 100644 index 9b641f014c01..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt +++ /dev/null @@ -1,153 +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.statusbar.notification.domain.interactor - -import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule -import com.android.systemui.SysuiTestCase -import com.android.systemui.collectLastValue -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.statusbar.notification.collection.render.NotifStats -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs -import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component -import org.junit.Test - -@SmallTest -class ActiveNotificationsInteractorTest : SysuiTestCase() { - - @Component(modules = [SysUITestModule::class]) - @SysUISingleton - interface TestComponent : SysUITestComponent<ActiveNotificationsInteractor> { - val activeNotificationListRepository: ActiveNotificationListRepository - - @Component.Factory - interface Factory { - fun create(@BindsInstance test: SysuiTestCase): TestComponent - } - } - - private val testComponent: TestComponent = - DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this) - - @Test - fun testAllNotificationsCount() = - testComponent.runTest { - val count by collectLastValue(underTest.allNotificationsCount) - - activeNotificationListRepository.setActiveNotifs(5) - runCurrent() - - assertThat(count).isEqualTo(5) - assertThat(underTest.allNotificationsCountValue).isEqualTo(5) - } - - @Test - fun testAreAnyNotificationsPresent_isTrue() = - testComponent.runTest { - val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) - - activeNotificationListRepository.setActiveNotifs(2) - runCurrent() - - assertThat(areAnyNotificationsPresent).isTrue() - assertThat(underTest.areAnyNotificationsPresentValue).isTrue() - } - - @Test - fun testAreAnyNotificationsPresent_isFalse() = - testComponent.runTest { - val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent) - - activeNotificationListRepository.setActiveNotifs(0) - runCurrent() - - assertThat(areAnyNotificationsPresent).isFalse() - assertThat(underTest.areAnyNotificationsPresentValue).isFalse() - } - - @Test - fun testActiveNotificationRanks_sizeMatches() { - testComponent.runTest { - val activeNotificationRanks by collectLastValue(underTest.activeNotificationRanks) - - activeNotificationListRepository.setActiveNotifs(5) - runCurrent() - - assertThat(activeNotificationRanks!!.size).isEqualTo(5) - } - } - - @Test - fun testHasClearableNotifications_whenHasClearableAlertingNotifs() = - testComponent.runTest { - val hasClearable by collectLastValue(underTest.hasClearableNotifications) - - activeNotificationListRepository.notifStats.value = - NotifStats( - numActiveNotifs = 2, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = true, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = false, - ) - runCurrent() - - assertThat(hasClearable).isTrue() - } - - @Test - fun testHasClearableNotifications_whenHasClearableSilentNotifs() = - testComponent.runTest { - val hasClearable by collectLastValue(underTest.hasClearableNotifications) - - activeNotificationListRepository.notifStats.value = - NotifStats( - numActiveNotifs = 2, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = false, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = true, - ) - runCurrent() - - assertThat(hasClearable).isTrue() - } - - @Test - fun testHasClearableNotifications_whenHasNoClearableNotifs() = - testComponent.runTest { - val hasClearable by collectLastValue(underTest.hasClearableNotifications) - - activeNotificationListRepository.notifStats.value = - NotifStats( - numActiveNotifs = 2, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = false, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = false, - ) - runCurrent() - - assertThat(hasClearable).isFalse() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index b922ab39912b..3811f04a365d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -472,6 +472,23 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test + public void publicMode_nullChannel_allowed() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); + // GIVEN an 'unfiltered-keyguard-showing' state + setupUnfilteredState(mEntry); + + // WHEN the notification's user is in public mode and settings are configured to disallow + // notifications in public mode + when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); + mEntry.setRanking(new RankingBuilder() + .setKey(mEntry.getKey()) + .setVisibilityOverride(VISIBILITY_SECRET).build()); + + // THEN allow the entry + assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test public void publicMode_notifDisallowed() { mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 04f3216c8e73..f326ceaf1950 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -614,7 +614,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { selected[0] = selectedRows; }); - mStackScroller.clearNotifications(ROWS_ALL, true); + mStackScroller.clearNotifications(ROWS_ALL, + /* closeShade = */ true, + /* hideSilentSection = */ true); assertEquals(1, numCalls[0]); assertEquals(ROWS_ALL, selected[0]); } @@ -628,7 +630,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { selected[0] = selectedRows; }); - mStackScroller.clearNotifications(NotificationStackScrollLayout.ROWS_GENTLE, false); + mStackScroller.clearNotifications(NotificationStackScrollLayout.ROWS_GENTLE, + /* closeShade = */ false, + /* hideSilentSection = */ true); assertEquals(1, numCalls[0]); assertEquals(ROWS_GENTLE, selected[0]); } @@ -640,7 +644,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { doReturn(true).when(mStackScroller).isVisible(row); mStackScroller.addContainerView(row); - mStackScroller.clearNotifications(ROWS_ALL, false); + mStackScroller.clearNotifications(ROWS_ALL, + /* closeShade = */ false, + /* hideSilentSection = */ false); assertClearAllInProgress(true); verify(mNotificationRoundnessManager).setClearAllInProgress(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt index 5a5703512a39..dfbe1acd1755 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt @@ -18,8 +18,10 @@ package com.android.systemui.statusbar.notification.stack import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent @@ -28,6 +30,7 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIM import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -38,9 +41,12 @@ import org.mockito.Mockito.eq import org.mockito.Mockito.verify private const val VIEW_HEIGHT = 100 +private const val FULL_SHADE_APPEAR_TRANSLATION = 300 +private const val HEADS_UP_ABOVE_SCREEN = 80 @SmallTest @RunWith(AndroidTestingRunner::class) +@RunWithLooper class StackStateAnimatorTest : SysuiTestCase() { private lateinit var stackStateAnimator: StackStateAnimator @@ -51,9 +57,15 @@ class StackStateAnimatorTest : SysuiTestCase() { private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor() @Before fun setUp() { + overrideResource( + R.dimen.go_to_full_shade_appearing_translation, + FULL_SHADE_APPEAR_TRANSLATION + ) + overrideResource(R.dimen.heads_up_appear_y_above_screen, HEADS_UP_ABOVE_SCREEN) + whenever(stackScroller.context).thenReturn(context) whenever(view.viewState).thenReturn(viewState) - stackStateAnimator = StackStateAnimator(stackScroller) + stackStateAnimator = StackStateAnimator(mContext, stackScroller) } @Test @@ -122,4 +134,22 @@ class StackStateAnimatorTest : SysuiTestCase() { verify(view, description("should be called at the end of the animation")) .removeFromTransientContainer() } + + @Test + fun initView_updatesResources() { + // Given: the resource values are initialized in the SSA + assertThat(stackStateAnimator.mGoToFullShadeAppearingTranslation) + .isEqualTo(FULL_SHADE_APPEAR_TRANSLATION) + assertThat(stackStateAnimator.mHeadsUpAppearStartAboveScreen) + .isEqualTo(HEADS_UP_ABOVE_SCREEN) + + // When: initView is called after the resources have changed + overrideResource(R.dimen.go_to_full_shade_appearing_translation, 200) + overrideResource(R.dimen.heads_up_appear_y_above_screen, 100) + stackStateAnimator.initView(mContext) + + // Then: the resource values are updated + assertThat(stackStateAnimator.mGoToFullShadeAppearingTranslation).isEqualTo(200) + assertThat(stackStateAnimator.mHeadsUpAppearStartAboveScreen).isEqualTo(100) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index 3a7659dd00e4..0a18eb66c4df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -28,11 +28,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.shared.model.WakefulnessState @@ -70,7 +66,6 @@ class NotificationListViewModelTest : SysuiTestCase() { private val activeNotificationListRepository = kosmos.activeNotificationListRepository private val fakeConfigurationController = kosmos.fakeConfigurationController private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository - private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val fakePowerRepository = kosmos.fakePowerRepository private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository private val fakeShadeRepository = kosmos.fakeShadeRepository @@ -90,11 +85,7 @@ class NotificationListViewModelTest : SysuiTestCase() { val important by collectLastValue(underTest.isImportantForAccessibility) // WHEN on lockscreen - fakeKeyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - testScope, - ) + fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) // AND has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) testScope.runCurrent() @@ -109,11 +100,7 @@ class NotificationListViewModelTest : SysuiTestCase() { val important by collectLastValue(underTest.isImportantForAccessibility) // WHEN on lockscreen - fakeKeyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - testScope, - ) + fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) // AND has notifs activeNotificationListRepository.setActiveNotifs(count = 2) runCurrent() @@ -128,11 +115,7 @@ class NotificationListViewModelTest : SysuiTestCase() { val important by collectLastValue(underTest.isImportantForAccessibility) // WHEN not on lockscreen - fakeKeyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - testScope, - ) + fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) // AND has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) runCurrent() @@ -150,7 +133,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 0) runCurrent() - // THEN should show + // THEN empty shade is visible assertThat(shouldShow).isTrue() } @@ -163,7 +146,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) runCurrent() - // THEN should not show + // THEN empty shade is not visible assertThat(shouldShow).isFalse() } @@ -178,7 +161,7 @@ class NotificationListViewModelTest : SysuiTestCase() { fakeShadeRepository.legacyQsFullscreen.value = true runCurrent() - // THEN should not show + // THEN empty shade is not visible assertThat(shouldShow).isFalse() } @@ -196,48 +179,54 @@ class NotificationListViewModelTest : SysuiTestCase() { fakeConfigurationController.notifyConfigurationChanged() runCurrent() - // THEN should show + // THEN empty shade is visible + assertThat(shouldShow).isTrue() + } + + @Test + fun testShouldShowEmptyShadeView_trueWhenLockedShade() = + testScope.runTest { + val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + + // WHEN has no notifs + activeNotificationListRepository.setActiveNotifs(count = 0) + // AND shade is open + fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) + runCurrent() + + // THEN empty shade is visible assertThat(shouldShow).isTrue() } @Test - fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() = + fun testShouldShowEmptyShadeView_falseWhenKeyguard() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) - // AND transitioning to AOD - fakeKeyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 0f, - ) - ) + // AND shade is open + fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) runCurrent() - // THEN should not show + // THEN empty shade is not visible assertThat(shouldShow).isFalse() } @Test - fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() = + fun testShouldShowEmptyShadeView_falseWhenStartingToSleep() = testScope.runTest { val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) - // AND is on bouncer - fakeKeyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.PRIMARY_BOUNCER, - testScope, - ) + // AND shade is open + fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) + // AND device is starting to go to sleep + fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP) runCurrent() - // THEN should not show + // THEN empty shade is not visible assertThat(shouldShow).isFalse() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index b048949e0e76..9c60a2b6fd12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -83,7 +83,7 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.back.domain.interactor.BackActionInteractor; import com.android.systemui.biometrics.AuthRippleController; @@ -307,7 +307,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private StartingSurface mStartingSurface; @Mock private OperatorNameViewController mOperatorNameViewController; @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; - @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; + @Mock private ActivityTransitionAnimator mActivityTransitionAnimator; @Mock private DeviceStateManager mDeviceStateManager; @Mock private WiredChargingRippleController mWiredChargingRippleController; @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; @@ -543,7 +543,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { new MessageRouterImpl(mMainExecutor), mWallpaperManager, Optional.of(mStartingSurface), - mActivityLaunchAnimator, + mActivityTransitionAnimator, mDeviceStateManager, mWiredChargingRippleController, mDreamManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 597e2e34f1d4..41514ce3e72c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -63,7 +63,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.flags.FakeFeatureFlags; @@ -159,7 +159,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private StatusBarNotificationActivityStarter mNotificationActivityStarter; @Mock - private ActivityLaunchAnimator mActivityLaunchAnimator; + private ActivityTransitionAnimator mActivityTransitionAnimator; @Mock private InteractionJankMonitor mJankMonitor; private FakePowerRepository mPowerRepository; @@ -255,7 +255,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(NotificationPresenter.class), mock(ShadeViewController.class), mock(NotificationShadeWindowController.class), - mActivityLaunchAnimator, + mActivityTransitionAnimator, new ShadeAnimationInteractorLegacyImpl( new ShadeAnimationRepository(), new FakeShadeRepository()), notificationAnimationProvider, @@ -306,7 +306,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mShadeController, atLeastOnce()).collapseShade(); - verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(), + verify(mActivityTransitionAnimator).startPendingIntentWithAnimation(any(), eq(false) /* animate */, any(), any()); verify(mAssistManager).hideAssist(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index 83439f0fc60d..8f4cbafecbcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -104,9 +104,9 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); - mManager = new ThemeOverlayApplier(mOverlayManager, - MoreExecutors.directExecutor(), - LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) { + mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(), + LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager, + MoreExecutors.directExecutor()) { @Override protected OverlayManagerTransaction.Builder getTransactionBuilder() { return mTransactionBuilder; @@ -179,7 +179,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_allEnabledExclusively() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, null); verify(mOverlayManager).commit(any()); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { @@ -191,7 +191,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() { mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, null); for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) { if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) { @@ -208,7 +208,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { public void allCategoriesSpecified_enabledForAllUserHandles() { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), - userHandles); + userHandles, null); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), @@ -225,7 +225,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), - userHandles); + userHandles, null); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true), @@ -239,7 +239,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { mock(FabricatedOverlay.class) }; mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, - TEST_USER.getIdentifier(), TEST_USER_HANDLES); + TEST_USER.getIdentifier(), TEST_USER_HANDLES, null); for (FabricatedOverlay overlay : pendingCreation) { verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay)); @@ -253,7 +253,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID); mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, null); for (OverlayIdentifier overlayPackage : categoryToPackage.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), @@ -270,7 +270,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void zeroCategoriesSpecified_allDisabled() { mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, null); for (String category : THEME_CATEGORIES) { verify(mTransactionBuilder).setEnabled( @@ -285,7 +285,7 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category")); mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), - TEST_USER_HANDLES); + TEST_USER_HANDLES, null); verify(mTransactionBuilder, never()).setEnabled( eq(new OverlayIdentifier("com.example.blah.category")), eq(false), diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index b58a41c89a4e..c02583ae6f0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.app.UiModeManager; import android.app.WallpaperColors; import android.app.WallpaperManager; @@ -129,6 +130,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private UiModeManager mUiModeManager; + @Mock + private ActivityManager mActivityManager; @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver; @Captor @@ -164,7 +167,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle, - mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) { + mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) { @VisibleForTesting protected boolean isNightMode() { return false; @@ -224,7 +227,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -249,7 +252,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastReceiver.getValue().onReceive(null, intent); mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -263,7 +266,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any()); // Should not change theme after changing wallpapers, if intent doesn't have // WallpaperManager.EXTRA_FROM_FOREGROUND_APP set to true. @@ -272,7 +275,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK), null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -294,7 +297,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -333,7 +336,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { .isFalse(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -367,8 +370,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { assertThat(updatedSetting.getValue().contains( "android.theme.customization.color_both\":\"0")).isTrue(); - verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -423,7 +425,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { assertThat(updatedSetting.getValue().contains( "android.theme.customization.color_both\":\"1")).isTrue(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -492,7 +494,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { "android.theme.customization.color_both\":\"1")).isTrue(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -523,7 +525,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index")) .isFalse(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -554,7 +556,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index")) .isFalse(); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -587,7 +589,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { anyInt()); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -620,7 +622,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture()); // Apply overlay by existing theme from secure setting - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -653,7 +655,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -675,7 +677,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays = ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any()); // Assert that we received secondary user colors assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -689,7 +691,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_PROFILE_ADDED) .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -700,7 +702,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { new Intent(Intent.ACTION_PROFILE_ADDED) .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -711,7 +713,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { new Intent(Intent.ACTION_PROFILE_ADDED) .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -723,7 +725,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { (new Intent(Intent.ACTION_PROFILE_ADDED)) .putExtra(Intent.EXTRA_USER, PRIVATE_USER_HANDLE)); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @@ -737,7 +739,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); // Regression test: null events should not reset the internal state and allow colors to be // applied again. @@ -747,11 +749,11 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastReceiver.getValue().onReceive(null, intent); mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), - any()); + any(), any()); mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.GREEN), null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), - any()); + any(), any()); } @Test @@ -770,7 +772,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle, - mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) { + mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) { @VisibleForTesting protected boolean isNightMode() { return false; @@ -791,7 +793,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture()); // Colors were applied during controller initialization. - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); clearInvocations(mThemeOverlayApplier); } @@ -810,7 +812,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier, mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController, mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle, - mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) { + mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) { @VisibleForTesting protected boolean isNightMode() { return false; @@ -831,7 +833,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture()); // Colors were applied during controller initialization. - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); clearInvocations(mThemeOverlayApplier); WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), @@ -853,12 +855,12 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { // Defers event because we already have initial colors. verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); // Then event happens after setup phase is over. when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); mDeviceProvisionedListener.getValue().onUserSetupChanged(); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -881,11 +883,11 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); mWakefulnessLifecycle.dispatchFinishedGoingToSleep(); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -907,10 +909,10 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep(); - verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); + verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); } @Test @@ -930,7 +932,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -949,19 +951,19 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { mColorsListener.getValue().onColorsChanged(startingColors, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); clearInvocations(mThemeOverlayApplier); // Set to the same colors. mColorsListener.getValue().onColorsChanged(sameColors, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM); verify(mThemeOverlayApplier, never()) - .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any()); // Verify that no change resulted. mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep(); verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), - any()); + any(), any()); } @Test @@ -975,7 +977,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(FabricatedOverlay[].class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any()); + .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any(), any()); FabricatedOverlay[] overlays = themeOverlays.getValue(); FabricatedOverlay accents = overlays[0]; diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index b6a033a7c5f6..1b4385148f88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.process.processWrapper import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -1147,6 +1148,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { uiEventLogger = uiEventLogger, featureFlags = kosmos.fakeFeatureFlagsClassic, userRestrictionChecker = mock(), + processWrapper = kosmos.processWrapper, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index 21d4549c904d..661837bdb1e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.process.ProcessWrapperFake import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -264,6 +265,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { guestUserInteractor = guestUserInteractor, uiEventLogger = uiEventLogger, userRestrictionChecker = mock(), + processWrapper = ProcessWrapperFake() ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index d0804be81072..5661e202d134 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.process.ProcessWrapperFake import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -176,6 +177,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { guestUserInteractor = guestUserInteractor, uiEventLogger = uiEventLogger, userRestrictionChecker = mock(), + processWrapper = ProcessWrapperFake() ), guestUserInteractor = guestUserInteractor, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java index 82631744d12d..fccb936a3bc8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java @@ -37,10 +37,10 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -68,7 +68,7 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { @Mock private ActivityStarter mActivityStarter; @Mock - private ActivityLaunchAnimator.Controller mAnimationController; + private ActivityTransitionAnimator.Controller mAnimationController; @Captor private ArgumentCaptor<GetWalletCardsRequest> mRequestCaptor; @Captor @@ -219,7 +219,7 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { public void getQuickAccessUiIntent_hasCards_noPendingIntent_startsWalletActivity() { mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, true); verify(mActivityStarter).startActivity(mIntentCaptor.capture(), eq(true), - any(ActivityLaunchAnimator.Controller.class), eq(true)); + any(ActivityTransitionAnimator.Controller.class), eq(true)); Intent intent = mIntentCaptor.getValue(); assertEquals(intent.getAction(), Intent.ACTION_VIEW); assertEquals( @@ -231,7 +231,7 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { public void getQuickAccessUiIntent_noCards_noPendingIntent_startsWalletActivity() { mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, false); verify(mActivityStarter).postStartActivityDismissingKeyguard(mIntentCaptor.capture(), eq(0), - any(ActivityLaunchAnimator.Controller.class)); + any(ActivityTransitionAnimator.Controller.class)); Intent intent = mIntentCaptor.getValue(); assertEquals(intent.getAction(), Intent.ACTION_VIEW); assertEquals( @@ -254,7 +254,7 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { }).when(mQuickAccessWalletClient).getWalletPendingIntent(any(), any()); mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, true); verify(mActivityStarter).postStartActivityDismissingKeyguard(mPendingIntentCaptor.capture(), - any(ActivityLaunchAnimator.Controller.class)); + any(ActivityTransitionAnimator.Controller.class)); PendingIntent pendingIntent = mPendingIntentCaptor.getValue(); Intent intent = pendingIntent.getIntent(); assertEquals(intent.getAction(), Intent.ACTION_VIEW); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index d45a9a944da2..8d933dcdb9d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -176,9 +176,11 @@ import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; import com.android.wm.shell.bubbles.BubbleEducationController; import com.android.wm.shell.bubbles.BubbleEntry; +import com.android.wm.shell.bubbles.BubbleExpandedViewManager; import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubbleStackView; +import com.android.wm.shell.bubbles.BubbleTaskView; import com.android.wm.shell.bubbles.BubbleViewInfoTask; import com.android.wm.shell.bubbles.BubbleViewProvider; import com.android.wm.shell.bubbles.Bubbles; @@ -195,6 +197,7 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.taskview.TaskView; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.Transitions; @@ -214,6 +217,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Optional; +import java.util.concurrent.Executor; import kotlinx.coroutines.test.TestScope; @@ -1416,7 +1420,9 @@ public class BubblesTest extends SysuiTestCase { .thenReturn(userContext); BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context, - mBubbleController, + BubbleExpandedViewManager.fromBubbleController(mBubbleController), + () -> new BubbleTaskView(mock(TaskView.class), mock(Executor.class)), + mPositioner, mBubbleController.getStackView(), new BubbleIconFactory(mContext, mContext.getResources().getDimensionPixelSize(com.android.wm.shell.R.dimen.bubble_size), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt index 128f58bf9751..66c9afba0cd6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt @@ -18,4 +18,4 @@ package com.android.systemui.animation import com.android.systemui.kosmos.Kosmos -val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() } +val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt new file mode 100644 index 000000000000..5485f79629e8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt @@ -0,0 +1,35 @@ +/* + * 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.communal.data.repository + +import android.app.admin.devicePolicyManager +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.util.settings.fakeSettings + +val Kosmos.communalSettingsRepository: CommunalSettingsRepository by + Kosmos.Fixture { + CommunalSettingsRepositoryImpl( + bgDispatcher = testDispatcher, + featureFlagsClassic = featureFlagsClassic, + secureSettings = fakeSettings, + broadcastDispatcher = broadcastDispatcher, + devicePolicyManager = devicePolicyManager, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index cccd90832326..ae7d87783b7c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.flow.stateIn @OptIn(ExperimentalCoroutinesApi::class) class FakeCommunalRepository( applicationScope: CoroutineScope, - override var isCommunalEnabled: Boolean = true, override val desiredScene: MutableStateFlow<CommunalSceneKey> = MutableStateFlow(CommunalSceneKey.DEFAULT), ) : CommunalRepository { @@ -40,21 +39,10 @@ class FakeCommunalRepository( _transitionState.value = transitionState } - fun setIsCommunalEnabled(value: Boolean) { - isCommunalEnabled = value - } - private val _isCommunalHubShowing: MutableStateFlow<Boolean> = MutableStateFlow(false) override val isCommunalHubShowing: Flow<Boolean> = _isCommunalHubShowing fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) { _isCommunalHubShowing.value = isCommunalHubShowing } - - private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false) - override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState - - fun setCommunalEnabledState(enabled: Boolean) { - _communalEnabledState.value = enabled - } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index c47f020a3b83..f7e9a117aa77 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -27,7 +27,6 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.smartspace.data.repository.smartspaceRepository -import com.android.systemui.user.data.repository.userRepository import com.android.systemui.util.mockito.mock val Kosmos.communalInteractor by Fixture { @@ -38,12 +37,12 @@ val Kosmos.communalInteractor by Fixture { mediaRepository = communalMediaRepository, communalPrefsRepository = communalPrefsRepository, smartspaceRepository = smartspaceRepository, - userRepository = userRepository, appWidgetHost = mock(), keyguardInteractor = keyguardInteractor, editWidgetsActivityStarter = editWidgetsActivityStarter, logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), + communalSettingsInteractor = communalSettingsInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt new file mode 100644 index 000000000000..b4773f69f1c5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt @@ -0,0 +1,33 @@ +/* + * 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.communal.domain.interactor + +import com.android.systemui.communal.data.repository.communalSettingsRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.user.domain.interactor.selectedUserInteractor +import com.android.systemui.util.mockito.mock + +val Kosmos.communalSettingsInteractor by Fixture { + CommunalSettingsInteractor( + bgScope = applicationCoroutineScope, + repository = communalSettingsRepository, + userInteractor = selectedUserInteractor, + tableLogBuffer = mock(), + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt index 9776b436555d..00fdceda01d1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt @@ -31,6 +31,7 @@ val Kosmos.communalTutorialInteractor by keyguardInteractor = keyguardInteractor, communalRepository = communalRepository, communalInteractor = communalInteractor, + communalSettingsInteractor = communalSettingsInteractor, tableLogBuffer = mock(), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt index 21cff0dbde2d..3b3e23e524c1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt @@ -18,5 +18,7 @@ package com.android.systemui.deviceentry.data.repository import com.android.systemui.kosmos.Kosmos +var Kosmos.fakeFaceWakeUpTriggersConfig by Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() } + var Kosmos.faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig by - Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() } + Kosmos.Fixture { fakeFaceWakeUpTriggersConfig } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt index 5575b05b3874..a8fc27a7da4e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt @@ -27,7 +27,6 @@ import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteract import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository -import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope @@ -51,7 +50,7 @@ val Kosmos.deviceEntryFaceAuthInteractor by keyguardTransitionInteractor = keyguardTransitionInteractor, faceAuthenticationLogger = faceAuthLogger, keyguardUpdateMonitor = keyguardUpdateMonitor, - deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository, + deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, userRepository = userRepository, facePropertyRepository = facePropertyRepository, faceWakeUpTriggersConfig = faceWakeUpTriggersConfig, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt index 9841778f835b..dee3644e95bd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt @@ -16,11 +16,17 @@ package com.android.systemui.process +import android.os.UserHandle + class ProcessWrapperFake : ProcessWrapper() { var systemUser: Boolean = false + var userHandle: UserHandle = UserHandle.getUserHandleForUid(0) + override fun isSystemUser(): Boolean { return systemUser } + + override fun myUserHandle() = userHandle } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderControllerKosmos.kt new file mode 100644 index 000000000000..3bbac3236885 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderControllerKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.statusbar.notification.collection.render + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.silentHeaderController by Kosmos.Fixture { mock<SectionHeaderController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt index 489598c4dba9..2de26f13ad73 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt @@ -22,10 +22,11 @@ import com.android.systemui.common.ui.configurationState import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.statusbar.notification.collection.render.silentHeaderController import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder import com.android.systemui.statusbar.notification.notificationActivityStarter -import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker +import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel import com.android.systemui.statusbar.phone.notificationIconAreaController import java.util.Optional @@ -42,5 +43,6 @@ val Kosmos.notificationListViewBinder by Fixture { hiderTracker = displaySwitchNotificationsHiderTracker, nicBinder = notificationIconContainerShelfViewBinder, notificationActivityStarter = { notificationActivityStarter }, + silentHeaderController = silentHeaderController, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt index 37b2b765c9bd..25e3eac0b4b7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt @@ -17,11 +17,9 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor @@ -40,7 +38,6 @@ val Kosmos.notificationListViewModel by Fixture { Optional.of(notificationListLoggerViewModel), activeNotificationsInteractor, keyguardInteractor, - keyguardTransitionInteractor, powerInteractor, remoteInputInteractor, seenNotificationsInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt index 8042b5c7bf3e..c83710acb7de 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt @@ -23,7 +23,7 @@ import android.service.dream.dreamManagerInterface import com.android.internal.logging.metricsLogger import com.android.internal.widget.lockPatternUtils import com.android.systemui.activityIntentHelper -import com.android.systemui.animation.activityLaunchAnimator +import com.android.systemui.animation.activityTransitionAnimator import com.android.systemui.assist.assistManager import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.kosmos.Kosmos @@ -78,7 +78,7 @@ val Kosmos.statusBarNotificationActivityStarter by notificationPresenter, shadeViewController, notificationShadeWindowController, - activityLaunchAnimator, + activityTransitionAnimator, shadeAnimationInteractor, notificationLaunchAnimatorControllerProvider, launchFullScreenIntentProvider, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt index 4e2dc7af8cb4..1504df4ef6d0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt @@ -28,6 +28,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.plugins.activityStarter +import com.android.systemui.process.processWrapper import com.android.systemui.telephony.domain.interactor.telephonyInteractor import com.android.systemui.user.data.repository.userRepository import com.android.systemui.utils.userRestrictionChecker @@ -53,5 +54,6 @@ val Kosmos.userSwitcherInteractor by guestUserInteractor = guestUserInteractor, uiEventLogger = uiEventLogger, userRestrictionChecker = userRestrictionChecker, + processWrapper = processWrapper, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt new file mode 100644 index 000000000000..bcb584858e32 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.util.settings + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings() } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index a11cf8ca1345..26c1bc904a1a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -928,17 +928,11 @@ public class AccessibilityWindowManager { * Dumps all {@link AccessibilityWindowInfo}s here. */ void dumpLocked(FileDescriptor fd, final PrintWriter pw, String[] args) { - pw.append("Global Info [ "); - pw.println("Top focused display Id = " + mTopFocusedDisplayId); - pw.println(" Active Window Id = " + mActiveWindowId); - pw.println(" Top Focused Window Id = " + mTopFocusedWindowId); - pw.println(" Accessibility Focused Window Id = " + mAccessibilityFocusedWindowId - + " ]"); if (mIsProxy) { pw.println("Proxy accessibility focused window = " + mProxyDisplayAccessibilityFocusedWindow); + pw.println(); } - pw.println(); if (mWindows != null) { final int windowCount = mWindows.size(); for (int j = 0; j < windowCount; j++) { @@ -2201,6 +2195,13 @@ public class AccessibilityWindowManager { * Dumps all {@link AccessibilityWindowInfo}s here. */ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + pw.append("Global Info [ "); + pw.println("Top focused display Id = " + mTopFocusedDisplayId); + pw.println(" Active Window Id = " + mActiveWindowId); + pw.println(" Top Focused Window Id = " + mTopFocusedWindowId); + pw.println(" Accessibility Focused Window Id = " + mAccessibilityFocusedWindowId + + " ]"); + pw.println(); final int count = mDisplayWindowsObservers.size(); for (int i = 0; i < count; i++) { final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index fd8ab9683831..e1291e5f75ec 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -451,7 +451,7 @@ final class AutofillManagerServiceImpl final Session.SaveResult saveResult = session.showSaveLocked(); - session.logContextCommitted(saveResult.getNoSaveUiReason(), commitReason); + session.logContextCommittedLocked(saveResult.getNoSaveUiReason(), commitReason); if (saveResult.isLogSaveShown()) { session.logSaveUiShown(); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 2ff23ee17bfd..b89e0d8c72df 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -3086,6 +3086,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * when necessary. */ public void logContextCommitted() { + if (sVerbose) { + Slog.v(TAG, "logContextCommitted (" + id + "): commit_reason:" + COMMIT_REASON_UNKNOWN + + " no_save_reason:" + Event.NO_SAVE_UI_REASON_NONE); + } mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this, Event.NO_SAVE_UI_REASON_NONE, COMMIT_REASON_UNKNOWN)); @@ -3094,16 +3098,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} - * when necessary. + * when necessary. Note that it could be called before save UI is shown and the session is + * committed. * * @param saveDialogNotShowReason The reason why a save dialog was not shown. * @param commitReason The reason why context is committed. */ - public void logContextCommitted(@NoSaveReason int saveDialogNotShowReason, + + @GuardedBy("mLock") + public void logContextCommittedLocked(@NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { + if (sVerbose) { + Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason + + " no_save_reason:" + saveDialogNotShowReason); + } mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this, saveDialogNotShowReason, commitReason)); - logAllEvents(commitReason); + + mSessionCommittedEventLogger.maybeSetCommitReason(commitReason); + mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); + mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE); } private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason, @@ -3159,6 +3173,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Nullable ArrayList<FieldClassification> detectedFieldClassifications, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { + if (sVerbose) { + Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason + + " no_save_reason:" + saveDialogNotShowReason); + } final FillResponse lastResponse = getLastResponseLocked("logContextCommited(%s)"); if (lastResponse == null) return; @@ -3335,7 +3353,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, mComponentName, mCompatMode, saveDialogNotShowReason); - logAllEvents(commitReason); + mSessionCommittedEventLogger.maybeSetCommitReason(commitReason); + mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); + mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason); } /** @@ -3776,11 +3796,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - if (sDebug) { - Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for " - + id + "!"); - } - final IAutoFillManagerClient client = getClient(); mPendingSaveUi = new PendingUi(new Binder(), id, client); @@ -3812,6 +3827,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } mSessionFlags.mShowingSaveUi = true; + if (sDebug) { + Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for " + + id + "!"); + } return new SaveResult(/* logSaveShown= */ true, /* removeSession= */ false, Event.NO_SAVE_UI_REASON_NONE); } @@ -6386,6 +6405,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void logAllEvents(@AutofillCommitReason int val) { + if (sVerbose) { + Slog.v(TAG, "logAllEvents(" + id + "): commitReason: " + val); + } mSessionCommittedEventLogger.maybeSetCommitReason(val); mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); mSessionCommittedEventLogger.maybeSetSessionDurationMillis( @@ -6411,6 +6433,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") RemoteFillService destroyLocked() { // Log unlogged events. + if (sVerbose) { + Slog.v(TAG, "destroyLocked for session: " + id); + } logAllEvents(COMMIT_REASON_SESSION_DESTROYED); if (mDestroyed) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 0054bc87af77..b43f1a93f183 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -1305,6 +1305,8 @@ public class CompanionDeviceManagerService extends SystemService { mAssociationStore.dump(out); mDevicePresenceMonitor.dump(out); mCompanionAppController.dump(out); + mTransportManager.dump(out); + mSystemDataTransferRequestStore.dump(out); } } diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java index 51c5fd69cdf2..c4c80f907b3a 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java @@ -48,6 +48,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -303,6 +304,32 @@ public class SystemDataTransferRequestStore { } } + + + /** + * Dumps current system data transfer request states. + */ + public void dump(@NonNull PrintWriter out) { + synchronized (mLock) { + out.append("System Data Transfer Requests (Cached): "); + if (mCachedPerUser.size() == 0) { + out.append("<empty>\n"); + } else { + out.append("\n"); + for (int i = 0; i < mCachedPerUser.size(); i++) { + final int userId = mCachedPerUser.keyAt(i); + for (SystemDataTransferRequest request : mCachedPerUser.get(userId)) { + out.append(" u") + .append(String.valueOf(userId)) + .append(" -> ") + .append(request.toString()) + .append('\n'); + } + } + } + } + } + private void writeRequestsToXml(@NonNull TypedXmlSerializer serializer, @Nullable Collection<SystemDataTransferRequest> requests) throws IOException { serializer.startTag(null, XML_TAG_REQUESTS); diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index 3e45626d9799..3861f99eb03c 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -36,6 +36,7 @@ import com.android.server.companion.AssociationStore; import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -225,6 +226,25 @@ public class CompanionTransportManager { } /** + * Dumps current list of active transports. + */ + public void dump(@NonNull PrintWriter out) { + synchronized (mTransports) { + out.append("System Data Transports: "); + if (mTransports.size() == 0) { + out.append("<empty>\n"); + } else { + out.append("\n"); + for (int i = 0; i < mTransports.size(); i++) { + final int associationId = mTransports.keyAt(i); + final Transport transport = mTransports.get(associationId); + out.append(" ").append(transport.toString()).append('\n'); + } + } + } + } + + /** * @hide */ public void enableSecureTransport(boolean enabled) { diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java index ca169aac6a37..05703ce21ca4 100644 --- a/services/companion/java/com/android/server/companion/transport/RawTransport.java +++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java @@ -94,6 +94,13 @@ class RawTransport extends Transport { } } + @Override + public String toString() { + return "RawTransport{" + + "mAssociationId=" + mAssociationId + + '}'; + } + private void receiveMessage() throws IOException { synchronized (mRemoteIn) { final byte[] headerBytes = new byte[HEADER_LENGTH]; diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java index 6e906ebe887a..1e95e65848a5 100644 --- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java +++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java @@ -152,4 +152,12 @@ class SecureTransport extends Transport implements SecureChannel.Callback { close(); } } + + @Override + public String toString() { + return "SecureTransport{" + + "mAssociationId=" + mAssociationId + + ", mSecureChannel=" + mSecureChannel + + '}'; + } } diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 627a62ee0496..34c3d7ec8433 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -246,16 +246,6 @@ public class AdbDebuggingManager { @Override public void run() { - if (mGuid.isEmpty()) { - Slog.e(TAG, "adbwifi guid was not set"); - return; - } - mPort = native_pairing_start(mGuid, mPairingCode); - if (mPort <= 0 || mPort > 65535) { - Slog.e(TAG, "Unable to start pairing server"); - return; - } - // Register the mdns service NsdServiceInfo serviceInfo = new NsdServiceInfo(); serviceInfo.setServiceName(mServiceName); @@ -288,6 +278,28 @@ public class AdbDebuggingManager { mHandler.sendMessage(message); } + @Override + public void start() { + /* + * If a user is fast enough to click cancel, native_pairing_cancel can be invoked + * while native_pairing_start is running which run the destruction of the object + * while it is being constructed. Here we start the pairing server on foreground + * Thread so native_pairing_cancel can never be called concurrently. Then we let + * the pairing server run on a background Thread. + */ + if (mGuid.isEmpty()) { + Slog.e(TAG, "adbwifi guid was not set"); + return; + } + mPort = native_pairing_start(mGuid, mPairingCode); + if (mPort <= 0) { + Slog.e(TAG, "Unable to start pairing server"); + return; + } + + super.start(); + } + public void cancelPairing() { native_pairing_cancel(); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index adc0255743c2..cd45b03ba7ad 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -581,7 +581,8 @@ public final class ActiveServices { if (DEBUG_FOREGROUND_SERVICE) { Slog.i(TAG, " Stopping fg for service " + r); } - setServiceForegroundInnerLocked(r, 0, null, 0, 0); + setServiceForegroundInnerLocked(r, 0, null, 0, 0, + 0); } } @@ -989,7 +990,7 @@ public final class ActiveServices { if (fgRequired) { logFgsBackgroundStart(r); - if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) { + if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r, callingUid)) { String msg = "startForegroundService() not allowed due to " + "mAllowStartForeground false: service " + r.shortInstanceName; @@ -1787,11 +1788,13 @@ public final class ActiveServices { public void setServiceForegroundLocked(ComponentName className, IBinder token, int id, Notification notification, int flags, int foregroundServiceType) { final int userId = UserHandle.getCallingUserId(); + final int callingUid = mAm.mInjector.getCallingUid(); final long origId = mAm.mInjector.clearCallingIdentity(); try { ServiceRecord r = findServiceLocked(className, token, userId); if (r != null) { - setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType); + setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType, + callingUid); } } finally { mAm.mInjector.restoreCallingIdentity(origId); @@ -2106,7 +2109,8 @@ public final class ActiveServices { */ @GuardedBy("mAm") private void setServiceForegroundInnerLocked(final ServiceRecord r, int id, - Notification notification, int flags, int foregroundServiceType) { + Notification notification, int flags, int foregroundServiceType, + int callingUidIfStart) { if (id != 0) { if (notification == null) { throw new IllegalArgumentException("null notification"); @@ -2234,7 +2238,8 @@ public final class ActiveServices { } // Whether FGS-BG-start restriction is enabled for this service. - final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r); + final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r, + callingUidIfStart); // Whether to extend the SHORT_SERVICE time out. boolean extendShortServiceTimeout = false; @@ -8486,14 +8491,43 @@ public final class ActiveServices { NOTE_FOREGROUND_SERVICE_BG_LAUNCH, n.build(), UserHandle.ALL); } - private boolean isBgFgsRestrictionEnabled(ServiceRecord r) { - return mAm.mConstants.mFlagFgsStartRestrictionEnabled - // Checking service's targetSdkVersion. - && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid) - && (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk - // Checking callingUid's targetSdkVersion. - || CompatChanges.isChangeEnabled( - FGS_BG_START_RESTRICTION_CHANGE_ID, r.mRecentCallingUid)); + private boolean isBgFgsRestrictionEnabled(ServiceRecord r, int actualCallingUid) { + // mFlagFgsStartRestrictionEnabled controls whether to enable the BG FGS restrictions: + // - If true (default), BG-FGS restrictions are enabled if the service targets >= S. + // - If false, BG-FGS restrictions are disabled for all apps. + if (!mAm.mConstants.mFlagFgsStartRestrictionEnabled) { + return false; + } + + // If the service target below S, then don't enable the restrictions. + if (!CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid)) { + return false; + } + + // mFgsStartRestrictionCheckCallerTargetSdk controls whether we take the caller's target + // SDK level into account or not: + // - If true (default), BG-FGS restrictions only happens if the caller _also_ targets >= S. + // - If false, BG-FGS restrictions do _not_ use the caller SDK levels. + if (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk) { + return true; // In this case, we only check the service's target SDK level. + } + final int callingUid; + if (Flags.newFgsRestrictionLogic()) { + // We always consider SYSTEM_UID to target S+, so just enable the restrictions. + if (actualCallingUid == Process.SYSTEM_UID) { + return true; + } + callingUid = actualCallingUid; + } else { + // Legacy logic used mRecentCallingUid. + callingUid = r.mRecentCallingUid; + } + if (!CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, callingUid)) { + return false; // If the caller targets < S, then we still disable the restrictions. + } + + // Both the service and the caller target S+, so enable the check. + return true; } private void logFgsBackgroundStart(ServiceRecord r) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a5531ae65bbe..2750344b487e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -187,6 +187,7 @@ import static com.android.server.wm.ActivityTaskManagerService.DUMP_TOP_RESUMED_ import static com.android.server.wm.ActivityTaskManagerService.DUMP_VISIBLE_ACTIVITIES; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToString; +import static com.android.systemui.shared.Flags.enableHomeDelay; import android.Manifest; import android.Manifest.permission; @@ -521,6 +522,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -921,6 +923,15 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") final ComponentAliasResolver mComponentAliasResolver; + private static final long HOME_LAUNCH_TIMEOUT_MS = 15000; + private final AtomicBoolean mHasHomeDelay = new AtomicBoolean(false); + + /** + * Tracks all users with computed color resources by ThemeOverlaycvontroller + */ + @GuardedBy("this") + private final Set<Integer> mThemeOverlayReadiness = new HashSet<>(); + /** * Tracks association information for a particular package along with debuggability. * <p> Associations for a package A are allowed to package B if B is part of the @@ -2332,6 +2343,7 @@ public class ActivityManagerService extends IActivityManager.Stub mService.startBroadcastObservers(); } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { mService.mPackageWatchdog.onPackagesReady(); + mService.setHomeTimeout(); } } @@ -5304,6 +5316,59 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Starts Home if there is no completion signal from ThemeOverlayController + */ + private void setHomeTimeout() { + if (enableHomeDelay() && mHasHomeDelay.compareAndSet(false, true)) { + mHandler.postDelayed(() -> { + if (!getThemeOverlayReadiness()) { + Slog.d(TAG, + "ThemeHomeDelay: ThemeOverlayController not responding, launching " + + "Home after " + + HOME_LAUNCH_TIMEOUT_MS + "ms"); + setThemeOverlayReady(true); + } + }, HOME_LAUNCH_TIMEOUT_MS); + } + } + + /** + * Used by ThemeOverlayController to notify all listeners for + * color palette readiness. + * @hide + */ + @Override + public void setThemeOverlayReady(boolean readiness) { + enforceCallingPermission(Manifest.permission.SET_THEME_OVERLAY_CONTROLLER_READY, + "setThemeOverlayReady"); + + int currentUserId = mUserController.getCurrentUserId(); + + boolean updateReadiness; + synchronized (mThemeOverlayReadiness) { + updateReadiness = readiness ? mThemeOverlayReadiness.add(currentUserId) + : mThemeOverlayReadiness.remove(currentUserId); + } + + if (updateReadiness && readiness && enableHomeDelay()) { + mAtmInternal.startHomeOnAllDisplays(currentUserId, "setThemeOverlayReady"); + } + } + + /** + * Returns current state of ThemeOverlayController color + * palette readiness. + * + * @hide + */ + public boolean getThemeOverlayReadiness() { + int uid = mUserController.getCurrentUserId(); + synchronized (mThemeOverlayReadiness) { + return mThemeOverlayReadiness.contains(uid); + } + } + final void ensureBootCompleted() { boolean booting; boolean enableScreen; @@ -18033,6 +18098,10 @@ public class ActivityManagerService extends IActivityManager.Stub mAtmInternal.onUserStopped(userId); // Clean up various services by removing the user mBatteryStatsService.onUserRemoved(userId); + + synchronized (mThemeOverlayReadiness) { + mThemeOverlayReadiness.remove(userId); + } } @Override @@ -19391,6 +19460,11 @@ public class ActivityManagerService extends IActivityManager.Stub return ActivityManagerService.this.clearApplicationUserData(packageName, keepState, isRestore, observer, userId); } + + @Override + public boolean getThemeOverlayReadiness() { + return ActivityManagerService.this.getThemeOverlayReadiness(); + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index bbbba26303a7..04deb0271c06 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -12458,6 +12458,20 @@ public class AudioService extends IAudioService.Stub return app; } + /** + * Retrieves all audioMixes registered with the AudioPolicyManager + * @return list of registered audio mixes + */ + public List<AudioMix> getRegisteredPolicyMixes() { + if (!android.media.audiopolicy.Flags.audioMixTestApi()) { + return Collections.emptyList(); + } + + synchronized (mAudioPolicies) { + return mAudioSystem.getRegisteredPolicyMixes(); + } + } + public int addMixForPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb) { if (DEBUG_AP) { Log.d(TAG, "addMixForPolicy for " + pcb.asBinder() + " with config:" + policyConfig); } diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 4f46dd13f973..49ab19a816dc 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -27,6 +27,7 @@ import android.media.ISoundDose; import android.media.ISoundDoseCallback; import android.media.audiopolicy.AudioMix; import android.media.audiopolicy.AudioMixingRule; +import android.media.audiopolicy.Flags; import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -42,6 +43,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; @@ -602,6 +604,23 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, } /** + * @return a list of AudioMixes that are registered in the audio policy manager. + */ + public List<AudioMix> getRegisteredPolicyMixes() { + if (!Flags.audioMixTestApi()) { + return Collections.emptyList(); + } + + List<AudioMix> audioMixes = new ArrayList<>(); + int result = AudioSystem.getRegisteredPolicyMixes(audioMixes); + if (result != AudioSystem.SUCCESS) { + throw new IllegalStateException( + "Cannot fetch registered policy mixes. Result: " + result); + } + return Collections.unmodifiableList(audioMixes); + } + + /** * Update already {@link AudioMixingRule}-s for already registered {@link AudioMix}-es. * * @param mixes - array of registered {@link AudioMix}-es to update. diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index fbd32a67fe6c..d061e2d21811 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -31,6 +31,7 @@ import android.util.Slog; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession; import java.util.function.Supplier; @@ -202,6 +203,16 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement } } + // TODO(b/317414324): Deprecate setIgnoreDisplayTouches + protected final void resetIgnoreDisplayTouches() { + final AidlSession session = (AidlSession) getFreshDaemon(); + try { + session.getSession().setIgnoreDisplayTouches(false); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when resetting setIgnoreDisplayTouches"); + } + } + @Override public boolean isInterruptable() { return true; diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java index 92218b1023c4..199db8c48c7c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java @@ -27,9 +27,8 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; /** * Allows clients (such as keyguard) to register for notifications on when biometric lockout @@ -42,7 +41,7 @@ public class LockoutResetDispatcher implements IBinder.DeathRecipient { private final Context mContext; @VisibleForTesting - final List<ClientCallback> mClientCallbacks = new ArrayList<>(); + final ConcurrentLinkedQueue<ClientCallback> mClientCallbacks = new ConcurrentLinkedQueue<>(); private static class ClientCallback { private static final long WAKELOCK_TIMEOUT_MS = 2000; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 8121a639ab0a..93d1b6e079ca 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -232,6 +232,7 @@ public class FingerprintAuthenticationClient handleLockout(authenticated); if (authenticated) { mState = STATE_STOPPED; + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); @@ -268,6 +269,7 @@ public class FingerprintAuthenticationClient // Send the error, but do not invoke the FinishCallback yet. Since lockout is not // controlled by the HAL, the framework must stop the sensor before finishing the // client. + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); @@ -298,6 +300,7 @@ public class FingerprintAuthenticationClient BiometricNotificationUtils.showBadCalibrationNotification(getContext()); } + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); @@ -306,6 +309,7 @@ public class FingerprintAuthenticationClient @Override protected void startHalOperation() { + resetIgnoreDisplayTouches(); mSensorOverlays.show(getSensorId(), getRequestReason(), this); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason()); @@ -419,6 +423,7 @@ public class FingerprintAuthenticationClient @Override protected void stopHalOperation() { + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); @@ -518,6 +523,7 @@ public class FingerprintAuthenticationClient Slog.e(TAG, "Remote exception", e); } + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); @@ -548,6 +554,7 @@ public class FingerprintAuthenticationClient Slog.e(TAG, "Remote exception", e); } + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index cb220b9e1c34..8d2b46fe743d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -87,6 +87,7 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> @Override protected void stopHalOperation() { + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); unsubscribeBiometricContext(); @@ -102,6 +103,7 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> @Override protected void startHalOperation() { + resetIgnoreDisplayTouches(); mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD, this); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 225bd594adc6..79975e515c70 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -144,6 +144,7 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement controller -> controller.onEnrollmentProgress(getSensorId(), remaining)); if (remaining == 0) { + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); @@ -178,6 +179,7 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); @@ -192,6 +194,7 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override protected void startHalOperation() { + resetIgnoreDisplayTouches(); mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason), this); if (sidefpsControllerRefactor()) { @@ -273,6 +276,7 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override protected void stopHalOperation() { + resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (sidefpsControllerRefactor()) { mAuthenticationStateListeners.onAuthenticationStopped(); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 458fd82d9a65..05e681edb05e 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -1020,6 +1020,10 @@ public class CameraServiceProxy extends SystemService } } + private boolean isAutomotive() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + private Set<Integer> getEnabledUserHandles(int currentUserHandle) { int[] userProfiles = mUserManager.getEnabledProfileIds(currentUserHandle); Set<Integer> handles = new ArraySet<>(userProfiles.length); @@ -1030,8 +1034,8 @@ public class CameraServiceProxy extends SystemService if (Flags.cameraHsumPermission()) { // If the device is running in headless system user mode then allow - // User 0 to access camera. - if (UserManager.isHeadlessSystemUserMode()) { + // User 0 to access camera only for automotive form factor. + if (UserManager.isHeadlessSystemUserMode() && isAutomotive()) { handles.add(UserHandle.USER_SYSTEM); } } diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java index c260f10b61a6..6a6e6ab23687 100644 --- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java +++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java @@ -47,5 +47,19 @@ public abstract class GrammaticalInflectionManagerInternal { * @see Configuration#getGrammaticalGender */ public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId); + + /** + * Retrieve the system grammatical gender. + * + * @return the value of grammatical gender + * + */ + public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender( + Configuration configuration); + + /** + * Whether the package can get the system grammatical gender or not. + */ + public abstract boolean canGetSystemGrammaticalGender(int uid, String packageName); } diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java index 6eb7e9559b8d..d01f54f09679 100644 --- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java +++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java @@ -22,17 +22,21 @@ import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission; import android.annotation.Nullable; +import android.app.ActivityTaskManager; import android.app.GrammaticalInflectionManager; import android.app.IGrammaticalInflectionManager; import android.content.AttributionSource; import android.content.Context; import android.content.pm.PackageManagerInternal; +import android.content.res.Configuration; import android.os.Binder; import android.os.Environment; import android.os.Process; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.SystemProperties; +import android.os.Trace; import android.permission.PermissionManager; import android.util.AtomicFile; import android.util.Log; @@ -43,6 +47,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityTaskManagerInternal; @@ -71,6 +76,7 @@ public class GrammaticalInflectionService extends SystemService { private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection"; private static final String GRAMMATICAL_INFLECTION_ENABLED = "i18n.grammatical_Inflection.enabled"; + private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender"; private final GrammaticalInflectionBackupHelper mBackupHelper; private final ActivityTaskManagerInternal mActivityTaskManagerInternal; @@ -121,16 +127,16 @@ public class GrammaticalInflectionService extends SystemService { @Override public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) { checkCallerIsSystem(); - checkSystemTermsOfAddressIsEnabled(); GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender, userId); } @Override public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) { - checkSystemTermsOfAddressIsEnabled(); - return GrammaticalInflectionService.this.getSystemGrammaticalGender(attributionSource, - userId); + return canGetSystemGrammaticalGender(attributionSource) + ? GrammaticalInflectionService.this.getSystemGrammaticalGender( + attributionSource, userId) + : GRAMMATICAL_GENDER_NOT_SPECIFIED; } @Override @@ -159,9 +165,33 @@ public class GrammaticalInflectionService extends SystemService { @Override public int getSystemGrammaticalGender(int userId) { - checkCallerIsSystem(); - return GrammaticalInflectionService.this.getSystemGrammaticalGender( - mContext.getAttributionSource(), userId); + return checkSystemTermsOfAddressIsEnabled() + ? GrammaticalInflectionService.this.getSystemGrammaticalGender( + mContext.getAttributionSource(), userId) + : GRAMMATICAL_GENDER_NOT_SPECIFIED; + } + + @Override + public int retrieveSystemGrammaticalGender(Configuration configuration) { + int systemGrammaticalGender = getSystemGrammaticalGender(mContext.getUserId()); + // Retrieve the grammatical gender from system property, set it into + // configuration which will get updated later if the grammatical gender raw value of + // current configuration is {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}. + if (configuration.getGrammaticalGenderRaw() + == Configuration.GRAMMATICAL_GENDER_UNDEFINED + || systemGrammaticalGender <= Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) { + systemGrammaticalGender = SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY, + Configuration.GRAMMATICAL_GENDER_UNDEFINED); + } + return systemGrammaticalGender; + } + + @Override + public boolean canGetSystemGrammaticalGender(int uid, String packageName) { + AttributionSource attributionSource = new AttributionSource.Builder( + uid).setPackageName(packageName).build(); + return GrammaticalInflectionService.this.canGetSystemGrammaticalGender( + attributionSource); } } @@ -202,11 +232,20 @@ public class GrammaticalInflectionService extends SystemService { } protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) { + Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender"); if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains( grammaticalGender)) { throw new IllegalArgumentException("Unknown grammatical gender"); } + if (!checkSystemTermsOfAddressIsEnabled()) { + if (grammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED) { + return; + } + Log.d(TAG, "Clearing the system grammatical gender setting"); + grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED; + } + synchronized (mLock) { final File file = getGrammaticalGenderFile(userId); final AtomicFile atomicFile = new AtomicFile(file); @@ -224,6 +263,15 @@ public class GrammaticalInflectionService extends SystemService { throw new RuntimeException(e); } } + + try { + Configuration config = new Configuration(); + config.setGrammaticalGender(grammaticalGender); + ActivityTaskManager.getService().updateConfiguration(config); + } catch (RemoteException e) { + Log.w(TAG, "Can not update configuration", e); + } + Trace.endSection(); } public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) { @@ -233,34 +281,9 @@ public class GrammaticalInflectionService extends SystemService { return GRAMMATICAL_GENDER_NOT_SPECIFIED; } - int callingUid = Binder.getCallingUid(); - if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) != callingUid) { - Log.d(TAG, - "Package " + packageName + " does not belong to the calling uid " + callingUid); - return GRAMMATICAL_GENDER_NOT_SPECIFIED; - } - - if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) { - return GRAMMATICAL_GENDER_NOT_SPECIFIED; - } - synchronized (mLock) { - final File file = getGrammaticalGenderFile(userId); - if (!file.exists()) { - Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file."); - return GRAMMATICAL_GENDER_NOT_SPECIFIED; - } - - if (mGrammaticalGenderCache.indexOfKey(userId) < 0) { - try { - InputStream in = new FileInputStream(file); - final TypedXmlPullParser parser = Xml.resolvePullParser(in); - mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser)); - } catch (IOException | XmlPullParserException e) { - Log.e(TAG, "Failed to parse XML configuration from " + file, e); - } - } - return mGrammaticalGenderCache.get(userId); + int grammaticalGender = mGrammaticalGenderCache.get(userId); + return grammaticalGender < 0 ? GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender; } } @@ -311,9 +334,39 @@ public class GrammaticalInflectionService extends SystemService { } } - private void checkSystemTermsOfAddressIsEnabled() { + private boolean checkSystemTermsOfAddressIsEnabled() { if (!systemTermsOfAddressEnabled()) { - throw new RuntimeException("The flag must be enabled to allow calling the API."); + Log.d(TAG, "The flag must be enabled to allow calling the API."); + return false; } + return true; + } + + private boolean canGetSystemGrammaticalGender(AttributionSource attributionSource) { + return checkSystemTermsOfAddressIsEnabled() && checkSystemGrammaticalGenderPermission( + mPermissionManager, attributionSource); + } + + @Override + public void onUserUnlocked(TargetUser user) { + IoThread.getHandler().post(() -> { + int userId = user.getUserIdentifier(); + final File file = getGrammaticalGenderFile(userId); + synchronized (mLock) { + if (!file.exists()) { + Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file."); + return; + } + if (mGrammaticalGenderCache.indexOfKey(userId) < 0) { + try { + InputStream in = new FileInputStream(file); + final TypedXmlPullParser parser = Xml.resolvePullParser(in); + mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser)); + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Failed to parse XML configuration from " + file, e); + } + } + } + }); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index e0e825d9147a..c8c662387d16 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1366,6 +1366,12 @@ public class HdmiControlService extends SystemService { // we don't call onInitializeCecComplete() // since we reallocate the logical address only. onInitializeCecComplete(initiatedBy); + } else if (initiatedBy == INITIATED_BY_HOTPLUG + && mDisplayStatusCallback == null) { + // Force to update display status for hotplug event. + synchronized (mLock) { + announceHdmiControlStatusChange(mHdmiControlEnabled); + } } // We remove local devices here, instead of before the start of // address allocation, to prevent multiple local devices of the diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 7726609e7075..574be34e56e5 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -621,10 +621,6 @@ public class InputManagerService extends IInputManager.Stub mBatteryController.systemRunning(); mKeyboardBacklightController.systemRunning(); mKeyRemapper.systemRunning(); - - mNative.setStylusPointerIconEnabled( - Objects.requireNonNull(mContext.getSystemService(InputManager.class)) - .isStylusPointerIconEnabled()); } private void reloadDeviceAliases() { diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index 5ffc3809ec98..c02d5249fdce 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -94,7 +94,9 @@ class InputSettingsObserver extends ContentObserver { Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SLOW_KEYS), (reason) -> updateAccessibilitySlowKeys()), Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_STICKY_KEYS), - (reason) -> updateAccessibilityStickyKeys())); + (reason) -> updateAccessibilityStickyKeys()), + Map.entry(Settings.Secure.getUriFor(Settings.Secure.STYLUS_POINTER_ICON_ENABLED), + (reason) -> updateStylusPointerIconEnabled())); } /** @@ -254,4 +256,8 @@ class InputSettingsObserver extends ContentObserver { mNative.setMinTimeBetweenUserActivityPokes(intervalMillis); } } + + private void updateStylusPointerIconEnabled() { + mNative.setStylusPointerIconEnabled(InputSettings.isStylusPointerIconEnabled(mContext)); + } } diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java index 66ad7169d6ec..a56406ed1412 100644 --- a/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java @@ -49,4 +49,10 @@ public interface PersistentDataBlockManagerInternal { /** Retrieves the UID that can access the persistent data partition. */ int getAllowedUid(); + + /** + * Attempt to deactivate Factory Reset Protection (FRP) without a secret. Returns true if + * successful, false if not. + */ + boolean deactivateFactoryResetProtectionWithoutSecret(); } diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java index b9b09fb0e84c..133fc8faf14d 100644 --- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java @@ -18,16 +18,31 @@ package com.android.server.pdb; import static com.android.internal.util.Preconditions.checkArgument; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.SYNC; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static java.nio.file.StandardOpenOption.WRITE; + import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; +import android.security.Flags; import android.service.persistentdata.IPersistentDataBlockService; import android.service.persistentdata.PersistentDataBlockManager; import android.text.TextUtils; @@ -56,10 +71,10 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.HexFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -85,9 +100,14 @@ import java.util.concurrent.TimeUnit; * | --------------------------------------------| * | FRP data block length (4 bytes) | * | --------------------------------------------| - * | FRP data (variable length) | + * | FRP data (variable length; 100KB max) | * | --------------------------------------------| * | ... | + * | Empty space. | + * | ... | + * | --------------------------------------------| + * | FRP secret magic (8 bytes) | + * | FRP secret (32 bytes) | * | --------------------------------------------| * | Test mode data block (10000 bytes) | * | --------------------------------------------| @@ -127,6 +147,14 @@ public class PersistentDataBlockService extends SystemService { /** Maximum size of the FRP credential handle that can be stored. */ @VisibleForTesting static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4; + /** Size of the FRP mode deactivation secret, in bytes */ + @VisibleForTesting + static final int FRP_SECRET_SIZE = 32; + /** Magic value to identify the FRP secret is present. */ + @VisibleForTesting + static final byte[] FRP_SECRET_MAGIC = {(byte) 0xda, (byte) 0xc2, (byte) 0xfc, + (byte) 0xcd, (byte) 0xb9, 0x1b, 0x09, (byte) 0x88}; + /** * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header. */ @@ -145,21 +173,52 @@ public class PersistentDataBlockService extends SystemService { private static final String FLASH_LOCK_LOCKED = "1"; private static final String FLASH_LOCK_UNLOCKED = "0"; + /** + * Path to FRP secret stored on /data. This file enables automatic deactivation of FRP mode if + * it contains the current FRP secret. When /data is wiped in an untrusted reset this file is + * destroyed, blocking automatic deactivation. + */ + private static final String FRP_SECRET_FILE = "/data/system/frp_secret"; + + /** + * Path to temp file used when changing the FRP secret. + */ + private static final String FRP_SECRET_TMP_FILE = "/data/system/frp_secret_tmp"; + + public static final String BOOTLOADER_LOCK_STATE = "ro.boot.vbmeta.device_state"; + public static final String VERIFIED_BOOT_STATE = "ro.boot.verifiedbootstate"; + public static final int INIT_WAIT_TIMEOUT = 10; + private final Context mContext; private final String mDataBlockFile; private final boolean mIsFileBacked; private final Object mLock = new Object(); private final CountDownLatch mInitDoneSignal = new CountDownLatch(1); + private final String mFrpSecretFile; + private final String mFrpSecretTmpFile; private int mAllowedUid = -1; private long mBlockDeviceSize = -1; // Load lazily + private final boolean mFrpEnforced; + + /** + * FRP active state. When true (the default) we may have had an untrusted factory reset. In + * that case we block any updates of the persistent data block. To exit active state, it's + * necessary for some caller to provide the FRP secret. + */ + private boolean mFrpActive = false; + @GuardedBy("mLock") private boolean mIsWritable = true; public PersistentDataBlockService(Context context) { super(context); mContext = context; + mFrpEnforced = Flags.frpEnforcement(); + mFrpActive = mFrpEnforced; + mFrpSecretFile = FRP_SECRET_FILE; + mFrpSecretTmpFile = FRP_SECRET_TMP_FILE; if (SystemProperties.getBoolean(GSI_RUNNING_PROP, false)) { mIsFileBacked = true; mDataBlockFile = GSI_SANDBOX; @@ -171,12 +230,17 @@ public class PersistentDataBlockService extends SystemService { @VisibleForTesting PersistentDataBlockService(Context context, boolean isFileBacked, String dataBlockFile, - long blockDeviceSize) { + long blockDeviceSize, boolean frpEnabled, String frpSecretFile, + String frpSecretTmpFile) { super(context); mContext = context; mIsFileBacked = isFileBacked; mDataBlockFile = dataBlockFile; mBlockDeviceSize = blockDeviceSize; + mFrpEnforced = frpEnabled; + mFrpActive = mFrpEnforced; + mFrpSecretFile = frpSecretFile; + mFrpSecretTmpFile = frpSecretTmpFile; } private int getAllowedUid() { @@ -206,24 +270,35 @@ public class PersistentDataBlockService extends SystemService { // Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY SystemServerInitThreadPool.submit(() -> { enforceChecksumValidity(); - formatIfOemUnlockEnabled(); + if (mFrpEnforced) { + automaticallyDeactivateFrpIfPossible(); + setOemUnlockEnabledProperty(doGetOemUnlockEnabled()); + // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it. + // They should switch to calling #isFrpActive(). + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.SECURE_FRP_MODE, mFrpActive ? 1 : 0); + } else { + formatIfOemUnlockEnabled(); + } publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService); - mInitDoneSignal.countDown(); + signalInitDone(); }, TAG + ".onStart"); } + @VisibleForTesting + void signalInitDone() { + mInitDoneSignal.countDown(); + } + + private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) { + setProperty(OEM_UNLOCK_PROP, oemUnlockEnabled ? "1" : "0"); + } + @Override public void onBootPhase(int phase) { // Wait for initialization in onStart to finish if (phase == PHASE_SYSTEM_SERVICES_READY) { - try { - if (!mInitDoneSignal.await(10, TimeUnit.SECONDS)) { - throw new IllegalStateException("Service " + TAG + " init timeout"); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Service " + TAG + " init interrupted", e); - } + waitForInitDoneSignal(); // The user responsible for FRP should exist by now. mAllowedUid = getAllowedUid(); LocalServices.addService(PersistentDataBlockManagerInternal.class, mInternalService); @@ -231,6 +306,17 @@ public class PersistentDataBlockService extends SystemService { super.onBootPhase(phase); } + private void waitForInitDoneSignal() { + try { + if (!mInitDoneSignal.await(INIT_WAIT_TIMEOUT, TimeUnit.SECONDS)) { + throw new IllegalStateException("Service " + TAG + " init timeout"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Service " + TAG + " init interrupted", e); + } + } + @VisibleForTesting void setAllowedUid(int uid) { mAllowedUid = uid; @@ -243,8 +329,7 @@ public class PersistentDataBlockService extends SystemService { formatPartitionLocked(true); } } - - setProperty(OEM_UNLOCK_PROP, enabled ? "1" : "0"); + setOemUnlockEnabledProperty(enabled); } private void enforceOemUnlockReadPermission() { @@ -263,9 +348,18 @@ public class PersistentDataBlockService extends SystemService { "Can't modify OEM unlock state"); } + private void enforceConfigureFrpPermission() { + if (mFrpEnforced && mContext.checkCallingOrSelfPermission( + Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION) + == PackageManager.PERMISSION_DENIED) { + throw new SecurityException(("Can't configure Factory Reset Protection. Requires " + + "CONFIGURE_FACTORY_RESET_PROTECTION")); + } + } + private void enforceUid(int callingUid) { - if (callingUid != mAllowedUid) { - throw new SecurityException("uid " + callingUid + " not allowed to access PST"); + if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT) { + throw new SecurityException("uid " + callingUid + " not allowed to access PDB"); } } @@ -315,7 +409,9 @@ public class PersistentDataBlockService extends SystemService { @VisibleForTesting int getMaximumFrpDataSize() { - return (int) (getTestHarnessModeDataOffset() - DIGEST_SIZE_BYTES - HEADER_SIZE); + long frpSecretSize = mFrpEnforced ? FRP_SECRET_MAGIC.length + FRP_SECRET_SIZE : 0; + return (int) (getTestHarnessModeDataOffset() - DIGEST_SIZE_BYTES - HEADER_SIZE + - frpSecretSize); } @VisibleForTesting @@ -324,6 +420,16 @@ public class PersistentDataBlockService extends SystemService { } @VisibleForTesting + long getFrpSecretMagicOffset() { + return getFrpSecretDataOffset() - FRP_SECRET_MAGIC.length; + } + + @VisibleForTesting + long getFrpSecretDataOffset() { + return getTestHarnessModeDataOffset() - FRP_SECRET_SIZE; + } + + @VisibleForTesting long getTestHarnessModeDataOffset() { return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE; } @@ -349,6 +455,11 @@ public class PersistentDataBlockService extends SystemService { } private FileChannel getBlockOutputChannel() throws IOException { + enforceFactoryResetProtectionInactive(); + return getBlockOutputChannelIgnoringFrp(); + } + + private FileChannel getBlockOutputChannelIgnoringFrp() throws FileNotFoundException { return new RandomAccessFile(mDataBlockFile, "rw").getChannel(); } @@ -416,7 +527,7 @@ public class PersistentDataBlockService extends SystemService { @VisibleForTesting void formatPartitionLocked(boolean setOemUnlockEnabled) { - try (FileChannel channel = getBlockOutputChannel()) { + try (FileChannel channel = getBlockOutputChannelIgnoringFrp()) { // Format the data selectively. // // 1. write header, set length = 0 @@ -431,12 +542,18 @@ public class PersistentDataBlockService extends SystemService { // 2. corrupt the legacy FRP data explicitly int payload_size = (int) getBlockDeviceSize() - header_size; - buf = ByteBuffer.allocate(payload_size - - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1); + if (mFrpEnforced) { + buf = ByteBuffer.allocate(payload_size - TEST_MODE_RESERVED_SIZE + - FRP_SECRET_MAGIC.length - FRP_SECRET_SIZE - FRP_CREDENTIAL_RESERVED_SIZE + - 1); + } else { + buf = ByteBuffer.allocate(payload_size - TEST_MODE_RESERVED_SIZE + - FRP_CREDENTIAL_RESERVED_SIZE - 1); + } channel.write(buf); channel.force(true); - // 3. skip the test mode data and leave it unformat + // 3. skip the test mode data and leave it unformatted. // This is for a feature that enables testing. channel.position(channel.position() + TEST_MODE_RESERVED_SIZE); @@ -451,6 +568,11 @@ public class PersistentDataBlockService extends SystemService { buf.flip(); channel.write(buf); channel.force(true); + + // 6. Write the default FRP secret (all zeros). + if (mFrpEnforced) { + writeFrpMagicAndDefaultSecret(); + } } catch (IOException e) { Slog.e(TAG, "failed to format block", e); return; @@ -460,10 +582,198 @@ public class PersistentDataBlockService extends SystemService { computeAndWriteDigestLocked(); } - private void doSetOemUnlockEnabledLocked(boolean enabled) { + /** + * Try to deactivate FRP by presenting an FRP secret from the data partition, or the default + * secret if the secret(s) on the data partition are not present or don't work. + */ + @VisibleForTesting + boolean automaticallyDeactivateFrpIfPossible() { + synchronized (mLock) { + if (deactivateFrpWithFileSecret(mFrpSecretFile)) { + return true; + } - try (FileChannel channel = getBlockOutputChannel()) { + Slog.w(TAG, "Failed to deactivate with primary secret file, trying backup."); + if (deactivateFrpWithFileSecret(mFrpSecretTmpFile)) { + // The backup file has the FRP secret, make it the primary file. + moveFrpTempFileToPrimary(); + return true; + } + + Slog.w(TAG, "Failed to deactivate with backup secret file, trying default secret."); + if (deactivateFrp(new byte[FRP_SECRET_SIZE])) { + return true; + } + + // We could not deactivate FRP. It's possible that we have hit an obscure corner case, + // a device that once ran a version of Android that set the FRP magic and a secret, + // then downgraded to a version that did not know about FRP, wiping the FRP secrets + // files, then upgraded to a version (the current one) that does know about FRP, + // potentially leaving the user unable to deactivate FRP because all copies of the + // secret are gone. + // + // To handle this case, we check to see if we have recently upgraded from a pre-V + // version. If so, we deactivate FRP and set the secret to the default value. + if (isUpgradingFromPreVRelease()) { + Slog.w(TAG, "Upgrading from Android 14 or lower, defaulting FRP secret"); + writeFrpMagicAndDefaultSecret(); + mFrpActive = false; + return true; + } + + Slog.e(TAG, "Did not find valid FRP secret, FRP remains active."); + return false; + } + } + + private boolean deactivateFrpWithFileSecret(String frpSecretFile) { + try { + return deactivateFrp(Files.readAllBytes(Paths.get(frpSecretFile))); + } catch (IOException e) { + Slog.w(TAG, "Failed to read FRP secret file: " + frpSecretFile + " " + + e.getClass().getSimpleName()); + return false; + } + } + + private void moveFrpTempFileToPrimary() { + try { + Files.move(Paths.get(mFrpSecretTmpFile), Paths.get(mFrpSecretFile), REPLACE_EXISTING); + } catch (IOException e) { + Slog.e(TAG, "Error moving FRP backup file to primary (ignored)", e); + } + } + + @VisibleForTesting + boolean isFrpActive() { + waitForInitDoneSignal(); + synchronized (mLock) { + return mFrpActive; + } + } + + /** + * Write the provided secret to the FRP secret file in /data and to the /persist partition. + * + * Writing is a three-step process, to ensure that we can recover from a crash at any point. + */ + private boolean updateFrpSecret(byte[] secret) { + // 1. Write the new secret to a temporary file, and sync the write. + try { + Files.write( + Paths.get(mFrpSecretTmpFile), secret, WRITE, CREATE, TRUNCATE_EXISTING, SYNC); + } catch (IOException e) { + Slog.e(TAG, "Failed to write FRP secret file", e); + return false; + } + + // 2. Write the new secret to /persist, and sync the write. + if (!mInternalService.writeDataBuffer(getFrpSecretDataOffset(), ByteBuffer.wrap(secret))) { + return false; + } + + // 3. Move the temporary file to the primary file location. Syncing doesn't matter + // here. In the event this update doesn't complete it will get done by + // #automaticallyDeactivateFrpIfPossible() during the next boot. + moveFrpTempFileToPrimary(); + return true; + } + + /** + * Only for testing, activate FRP. + */ + @VisibleForTesting + void activateFrp() { + synchronized (mLock) { + mFrpActive = true; + } + } + + private boolean hasFrpSecretMagic() { + final byte[] frpMagic = + readDataBlock(getFrpSecretMagicOffset(), FRP_SECRET_MAGIC.length); + if (frpMagic == null) { + // Transient read error on the partition? + Slog.e(TAG, "Failed to read FRP magic region."); + return false; + } + return Arrays.equals(frpMagic, FRP_SECRET_MAGIC); + } + private byte[] getFrpSecret() { + return readDataBlock(getFrpSecretDataOffset(), FRP_SECRET_SIZE); + } + + private boolean deactivateFrp(byte[] secret) { + if (secret == null || secret.length != FRP_SECRET_SIZE) { + Slog.w(TAG, "Attempted to deactivate FRP with a null or incorrectly-sized secret"); + return false; + } + + synchronized (mLock) { + if (!hasFrpSecretMagic()) { + Slog.i(TAG, "No FRP secret magic, system must have been upgraded."); + writeFrpMagicAndDefaultSecret(); + } + } + + final byte[] partitionSecret = getFrpSecret(); + if (partitionSecret == null || partitionSecret.length != FRP_SECRET_SIZE) { + Slog.e(TAG, "Failed to read FRP secret from persistent data partition"); + return false; + } + + // MessageDigest.isEqual is constant-time, to protect secret deduction by timing attack. + if (MessageDigest.isEqual(secret, partitionSecret)) { + mFrpActive = false; + Slog.i(TAG, "FRP secret matched, FRP deactivated."); + return true; + } else { + Slog.e(TAG, + "FRP deactivation failed with secret " + HexFormat.of().formatHex(secret)); + return false; + } + } + + private void writeFrpMagicAndDefaultSecret() { + try (FileChannel channel = getBlockOutputChannelIgnoringFrp()) { + synchronized (mLock) { + // Write secret first in case we crash between the writes, causing the first write + // to be synced but the second to be lost. + Slog.i(TAG, "Writing default FRP secret"); + channel.position(getFrpSecretDataOffset()); + channel.write(ByteBuffer.allocate(FRP_SECRET_SIZE)); + channel.force(true); + + Slog.i(TAG, "Writing FRP secret magic"); + channel.position(getFrpSecretMagicOffset()); + channel.write(ByteBuffer.wrap(FRP_SECRET_MAGIC)); + channel.force(true); + + mFrpActive = false; + } + } catch (IOException e) { + Slog.e(TAG, "Failed to write FRP magic and default secret", e); + } + } + + @VisibleForTesting + byte[] readDataBlock(long offset, int length) { + try (DataInputStream inputStream = + new DataInputStream(new FileInputStream(new File(mDataBlockFile)))) { + synchronized (mLock) { + inputStream.skip(offset); + byte[] bytes = new byte[length]; + inputStream.readFully(bytes); + return bytes; + } + } catch (IOException e) { + throw new IllegalStateException("persistent partition not readable", e); + } + } + + private void doSetOemUnlockEnabledLocked(boolean enabled) { + try (FileChannel channel = getBlockOutputChannel()) { channel.position(getBlockDeviceSize() - 1); ByteBuffer data = ByteBuffer.allocate(1); @@ -475,7 +785,7 @@ public class PersistentDataBlockService extends SystemService { Slog.e(TAG, "unable to access persistent partition", e); return; } finally { - setProperty(OEM_UNLOCK_PROP, enabled ? "1" : "0"); + setOemUnlockEnabledProperty(enabled); } } @@ -507,8 +817,10 @@ public class PersistentDataBlockService extends SystemService { } private long doGetMaximumDataBlockSize() { - long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES - - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1; + final long frpSecretSize = + mFrpEnforced ? (FRP_SECRET_MAGIC.length + FRP_SECRET_SIZE) : 0; + final long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES + - TEST_MODE_RESERVED_SIZE - frpSecretSize - FRP_CREDENTIAL_RESERVED_SIZE - 1; return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE; } @@ -526,6 +838,140 @@ public class PersistentDataBlockService extends SystemService { } private final IBinder mService = new IPersistentDataBlockService.Stub() { + private int printFrpStatus(PrintWriter pw, boolean printSecrets) { + enforceUid(Binder.getCallingUid()); + + pw.println("FRP state"); + pw.println("========="); + pw.println("Enforcement enabled: " + mFrpEnforced); + pw.println("FRP state: " + mFrpActive); + printFrpDataFilesContents(pw, printSecrets); + printFrpSecret(pw, printSecrets); + pw.println("OEM unlock state: " + getOemUnlockEnabled()); + pw.println("Bootloader lock state: " + getFlashLockState()); + pw.println("Verified boot state: " + getVerifiedBootState()); + pw.println("Has FRP credential handle: " + hasFrpCredentialHandle()); + pw.println("FRP challenge block size: " + getDataBlockSize()); + return 1; + } + + private void printFrpSecret(PrintWriter pw, boolean printSecret) { + if (hasFrpSecretMagic()) { + if (printSecret) { + pw.println("FRP secret in PDB: " + HexFormat.of().formatHex( + readDataBlock(getFrpSecretDataOffset(), FRP_SECRET_SIZE))); + } else { + pw.println("FRP secret present but omitted."); + } + } else { + pw.println("FRP magic not found"); + } + } + + private void printFrpDataFilesContents(PrintWriter pw, boolean printSecrets) { + printFrpDataFileContents(pw, mFrpSecretFile, printSecrets); + printFrpDataFileContents(pw, mFrpSecretTmpFile, printSecrets); + } + + private void printFrpDataFileContents( + PrintWriter pw, String frpSecretFile, boolean printSecret) { + if (Files.exists(Paths.get(frpSecretFile))) { + if (printSecret) { + try { + pw.println("FRP secret in " + frpSecretFile + ": " + HexFormat.of() + .formatHex(Files.readAllBytes(Paths.get(mFrpSecretFile)))); + } catch (IOException e) { + Slog.e(TAG, "Failed to read " + frpSecretFile, e); + } + } else { + pw.println( + "FRP secret file " + frpSecretFile + " exists, contents omitted."); + } + } + } + + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + if (!mFrpEnforced) { + super.onShellCommand(in, out, err, args, callback, resultReceiver); + return; + } + new ShellCommand(){ + @Override + public int onCommand(final String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + final PrintWriter pw = getOutPrintWriter(); + return switch (cmd) { + case "status" -> printFrpStatus(pw, /* printSecrets */ !mFrpActive); + case "activate" -> { + activateFrp(); + yield printFrpStatus(pw, /* printSecrets */ !mFrpActive); + } + + case "deactivate" -> { + byte[] secret = hashSecretString(getNextArg()); + pw.println("Attempting to deactivate with: " + HexFormat.of().formatHex( + secret)); + pw.println("Deactivation " + + (deactivateFrp(secret) ? "succeeded" : "failed")); + yield printFrpStatus(pw, /* printSecrets */ !mFrpActive); + } + + case "auto_deactivate" -> { + boolean result = automaticallyDeactivateFrpIfPossible(); + pw.println( + "Automatic deactivation " + (result ? "succeeded" : "failed")); + yield printFrpStatus(pw, /* printSecrets */ !mFrpActive); + } + + case "set_secret" -> { + byte[] secret = new byte[FRP_SECRET_SIZE]; + String secretString = getNextArg(); + if (!secretString.equals("default")) { + secret = hashSecretString(secretString); + } + pw.println("Setting FRP secret to: " + HexFormat.of() + .formatHex(secret) + " length: " + secret.length); + setFactoryResetProtectionSecret(secret); + yield printFrpStatus(pw, /* printSecrets */ !mFrpActive); + } + + default -> handleDefaultCommands(cmd); + }; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Commands"); + pw.println("status: Print the FRP state and associated information."); + pw.println("activate: Put FRP into \"active\" mode."); + pw.println("deactivate <secret>: Deactivate with a hash of 'secret'."); + pw.println("auto_deactivate: Deactivate with the stored secret or the default"); + pw.println("set_secret <secret>: Set the stored secret to a hash of `secret`"); + } + + private static byte[] hashSecretString(String secretInput) { + try { + // SHA-256 produces 32-byte outputs, same as the FRP secret size, so it's + // a convenient way to "normalize" the length of whatever the user provided. + // Also, hashing makes it difficult for an attacker to set the secret to a + // known value that was randomly generated. + MessageDigest md = MessageDigest.getInstance("SHA-256"); + return md.digest(secretInput.getBytes()); + } catch (NoSuchAlgorithmException e) { + Slog.e(TAG, "Can't happen", e); + return new byte[FRP_SECRET_SIZE]; + } + } + }.exec(this, in, out, err, args, callback, resultReceiver); + } /** * Write the data to the persistent data block. @@ -545,7 +991,7 @@ public class PersistentDataBlockService extends SystemService { } ByteBuffer headerAndData = ByteBuffer.allocate( - data.length + HEADER_SIZE + DIGEST_SIZE_BYTES); + data.length + HEADER_SIZE + DIGEST_SIZE_BYTES); headerAndData.put(new byte[DIGEST_SIZE_BYTES]); headerAndData.putInt(PARTITION_TYPE_MARKER); headerAndData.putInt(data.length); @@ -619,6 +1065,7 @@ public class PersistentDataBlockService extends SystemService { @Override public void wipe() { + enforceFactoryResetProtectionInactive(); enforceOemUnlockWritePermission(); synchronized (mLock) { @@ -626,7 +1073,7 @@ public class PersistentDataBlockService extends SystemService { if (mIsFileBacked) { try { Files.write(Paths.get(mDataBlockFile), new byte[MAX_DATA_BLOCK_SIZE], - StandardOpenOption.TRUNCATE_EXISTING); + TRUNCATE_EXISTING); ret = 0; } catch (IOException e) { ret = -1; @@ -685,6 +1132,10 @@ public class PersistentDataBlockService extends SystemService { } } + private static String getVerifiedBootState() { + return SystemProperties.get(VERIFIED_BOOT_STATE); + } + @Override public int getDataBlockSize() { enforcePersistentDataBlockAccess(); @@ -716,6 +1167,18 @@ public class PersistentDataBlockService extends SystemService { } } + private void enforceConfigureFrpPermissionOrPersistentDataBlockAccess() { + if (!mFrpEnforced) { + enforcePersistentDataBlockAccess(); + } else { + if (mContext.checkCallingOrSelfPermission( + Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION) + == PackageManager.PERMISSION_DENIED) { + enforcePersistentDataBlockAccess(); + } + } + } + @Override public long getMaximumDataBlockSize() { enforceUid(Binder.getCallingUid()); @@ -724,7 +1187,7 @@ public class PersistentDataBlockService extends SystemService { @Override public boolean hasFrpCredentialHandle() { - enforcePersistentDataBlockAccess(); + enforceConfigureFrpPermissionOrPersistentDataBlockAccess(); try { return mInternalService.getFrpCredentialHandle() != null; } catch (IllegalStateException e) { @@ -751,9 +1214,51 @@ public class PersistentDataBlockService extends SystemService { synchronized (mLock) { pw.println("mIsWritable: " + mIsWritable); } + printFrpStatus(pw, /* printSecrets */ false); + } + + @Override + public boolean isFactoryResetProtectionActive() { + return isFrpActive(); + } + + @Override + public boolean deactivateFactoryResetProtection(byte[] secret) { + enforceConfigureFrpPermission(); + return deactivateFrp(secret); + } + + @Override + public boolean setFactoryResetProtectionSecret(byte[] secret) { + enforceUid(Binder.getCallingUid()); + if (secret == null || secret.length != FRP_SECRET_SIZE) { + throw new IllegalArgumentException( + "Invalid FRP secret: " + HexFormat.of().formatHex(secret)); + } + enforceFactoryResetProtectionInactive(); + return updateFrpSecret(secret); } }; + private void enforceFactoryResetProtectionInactive() { + if (mFrpEnforced && isFrpActive()) { + throw new SecurityException("FRP is active"); + } + } + + @VisibleForTesting + boolean isUpgradingFromPreVRelease() { + PackageManagerInternal packageManagerInternal = + LocalServices.getService(PackageManagerInternal.class); + if (packageManagerInternal == null) { + Slog.e(TAG, "Unable to retrieve PackageManagerInternal"); + return false; + } + + return packageManagerInternal + .isUpgradingFromLowerThan(Build.VERSION_CODES.VANILLA_ICE_CREAM); + } + private InternalService mInternalService = new InternalService(); private class InternalService implements PersistentDataBlockManagerInternal { @@ -792,6 +1297,14 @@ public class PersistentDataBlockService extends SystemService { return mAllowedUid; } + @Override + public boolean deactivateFactoryResetProtectionWithoutSecret() { + synchronized (mLock) { + mFrpActive = false; + } + return true; + } + private void writeInternal(byte[] data, long offset, int dataLength) { checkArgument(data == null || data.length > 0, "data must be null or non-empty"); checkArgument( @@ -808,10 +1321,10 @@ public class PersistentDataBlockService extends SystemService { writeDataBuffer(offset, dataBuffer); } - private void writeDataBuffer(long offset, ByteBuffer dataBuffer) { + private boolean writeDataBuffer(long offset, ByteBuffer dataBuffer) { synchronized (mLock) { if (!mIsWritable) { - return; + return false; } try (FileChannel channel = getBlockOutputChannel()) { channel.position(offset); @@ -819,10 +1332,10 @@ public class PersistentDataBlockService extends SystemService { channel.force(true); } catch (IOException e) { Slog.e(TAG, "unable to access persistent partition", e); - return; + return false; } - computeAndWriteDigestLocked(); + return computeAndWriteDigestLocked(); } } @@ -864,5 +1377,5 @@ public class PersistentDataBlockService extends SystemService { computeAndWriteDigestLocked(); } } - }; + } } diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java new file mode 100644 index 000000000000..155361837071 --- /dev/null +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java @@ -0,0 +1,97 @@ +/* + * 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.pm; + +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; + +import android.annotation.NonNull; +import android.app.BackgroundInstallControlManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IRemoteCallback; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ServiceThread; + +public class BackgroundInstallControlCallbackHelper { + + @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName"; + @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId"; + private static final String TAG = "BackgroundInstallControlCallbackHelper"; + + private final Handler mHandler; + + BackgroundInstallControlCallbackHelper() { + HandlerThread backgroundThread = + new ServiceThread( + "BackgroundInstallControlCallbackHelperBg", + THREAD_PRIORITY_BACKGROUND, + true); + backgroundThread.start(); + mHandler = new Handler(backgroundThread.getLooper()); + } + + @NonNull @VisibleForTesting + final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>(); + + /** Registers callback that gets invoked upon detection of an MBA + * + * NOTE: The callback is user context agnostic and currently broadcasts to all users of other + * users app installs. This is fine because the API is for SystemServer use only. + */ + public void registerBackgroundInstallCallback(IRemoteCallback callback) { + synchronized (mCallbacks) { + mCallbacks.register(callback, null); + } + } + + /** Unregisters callback */ + public void unregisterBackgroundInstallCallback(IRemoteCallback callback) { + synchronized (mCallbacks) { + mCallbacks.unregister(callback); + } + } + + /** + * Invokes all registered callbacks Callbacks are processed through user provided-threads and + * parameters are passed in via {@link BackgroundInstallControlManager} InstallEvent + */ + public void notifyAllCallbacks(int userId, String packageName) { + Bundle extras = new Bundle(); + extras.putCharSequence(FLAGGED_PACKAGE_NAME_KEY, packageName); + extras.putInt(FLAGGED_USER_ID_KEY, userId); + synchronized (mCallbacks) { + mHandler.post( + () -> + mCallbacks.broadcast( + callback -> { + try { + callback.sendResult(extras); + } catch (RemoteException e) { + Slog.e( + TAG, + "error detected: " + e.getLocalizedMessage(), + e); + } + })); + } + } +} diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java index 200b17bc2f97..3468081088a3 100644 --- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java @@ -36,6 +36,7 @@ import android.os.Binder; import android.os.Build; import android.os.Environment; import android.os.Handler; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.SystemClock; @@ -93,6 +94,8 @@ public class BackgroundInstallControlService extends SystemService { private final File mDiskFile; private final Context mContext; + private final BackgroundInstallControlCallbackHelper mCallbackHelper; + private SparseSetArray<String> mBackgroundInstalledPackages = null; // User ID -> package name -> set of foreground time frame @@ -112,6 +115,7 @@ public class BackgroundInstallControlService extends SystemService { mHandler = new EventHandler(injector.getLooper(), this); mDiskFile = injector.getDiskFile(); mContext = injector.getContext(); + mCallbackHelper = injector.getBackgroundInstallControlCallbackHelper(); UsageStatsManagerInternal usageStatsManagerInternal = injector.getUsageStatsManagerInternal(); usageStatsManagerInternal.registerListener( @@ -150,6 +154,16 @@ public class BackgroundInstallControlService extends SystemService { } } + @Override + public void registerBackgroundInstallCallback(IRemoteCallback callback) { + mService.mCallbackHelper.registerBackgroundInstallCallback(callback); + } + + @Override + public void unregisterBackgroundInstallCallback(IRemoteCallback callback) { + mService.mCallbackHelper.unregisterBackgroundInstallCallback(callback); + } + } @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES) @@ -274,6 +288,7 @@ public class BackgroundInstallControlService extends SystemService { initBackgroundInstalledPackages(); mBackgroundInstalledPackages.add(userId, packageName); + mCallbackHelper.notifyAllCallbacks(userId, packageName); writeBackgroundInstalledPackagesToDisk(); } @@ -568,6 +583,8 @@ public class BackgroundInstallControlService extends SystemService { File getDiskFile(); + BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper(); + } private static final class InjectorImpl implements Injector { @@ -617,5 +634,10 @@ public class BackgroundInstallControlService extends SystemService { File file = new File(dir, DISK_FILE_NAME); return file; } + + @Override + public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() { + return new BackgroundInstallControlCallbackHelper(); + } } } diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index a9e5a5434189..1c70af0a56ea 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -594,6 +594,7 @@ public class PackageInfoUtils { } ai.applicationInfo = applicationInfo; ai.requiredDisplayCategory = a.getRequiredDisplayCategory(); + ai.requireContentUriPermissionFromCaller = a.getRequireContentUriPermissionFromCaller(); ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts()); assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, userId); return ai; diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java index 03c75e018dab..195e91cf5716 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java @@ -19,6 +19,7 @@ package com.android.server.uri; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; +import android.content.pm.ActivityInfo.RequiredContentUriPermission; import android.content.pm.ProviderInfo; import android.net.Uri; import android.os.IBinder; @@ -63,6 +64,15 @@ public interface UriGrantsManagerInternal { String targetPkg, int targetUserId); /** + * Same as {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int)}, but with an + * extra parameter {@code requireContentUriPermissionFromCaller}, which is the value from {@link + * android.R.attr#requireContentUriPermissionFromCaller} attribute. + */ + NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid, + String targetPkg, int targetUserId, + @RequiredContentUriPermission int requireContentUriPermissionFromCaller); + + /** * Extend a previously calculated set of permissions grants to the given * owner. All security checks will have already been performed as part of * calculating {@link NeededUriGrants}. diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index ce2cbed0c9a9..d2f6701e313e 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -23,6 +23,10 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; +import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_NONE; +import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_READ_OR_WRITE; +import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionRead; +import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionWrite; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; @@ -53,6 +57,8 @@ import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.RequiredContentUriPermission; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; @@ -609,7 +615,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements /** Like checkGrantUriPermission, but takes an Intent. */ private NeededUriGrants checkGrantUriPermissionFromIntentUnlocked(int callingUid, - String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) { + String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId, + @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) { if (DEBUG) Slog.v(TAG, "Checking URI perm to data=" + (intent != null ? intent.getData() : null) + " clip=" + (intent != null ? intent.getClipData() : null) @@ -647,6 +654,10 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements } if (data != null) { GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode); + if (android.security.Flags.contentUriPermissionApis()) { + enforceRequireContentUriPermissionFromCaller(requireContentUriPermissionFromCaller, + grantUri, callingUid); + } targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode, targetUid); if (targetUid > 0) { @@ -661,6 +672,10 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements Uri uri = clip.getItemAt(i).getUri(); if (uri != null) { GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode); + if (android.security.Flags.contentUriPermissionApis()) { + enforceRequireContentUriPermissionFromCaller( + requireContentUriPermissionFromCaller, grantUri, callingUid); + } targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode, targetUid); if (targetUid > 0) { @@ -673,7 +688,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements Intent clipIntent = clip.getItemAt(i).getIntent(); if (clipIntent != null) { NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentUnlocked( - callingUid, targetPkg, clipIntent, mode, needed, targetUserId); + callingUid, targetPkg, clipIntent, mode, needed, targetUserId, + requireContentUriPermissionFromCaller); if (newNeeded != null) { needed = newNeeded; } @@ -685,6 +701,38 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements return needed; } + private void enforceRequireContentUriPermissionFromCaller( + @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller, + GrantUri grantUri, int uid) { + // Ignore if requireContentUriPermissionFromCaller hasn't been set or the URI is a + // non-content URI. + if (requireContentUriPermissionFromCaller == null + || requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_NONE + || !ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) { + return; + } + + final boolean readMet = !isRequiredContentUriPermissionRead( + requireContentUriPermissionFromCaller) + || checkContentUriPermissionFullUnlocked(grantUri, uid, + Intent.FLAG_GRANT_READ_URI_PERMISSION); + + final boolean writeMet = !isRequiredContentUriPermissionWrite( + requireContentUriPermissionFromCaller) + || checkContentUriPermissionFullUnlocked(grantUri, uid, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + boolean hasPermission = + requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_READ_OR_WRITE + ? (readMet || writeMet) : (readMet && writeMet); + + if (!hasPermission) { + throw new SecurityException("You can't launch this activity because you don't have the" + + " required " + ActivityInfo.requiredContentUriPermissionToShortString( + requireContentUriPermissionFromCaller) + " access to " + grantUri.uri); + } + } + @GuardedBy("mLock") private void readGrantedUriPermissionsLocked() { if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()"); @@ -1560,9 +1608,24 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements @Override public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid, String targetPkg, int targetUserId) { + return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg, + targetUserId, /* requireContentUriPermissionFromCaller */ null); + } + + @Override + public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid, + String targetPkg, int targetUserId, int requireContentUriPermissionFromCaller) { + return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg, + targetUserId, requireContentUriPermissionFromCaller); + } + + private NeededUriGrants internalCheckGrantUriPermissionFromIntent(Intent intent, + int callingUid, String targetPkg, int targetUserId, + @Nullable Integer requireContentUriPermissionFromCaller) { final int mode = (intent != null) ? intent.getFlags() : 0; return UriGrantsManagerService.this.checkGrantUriPermissionFromIntentUnlocked( - callingUid, targetPkg, intent, mode, null, targetUserId); + callingUid, targetPkg, intent, mode, null, targetUserId, + requireContentUriPermissionFromCaller); } @Override diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java index 8f8fe3cd48f5..17a9e3330375 100644 --- a/services/core/java/com/android/server/vibrator/VibratorControlService.java +++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java @@ -248,8 +248,6 @@ public final class VibratorControlService extends IVibratorControlService.Stub { IVibratorController vibratorController = mVibratorControllerHolder.getVibratorController(); if (vibratorController == null) { - Slog.d(TAG, "Unable to check if should request vibration params. " - + "There is no registered IVibrationController."); return false; } diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java index b2bbcda862ec..88d3daf359c2 100644 --- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java +++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java @@ -32,6 +32,8 @@ import android.util.Slog; import com.android.internal.infra.ServiceConnector; +import java.io.IOException; + /** Manages the connection to the remote wearable sensing service. */ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> { private static final String TAG = @@ -56,6 +58,29 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable } /** + * Provides a secure connection to the wearable. + * + * @param secureWearableConnection The secure connection to the wearable + * @param callback The callback for service status + */ + public void provideSecureWearableConnection( + ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) { + if (DEBUG) { + Slog.i(TAG, "Providing secure wearable connection."); + } + var unused = post( + service -> { + service.provideSecureWearableConnection(secureWearableConnection, callback); + try { + // close the local fd after it has been sent to the WSS process + secureWearableConnection.close(); + } catch (IOException ex) { + Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex); + } + }); + } + + /** * Provides the implementation a data stream to the wearable. * * @param parcelFileDescriptor The data stream to the wearable @@ -66,7 +91,16 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable if (DEBUG) { Slog.i(TAG, "Providing data stream."); } - post(service -> service.provideDataStream(parcelFileDescriptor, callback)); + var unused = post( + service -> { + service.provideDataStream(parcelFileDescriptor, callback); + try { + // close the local fd after it has been sent to the WSS process + parcelFileDescriptor.close(); + } catch (IOException ex) { + Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex); + } + }); } /** @@ -84,4 +118,62 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable } post(service -> service.provideData(data, sharedMemory, callback)); } + + /** + * Registers a data request observer with WearableSensingService. + * + * @param dataType The data type to listen to. Values are defined by the application that + * implements WearableSensingService. + * @param dataRequestCallback The observer to send data requests to. + * @param dataRequestObserverId The unique ID for the data request observer. It will be used for + * unregistering the observer. + * @param packageName The package name of the app that will receive the data requests. + * @param statusCallback The callback for status of the method call. + */ + public void registerDataRequestObserver( + int dataType, + RemoteCallback dataRequestCallback, + int dataRequestObserverId, + String packageName, + RemoteCallback statusCallback) { + if (DEBUG) { + Slog.i(TAG, "Registering data request observer."); + } + var unused = + post( + service -> + service.registerDataRequestObserver( + dataType, + dataRequestCallback, + dataRequestObserverId, + packageName, + statusCallback)); + } + + /** + * Unregisters a previously registered data request observer. + * + * @param dataType The data type the observer was registered against. + * @param dataRequestObserverId The unique ID of the observer to unregister. + * @param packageName The package name of the app that will receive requests sent to the + * observer. + * @param statusCallback The callback for status of the method call. + */ + public void unregisterDataRequestObserver( + int dataType, + int dataRequestObserverId, + String packageName, + RemoteCallback statusCallback) { + if (DEBUG) { + Slog.i(TAG, "Unregistering data request observer."); + } + var unused = + post( + service -> + service.unregisterDataRequestObserver( + dataType, + dataRequestObserverId, + packageName, + statusCallback)); + } } diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java index e73fd0f400ac..0e8b82f75426 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java @@ -22,17 +22,19 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppGlobals; import android.app.ambientcontext.AmbientContextEvent; +import android.app.wearable.Flags; import android.app.wearable.WearableSensingManager; +import android.companion.CompanionDeviceManager; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Bundle; -import android.system.OsConstants; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SharedMemory; +import android.system.OsConstants; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -40,6 +42,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.infra.AbstractPerUserSystemService; +import java.io.IOException; import java.io.PrintWriter; /** @@ -55,6 +58,10 @@ final class WearableSensingManagerPerUserService extends RemoteWearableSensingService mRemoteService; private ComponentName mComponentName; + private final Object mSecureChannelLock = new Object(); + + @GuardedBy("mSecureChannelLock") + private WearableSensingSecureChannel mSecureChannel; WearableSensingManagerPerUserService( @NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) { @@ -76,6 +83,11 @@ final class WearableSensingManagerPerUserService extends mRemoteService = null; } } + synchronized (mSecureChannelLock) { + if (mSecureChannel != null) { + mSecureChannel.close(); + } + } } @GuardedBy("mLock") @@ -156,6 +168,63 @@ final class WearableSensingManagerPerUserService extends } /** + * Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing + * service. + */ + public void onProvideWearableConnection( + ParcelFileDescriptor wearableConnection, RemoteCallback callback) { + Slog.i(TAG, "onProvideWearableConnection in per user service."); + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + } + synchronized (mSecureChannelLock) { + if (mSecureChannel != null) { + // TODO(b/321012559): Kill the WearableSensingService process if it has not been + // killed from onError + mSecureChannel.close(); + } + try { + mSecureChannel = + WearableSensingSecureChannel.create( + getContext().getSystemService(CompanionDeviceManager.class), + wearableConnection, + new WearableSensingSecureChannel.SecureTransportListener() { + @Override + public void onSecureTransportAvailable( + ParcelFileDescriptor secureTransport) { + Slog.i(TAG, "calling over to remote service."); + synchronized (mLock) { + ensureRemoteServiceInitiated(); + mRemoteService.provideSecureWearableConnection( + secureTransport, callback); + } + } + + @Override + public void onError() { + // TODO(b/321012559): Kill the WearableSensingService + // process if mSecureChannel has not been reassigned + if (Flags.enableProvideWearableConnectionApi()) { + notifyStatusCallback( + callback, + WearableSensingManager.STATUS_CHANNEL_ERROR); + } + } + }); + } catch (IOException ex) { + Slog.e(TAG, "Unable to create the secure channel.", ex); + if (Flags.enableProvideWearableConnectionApi()) { + notifyStatusCallback(callback, WearableSensingManager.STATUS_CHANNEL_ERROR); + } + } + } + } + + /** * Handles sending the provided data stream for the wearable to the wearable sensing service. */ public void onProvideDataStream( @@ -193,4 +262,65 @@ final class WearableSensingManagerPerUserService extends mRemoteService.provideData(data, sharedMemory, callback); } } + + /** + * Handles registering a data request observer. + * + * @param dataType The data type to listen to. Values are defined by the application that + * implements WearableSensingService. + * @param dataRequestObserver The observer to register. + * @param dataRequestObserverId The unique ID for the data request observer. It will be used for + * unregistering the observer. + * @param packageName The package name of the app that will receive the data requests. + * @param statusCallback The callback for status of the method call. + */ + public void onRegisterDataRequestObserver( + int dataType, + RemoteCallback dataRequestObserver, + int dataRequestObserverId, + String packageName, + RemoteCallback statusCallback) { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + ensureRemoteServiceInitiated(); + mRemoteService.registerDataRequestObserver( + dataType, + dataRequestObserver, + dataRequestObserverId, + packageName, + statusCallback); + } + } + + /** + * Handles unregistering a previously registered data request observer. + * + * @param dataType The data type the observer was registered against. + * @param dataRequestObserverId The unique ID of the observer to unregister. + * @param packageName The package name of the app that will receive requests sent to the + * observer. + * @param statusCallback The callback for status of the method call. + */ + public void onUnregisterDataRequestObserver( + int dataType, + int dataRequestObserverId, + String packageName, + RemoteCallback statusCallback) { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + ensureRemoteServiceInitiated(); + mRemoteService.unregisterDataRequestObserver( + dataType, dataRequestObserverId, packageName, statusCallback); + } + } } diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java index 4cc2c025575e..78952fa4590b 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java @@ -21,12 +21,18 @@ import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING; import android.Manifest; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.BroadcastOptions; +import android.app.ComponentOptions; +import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; import android.app.wearable.IWearableSensingManager; +import android.app.wearable.WearableSensingDataRequest; import android.app.wearable.WearableSensingManager; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManagerInternal; +import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; @@ -35,6 +41,8 @@ import android.os.SharedMemory; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.service.wearable.WearableSensingDataRequester; +import android.text.TextUtils; import android.util.Slog; import com.android.internal.R; @@ -44,10 +52,13 @@ import com.android.server.SystemService; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; import com.android.server.pm.KnownPackages; +import com.android.server.utils.quota.MultiRateLimiter; import java.io.FileDescriptor; +import java.util.HashSet; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; /** @@ -64,9 +75,38 @@ public class WearableSensingManagerService extends /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_SERVICE_ENABLED = true; + public static final int MAX_TEMPORARY_SERVICE_DURATION_MS = 30000; + private static final String RATE_LIMITER_PACKAGE_NAME = "android"; + private static final String RATE_LIMITER_TAG = + WearableSensingManagerService.class.getSimpleName(); + + private static final class DataRequestObserverContext { + final int mDataType; + final int mUserId; + final int mDataRequestObserverId; + @NonNull final PendingIntent mDataRequestPendingIntent; + @NonNull final RemoteCallback mDataRequestRemoteCallback; + + DataRequestObserverContext( + int dataType, + int userId, + int dataRequestObserverId, + PendingIntent dataRequestPendingIntent, + RemoteCallback dataRequestRemoteCallback) { + mDataType = dataType; + mUserId = userId; + mDataRequestObserverId = dataRequestObserverId; + mDataRequestPendingIntent = dataRequestPendingIntent; + mDataRequestRemoteCallback = dataRequestRemoteCallback; + } + } + private final Context mContext; + private final AtomicInteger mNextDataRequestObserverId = new AtomicInteger(1); + private final Set<DataRequestObserverContext> mDataRequestObserverContexts = new HashSet<>(); + private final MultiRateLimiter mDataRequestRateLimiter; volatile boolean mIsServiceEnabled; public WearableSensingManagerService(Context context) { @@ -78,6 +118,12 @@ public class WearableSensingManagerService extends PACKAGE_UPDATE_POLICY_REFRESH_EAGER | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER); mContext = context; + mDataRequestRateLimiter = + new MultiRateLimiter.Builder(context) + .addRateLimit( + WearableSensingDataRequest.getRateLimit(), + WearableSensingDataRequest.getRateLimitWindowSize()) + .build(); } @Override @@ -192,6 +238,96 @@ public class WearableSensingManagerService extends } } + private DataRequestObserverContext getDataRequestObserverContext( + int dataType, int userId, PendingIntent dataRequestPendingIntent) { + synchronized (mDataRequestObserverContexts) { + for (DataRequestObserverContext observerContext : mDataRequestObserverContexts) { + if (observerContext.mDataType == dataType + && observerContext.mUserId == userId + && observerContext.mDataRequestPendingIntent.equals( + dataRequestPendingIntent)) { + return observerContext; + } + } + } + return null; + } + + @NonNull + private RemoteCallback createDataRequestRemoteCallback( + PendingIntent dataRequestPendingIntent, int userId) { + return new RemoteCallback( + bundle -> { + WearableSensingDataRequest dataRequest = + bundle.getParcelable( + WearableSensingDataRequest.REQUEST_BUNDLE_KEY, + WearableSensingDataRequest.class); + if (dataRequest == null) { + Slog.e(TAG, "Received data request callback without a request."); + return; + } + RemoteCallback dataRequestStatusCallback = + bundle.getParcelable( + WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY, + RemoteCallback.class); + if (dataRequestStatusCallback == null) { + Slog.e(TAG, "Received data request callback without a status callback."); + return; + } + if (dataRequest.getDataSize() + > WearableSensingDataRequest.getMaxRequestSize()) { + Slog.w( + TAG, + TextUtils.formatSimple( + "WearableSensingDataRequest size exceeds the maximum" + + " allowed size of %s bytes. Dropping the request.", + WearableSensingDataRequest.getMaxRequestSize())); + WearableSensingManagerPerUserService.notifyStatusCallback( + dataRequestStatusCallback, + WearableSensingDataRequester.STATUS_TOO_LARGE); + return; + } + if (!mDataRequestRateLimiter.isWithinQuota( + userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG)) { + Slog.w(TAG, "Data request exceeded rate limit. Dropping the request."); + WearableSensingManagerPerUserService.notifyStatusCallback( + dataRequestStatusCallback, + WearableSensingDataRequester.STATUS_TOO_FREQUENT); + return; + } + Intent intent = new Intent(); + intent.putExtra( + WearableSensingManager.EXTRA_WEARABLE_SENSING_DATA_REQUEST, + dataRequest); + BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED); + mDataRequestRateLimiter.noteEvent( + userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG); + final long previousCallingIdentity = Binder.clearCallingIdentity(); + try { + dataRequestPendingIntent.send( + getContext(), 0, intent, null, null, null, options.toBundle()); + WearableSensingManagerPerUserService.notifyStatusCallback( + dataRequestStatusCallback, + WearableSensingDataRequester.STATUS_SUCCESS); + Slog.i( + TAG, + TextUtils.formatSimple( + "Sending data request to %s: %s", + dataRequestPendingIntent.getCreatorPackage(), + dataRequest.toExpandedString())); + } catch (PendingIntent.CanceledException e) { + Slog.w(TAG, "Could not deliver pendingIntent: " + dataRequestPendingIntent); + WearableSensingManagerPerUserService.notifyStatusCallback( + dataRequestStatusCallback, + WearableSensingDataRequester.STATUS_OBSERVER_CANCELLED); + } finally { + Binder.restoreCallingIdentity(previousCallingIdentity); + } + }); + } + private void callPerUserServiceIfExist( Consumer<WearableSensingManagerPerUserService> serviceConsumer, RemoteCallback statusCallback) { @@ -211,9 +347,27 @@ public class WearableSensingManagerService extends private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub { @Override + public void provideWearableConnection( + ParcelFileDescriptor wearableConnection, RemoteCallback callback) { + Slog.i(TAG, "WearableSensingManagerInternal provideWearableConnection."); + Objects.requireNonNull(wearableConnection); + Objects.requireNonNull(callback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available."); + WearableSensingManagerPerUserService.notifyStatusCallback( + callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + callPerUserServiceIfExist( + service -> service.onProvideWearableConnection(wearableConnection, callback), + callback); + } + + @Override public void provideDataStream( - ParcelFileDescriptor parcelFileDescriptor, - RemoteCallback callback) { + ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) { Slog.i(TAG, "WearableSensingManagerInternal provideDataStream."); Objects.requireNonNull(parcelFileDescriptor); Objects.requireNonNull(callback); @@ -242,8 +396,8 @@ public class WearableSensingManagerService extends Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG); if (!mIsServiceEnabled) { Slog.w(TAG, "Service not available."); - WearableSensingManagerPerUserService.notifyStatusCallback(callback, - WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + WearableSensingManagerPerUserService.notifyStatusCallback( + callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); return; } callPerUserServiceIfExist( @@ -252,6 +406,96 @@ public class WearableSensingManagerService extends } @Override + public void registerDataRequestObserver( + int dataType, + PendingIntent dataRequestPendingIntent, + RemoteCallback statusCallback) { + Slog.i(TAG, "WearableSensingManagerInternal registerDataRequestObserver."); + Objects.requireNonNull(dataRequestPendingIntent); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available."); + WearableSensingManagerPerUserService.notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + int userId = UserHandle.getCallingUserId(); + RemoteCallback dataRequestCallback; + int dataRequestObserverId; + synchronized (mDataRequestObserverContexts) { + DataRequestObserverContext previousObserverContext = + getDataRequestObserverContext(dataType, userId, dataRequestPendingIntent); + if (previousObserverContext != null) { + Slog.i(TAG, "Received duplicate data request observer."); + dataRequestCallback = previousObserverContext.mDataRequestRemoteCallback; + dataRequestObserverId = previousObserverContext.mDataRequestObserverId; + } else { + dataRequestCallback = + createDataRequestRemoteCallback(dataRequestPendingIntent, userId); + dataRequestObserverId = mNextDataRequestObserverId.getAndIncrement(); + mDataRequestObserverContexts.add( + new DataRequestObserverContext( + dataType, + userId, + dataRequestObserverId, + dataRequestPendingIntent, + dataRequestCallback)); + } + } + callPerUserServiceIfExist( + service -> + service.onRegisterDataRequestObserver( + dataType, + dataRequestCallback, + dataRequestObserverId, + dataRequestPendingIntent.getCreatorPackage(), + statusCallback), + statusCallback); + } + + @Override + public void unregisterDataRequestObserver( + int dataType, + PendingIntent dataRequestPendingIntent, + RemoteCallback statusCallback) { + Slog.i(TAG, "WearableSensingManagerInternal unregisterDataRequestObserver."); + Objects.requireNonNull(dataRequestPendingIntent); + Objects.requireNonNull(statusCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available."); + WearableSensingManagerPerUserService.notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + int userId = UserHandle.getCallingUserId(); + int previousDataRequestObserverId; + String pendingIntentCreatorPackage; + synchronized (mDataRequestObserverContexts) { + DataRequestObserverContext previousObserverContext = + getDataRequestObserverContext(dataType, userId, dataRequestPendingIntent); + if (previousObserverContext == null) { + Slog.w(TAG, "Previous observer not found, cannot unregister."); + return; + } + mDataRequestObserverContexts.remove(previousObserverContext); + previousDataRequestObserverId = previousObserverContext.mDataRequestObserverId; + pendingIntentCreatorPackage = + previousObserverContext.mDataRequestPendingIntent.getCreatorPackage(); + } + callPerUserServiceIfExist( + service -> + service.onUnregisterDataRequestObserver( + dataType, + previousDataRequestObserverId, + pendingIntentCreatorPackage, + statusCallback), + statusCallback); + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { new WearableSensingShellCommand(WearableSensingManagerService.this).exec( diff --git a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java new file mode 100644 index 000000000000..a16ff51e2d20 --- /dev/null +++ b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java @@ -0,0 +1,350 @@ +/* + * 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.wearable; + +import android.annotation.NonNull; +import android.companion.AssociationInfo; +import android.companion.AssociationRequest; +import android.companion.CompanionDeviceManager; +import android.os.Binder; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseInputStream; +import android.os.ParcelFileDescriptor.AutoCloseOutputStream; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * A wrapper that manages a CompanionDeviceManager secure channel for wearable sensing. + * + * <p>This wrapper accepts a connection to a wearable from the caller. It then attaches the + * connection to the CompanionDeviceManager via {@link + * CompanionDeviceManager#attachSystemDataTransport(int, InputStream, OutputStream)}, which will + * create an encrypted channel using the provided connection as the raw underlying connection. The + * wearable device is expected to attach its side of the raw connection to its + * CompanionDeviceManager via the same method so that the two CompanionDeviceManagers on the two + * devices can perform attestation and set up the encrypted channel. Attestation requirements are + * listed in {@link com.android.server.security.AttestationVerificationPeerDeviceVerifier}. + * + * <p>When the encrypted channel is available, it will be provided to the caller via the + * SecureTransportListener. + */ +final class WearableSensingSecureChannel { + + /** A listener for secure transport and its error signal. */ + interface SecureTransportListener { + + /** Called when the secure transport is available. */ + void onSecureTransportAvailable(ParcelFileDescriptor secureTransport); + + /** + * Called when there is a non-recoverable error. The secure channel will be automatically + * closed. + */ + void onError(); + } + + private static final String TAG = WearableSensingSecureChannel.class.getSimpleName(); + private static final String CDM_ASSOCIATION_DISPLAY_NAME = "PlaceholderDisplayNameFromWSM"; + // The batch size of reading from the ParcelFileDescriptor returned to mSecureTransportListener + private static final int READ_BUFFER_SIZE = 8192; + + private final Object mLock = new Object(); + // CompanionDeviceManager (CDM) can continue to call these ExecutorServices even after the + // corresponding cleanup methods in CDM have been called (e.g. + // removeOnTransportsChangedListener). Since we shut down these ExecutorServices after + // clean up, we use SoftShutdownExecutor to suppress RejectedExecutionExceptions. + private final SoftShutdownExecutor mMessageFromWearableExecutor = + new SoftShutdownExecutor(Executors.newSingleThreadExecutor()); + private final SoftShutdownExecutor mMessageToWearableExecutor = + new SoftShutdownExecutor(Executors.newSingleThreadExecutor()); + private final SoftShutdownExecutor mLightWeightExecutor = + new SoftShutdownExecutor(Executors.newSingleThreadExecutor()); + private final CompanionDeviceManager mCompanionDeviceManager; + private final ParcelFileDescriptor mUnderlyingTransport; + private final SecureTransportListener mSecureTransportListener; + private final AtomicBoolean mTransportAvailable = new AtomicBoolean(false); + private final Consumer<List<AssociationInfo>> mOnTransportsChangedListener = + this::onTransportsChanged; + private final BiConsumer<Integer, byte[]> mOnMessageReceivedListener = this::onMessageReceived; + private final ParcelFileDescriptor mRemoteFd; // To be returned to mSecureTransportListener + // read input received from the ParcelFileDescriptor returned to mSecureTransportListener + private final InputStream mLocalIn; + // send output to the ParcelFileDescriptor returned to mSecureTransportListener + private final OutputStream mLocalOut; + + @GuardedBy("mLock") + private boolean mClosed = false; + + private Integer mAssociationId = null; + + /** + * Creates a WearableSensingSecureChannel. When the secure transport is ready, + * secureTransportListener will be notified. + * + * @param companionDeviceManager The CompanionDeviceManager system service. + * @param underlyingTransport The underlying transport to create the secure channel on. + * @param secureTransportListener The listener to receive the secure transport when it is ready. + * @throws IOException if it cannot create a {@link ParcelFileDescriptor} socket pair. + */ + static WearableSensingSecureChannel create( + @NonNull CompanionDeviceManager companionDeviceManager, + @NonNull ParcelFileDescriptor underlyingTransport, + @NonNull SecureTransportListener secureTransportListener) + throws IOException { + Objects.requireNonNull(companionDeviceManager); + Objects.requireNonNull(underlyingTransport); + Objects.requireNonNull(secureTransportListener); + ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(); + WearableSensingSecureChannel channel = + new WearableSensingSecureChannel( + companionDeviceManager, + underlyingTransport, + secureTransportListener, + pair[0], + pair[1]); + channel.initialize(); + return channel; + } + + private WearableSensingSecureChannel( + CompanionDeviceManager companionDeviceManager, + ParcelFileDescriptor underlyingTransport, + SecureTransportListener secureTransportListener, + ParcelFileDescriptor remoteFd, + ParcelFileDescriptor localFd) { + mCompanionDeviceManager = companionDeviceManager; + mUnderlyingTransport = underlyingTransport; + mSecureTransportListener = secureTransportListener; + mRemoteFd = remoteFd; + mLocalIn = new AutoCloseInputStream(localFd); + mLocalOut = new AutoCloseOutputStream(localFd); + } + + private void initialize() { + final long originalCallingIdentity = Binder.clearCallingIdentity(); + try { + Slog.d(TAG, "Requesting CDM association."); + mCompanionDeviceManager.associate( + new AssociationRequest.Builder() + .setDisplayName(CDM_ASSOCIATION_DISPLAY_NAME) + .setSelfManaged(true) + .build(), + mLightWeightExecutor, + new CompanionDeviceManager.Callback() { + @Override + public void onAssociationCreated(AssociationInfo associationInfo) { + WearableSensingSecureChannel.this.onAssociationCreated( + associationInfo.getId()); + } + + @Override + public void onFailure(CharSequence error) { + Slog.e( + TAG, + "Failed to create CompanionDeviceManager association: " + + error); + onError(); + } + }); + } finally { + Binder.restoreCallingIdentity(originalCallingIdentity); + } + } + + private void onAssociationCreated(int associationId) { + Slog.i(TAG, "CDM association created."); + synchronized (mLock) { + if (mClosed) { + return; + } + mAssociationId = associationId; + mCompanionDeviceManager.addOnMessageReceivedListener( + mMessageFromWearableExecutor, + CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE, + mOnMessageReceivedListener); + mCompanionDeviceManager.addOnTransportsChangedListener( + mLightWeightExecutor, mOnTransportsChangedListener); + mCompanionDeviceManager.attachSystemDataTransport( + associationId, + new AutoCloseInputStream(mUnderlyingTransport), + new AutoCloseOutputStream(mUnderlyingTransport)); + } + } + + private void onTransportsChanged(List<AssociationInfo> associationInfos) { + synchronized (mLock) { + if (mClosed) { + return; + } + if (mAssociationId == null) { + Slog.e(TAG, "mAssociationId is null when transport changed"); + return; + } + } + // Do not call onTransportAvailable() or onError() when holding the lock because it can + // cause a deadlock if the callback holds another lock. + boolean transportAvailable = + associationInfos.stream().anyMatch(info -> info.getId() == mAssociationId); + if (transportAvailable && mTransportAvailable.compareAndSet(false, true)) { + onTransportAvailable(); + } else if (!transportAvailable && mTransportAvailable.compareAndSet(true, false)) { + Slog.i(TAG, "CDM transport is detached. This is not recoverable."); + onError(); + } + } + + private void onTransportAvailable() { + // Start sending data received from the remote stream to the wearable. + Slog.i(TAG, "Transport available"); + mMessageToWearableExecutor.execute( + () -> { + int[] associationIdsToSendMessageTo = new int[] {mAssociationId}; + byte[] buffer = new byte[READ_BUFFER_SIZE]; + int readLen; + try { + while ((readLen = mLocalIn.read(buffer)) != -1) { + byte[] data = new byte[readLen]; + System.arraycopy(buffer, 0, data, 0, readLen); + Slog.v(TAG, "Sending message to wearable"); + mCompanionDeviceManager.sendMessage( + CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE, + data, + associationIdsToSendMessageTo); + } + } catch (IOException e) { + Slog.i(TAG, "IOException while reading from remote stream."); + onError(); + return; + } + Slog.i( + TAG, + "Reached EOF when reading from remote stream. Reporting this as an" + + " error."); + onError(); + }); + mSecureTransportListener.onSecureTransportAvailable(mRemoteFd); + } + + private void onMessageReceived(int associationIdForMessage, byte[] data) { + if (associationIdForMessage == mAssociationId) { + Slog.v(TAG, "Received message from wearable."); + try { + mLocalOut.write(data); + mLocalOut.flush(); + } catch (IOException e) { + Slog.i( + TAG, + "IOException when writing to remote stream. Closing the secure channel."); + onError(); + } + } else { + Slog.v( + TAG, + "Received CDM message of type MESSAGE_ONEWAY_FROM_WEARABLE, but it is for" + + " another association. Ignoring the message."); + } + } + + private void onError() { + synchronized (mLock) { + if (mClosed) { + return; + } + } + mSecureTransportListener.onError(); + close(); + } + + /** Closes this secure channel and releases all resources. */ + void close() { + synchronized (mLock) { + if (mClosed) { + return; + } + Slog.i(TAG, "Closing WearableSensingSecureChannel."); + mClosed = true; + if (mAssociationId != null) { + final long originalCallingIdentity = Binder.clearCallingIdentity(); + try { + mCompanionDeviceManager.removeOnTransportsChangedListener( + mOnTransportsChangedListener); + mCompanionDeviceManager.removeOnMessageReceivedListener( + CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE, + mOnMessageReceivedListener); + mCompanionDeviceManager.detachSystemDataTransport(mAssociationId); + mCompanionDeviceManager.disassociate(mAssociationId); + } finally { + Binder.restoreCallingIdentity(originalCallingIdentity); + } + } + try { + mLocalIn.close(); + } catch (IOException ex) { + Slog.e(TAG, "Encountered IOException when closing local input stream.", ex); + } + try { + mLocalOut.close(); + } catch (IOException ex) { + Slog.e(TAG, "Encountered IOException when closing local output stream.", ex); + } + mMessageFromWearableExecutor.shutdown(); + mMessageToWearableExecutor.shutdown(); + mLightWeightExecutor.shutdown(); + } + } + + /** + * An executor that can be shutdown. Unlike an ExecutorService, it will not throw a + * RejectedExecutionException if {@link #execute(Runnable)} is called after shutdown. + */ + private static class SoftShutdownExecutor implements Executor { + + private final ExecutorService mExecutorService; + + SoftShutdownExecutor(ExecutorService executorService) { + mExecutorService = executorService; + } + + @Override + public void execute(Runnable runnable) { + try { + mExecutorService.execute(runnable); + } catch (RejectedExecutionException ex) { + Slog.d(TAG, "Received new runnable after shutdown. Ignoring."); + } + } + + /** Shutdown the underlying ExecutorService. */ + void shutdown() { + mExecutorService.shutdown(); + } + } +} diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 19ea9f907306..ee865d3a588a 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1610,7 +1610,7 @@ final class AccessibilityController { Slog.i(LOG_TAG, "computeChangedWindows()"); } - final List<WindowInfo> windows = new ArrayList<>(); + final List<WindowInfo> windows; final List<AccessibilityWindow> visibleWindows = new ArrayList<>(); final int topFocusedDisplayId; IBinder topFocusedWindowToken = null; @@ -1640,69 +1640,11 @@ final class AccessibilityController { } final Display display = dc.getDisplay(); display.getRealSize(mTempPoint); - final int screenWidth = mTempPoint.x; - final int screenHeight = mTempPoint.y; - - Region unaccountedSpace = mTempRegion; - unaccountedSpace.set(0, 0, screenWidth, screenHeight); mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked( mDisplayId, visibleWindows); - Set<IBinder> addedWindows = mTempBinderSet; - addedWindows.clear(); - - boolean focusedWindowAdded = false; - final int visibleWindowCount = visibleWindows.size(); - - // Iterate until we figure out what is touchable for the entire screen. - for (int i = 0; i < visibleWindowCount; i++) { - final AccessibilityWindow a11yWindow = visibleWindows.get(i); - final Region regionInWindow = new Region(); - a11yWindow.getTouchableRegionInWindow(regionInWindow); - if (windowMattersToAccessibility(a11yWindow, regionInWindow, - unaccountedSpace)) { - addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows); - if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) { - updateUnaccountedSpace(a11yWindow, unaccountedSpace); - } - focusedWindowAdded |= a11yWindow.isFocused(); - } else if (a11yWindow.isUntouchableNavigationBar()) { - // If this widow is navigation bar without touchable region, accounting the - // region of navigation bar inset because all touch events from this region - // would be received by launcher, i.e. this region is a un-touchable one - // for the application. - unaccountedSpace.op( - getSystemBarInsetsFrame( - mService.mWindowMap.get(a11yWindow.getWindowInfo().token)), - unaccountedSpace, - Region.Op.REVERSE_DIFFERENCE); - } - - if (unaccountedSpace.isEmpty() && focusedWindowAdded) { - break; - } - } - - // Remove child/parent references to windows that were not added. - final int windowCount = windows.size(); - for (int i = 0; i < windowCount; i++) { - WindowInfo window = windows.get(i); - if (!addedWindows.contains(window.parentToken)) { - window.parentToken = null; - } - if (window.childTokens != null) { - final int childTokenCount = window.childTokens.size(); - for (int j = childTokenCount - 1; j >= 0; j--) { - if (!addedWindows.contains(window.childTokens.get(j))) { - window.childTokens.remove(j); - } - } - // Leave the child token list if empty. - } - } - - addedWindows.clear(); + windows = buildWindowInfoListLocked(visibleWindows, mTempPoint); // Gets the top focused display Id and window token for supporting multi-display. topFocusedDisplayId = mService.mRoot.getTopFocusedDisplayContent().getDisplayId(); @@ -1718,6 +1660,74 @@ final class AccessibilityController { mInitialized = true; } + /** + * From a list of windows, decides windows to be exposed to accessibility based on touchable + * region in the screen. + */ + private List<WindowInfo> buildWindowInfoListLocked(List<AccessibilityWindow> visibleWindows, + Point screenSize) { + final List<WindowInfo> windows = new ArrayList<>(); + final Set<IBinder> addedWindows = mTempBinderSet; + addedWindows.clear(); + + boolean focusedWindowAdded = false; + + final int visibleWindowCount = visibleWindows.size(); + + Region unaccountedSpace = mTempRegion; + unaccountedSpace.set(0, 0, screenSize.x, screenSize.y); + + // Iterate until we figure out what is touchable for the entire screen. + for (int i = 0; i < visibleWindowCount; i++) { + final AccessibilityWindow a11yWindow = visibleWindows.get(i); + final Region regionInWindow = new Region(); + a11yWindow.getTouchableRegionInWindow(regionInWindow); + if (windowMattersToAccessibility(a11yWindow, regionInWindow, unaccountedSpace)) { + addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows); + if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) { + updateUnaccountedSpace(a11yWindow, unaccountedSpace); + } + focusedWindowAdded |= a11yWindow.isFocused(); + } else if (a11yWindow.isUntouchableNavigationBar()) { + // If this widow is navigation bar without touchable region, accounting the + // region of navigation bar inset because all touch events from this region + // would be received by launcher, i.e. this region is a un-touchable one + // for the application. + unaccountedSpace.op( + getSystemBarInsetsFrame( + mService.mWindowMap.get(a11yWindow.getWindowInfo().token)), + unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + } + + if (unaccountedSpace.isEmpty() && focusedWindowAdded) { + break; + } + } + + // Remove child/parent references to windows that were not added. + final int windowCount = windows.size(); + for (int i = 0; i < windowCount; i++) { + WindowInfo window = windows.get(i); + if (!addedWindows.contains(window.parentToken)) { + window.parentToken = null; + } + if (window.childTokens != null) { + final int childTokenCount = window.childTokens.size(); + for (int j = childTokenCount - 1; j >= 0; j--) { + if (!addedWindows.contains(window.childTokens.get(j))) { + window.childTokens.remove(j); + } + } + // Leave the child token list if empty. + } + } + + addedWindows.clear(); + + return windows; + } + // Some windows should be excluded from unaccounted space computation, though they still // should be reported private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 85580ac6810a..d99000efeeb4 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -591,9 +591,18 @@ class ActivityStarter { // Carefully collect grants without holding lock if (activityInfo != null) { - intentGrants = supervisor.mService.mUgmInternal.checkGrantUriPermissionFromIntent( - intent, resolvedCallingUid, activityInfo.applicationInfo.packageName, - UserHandle.getUserId(activityInfo.applicationInfo.uid)); + if (android.security.Flags.contentUriPermissionApis()) { + intentGrants = supervisor.mService.mUgmInternal + .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid, + activityInfo.applicationInfo.packageName, + UserHandle.getUserId(activityInfo.applicationInfo.uid), + activityInfo.requireContentUriPermissionFromCaller); + } else { + intentGrants = supervisor.mService.mUgmInternal + .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid, + activityInfo.applicationInfo.packageName, + UserHandle.getUserId(activityInfo.applicationInfo.uid)); + } } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0def5a1861ea..877336604364 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -276,6 +276,7 @@ import com.android.server.am.PendingIntentController; import com.android.server.am.PendingIntentRecord; import com.android.server.am.UserState; import com.android.server.firewall.IntentFirewall; +import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal; import com.android.server.pm.UserManagerService; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.sdksandbox.SdkSandboxManagerLocal; @@ -317,7 +318,6 @@ import java.util.Set; * {@hide} */ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { - private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender"; private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM; static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK; static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; @@ -381,6 +381,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private PowerManagerInternal mPowerManagerInternal; private UsageStatsManagerInternal mUsageStatsInternal; + GrammaticalInflectionManagerInternal mGrammaticalManagerInternal; PendingIntentController mPendingIntentController; IntentFirewall mIntentFirewall; @@ -881,6 +882,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mActivityClientController.onSystemReady(); // TODO(b/258792202) Cleanup once ASM is ready to launch ActivitySecurityModelFeatureFlags.initialize(mContext.getMainExecutor(), pm); + mGrammaticalManagerInternal = LocalServices.getService( + GrammaticalInflectionManagerInternal.class); } } @@ -938,13 +941,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { configuration.setLayoutDirection(configuration.locale); } - // Retrieve the grammatical gender from system property, set it into configuration which - // will get updated later if the grammatical gender raw value of current configuration is - // {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}. - if (configuration.getGrammaticalGenderRaw() == Configuration.GRAMMATICAL_GENDER_UNDEFINED) { - configuration.setGrammaticalGender(SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY, - Configuration.GRAMMATICAL_GENDER_UNDEFINED)); - } + configuration.setGrammaticalGender( + mGrammaticalManagerInternal.retrieveSystemGrammaticalGender(configuration)); synchronized (mGlobalLock) { mForceResizableActivities = forceResizable; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 25646f19c461..609ad1e76370 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -83,6 +83,7 @@ import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE; import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION; import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING; +import static com.android.systemui.shared.Flags.enableHomeDelay; import static java.lang.Integer.MAX_VALUE; @@ -1444,6 +1445,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> aInfo = info.first; homeIntent = info.second; } + if (aInfo == null || homeIntent == null) { return false; } @@ -1452,6 +1454,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return false; } + if (enableHomeDelay() && !mService.mAmInternal.getThemeOverlayReadiness()) { + Slog.d(TAG, "ThemeHomeDelay: Home launch was deferred."); + return false; + } + // Updates the home component of the intent. homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 6d2e8cc29506..6acf1f3f84af 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -28,6 +28,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ProcessList.INVALID_ADJ; +import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission; import static com.android.server.wm.ActivityRecord.State.DESTROYED; import static com.android.server.wm.ActivityRecord.State.DESTROYING; import static com.android.server.wm.ActivityRecord.State.PAUSED; @@ -298,6 +299,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio */ private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; + private boolean mCanUseSystemGrammaticalGender; + public WindowProcessController(@NonNull ActivityTaskManagerService atm, @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner, @NonNull WindowProcessListener listener) { @@ -319,6 +322,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mIsActivityConfigOverrideAllowed = false; } + mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null + && mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid, + mInfo.packageName); onConfigurationChanged(atm.getGlobalConfiguration()); mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mInfo.packageName); } @@ -1568,6 +1574,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return; } + if (mCanUseSystemGrammaticalGender) { + config.setGrammaticalGender( + mAtm.mGrammaticalManagerInternal.getSystemGrammaticalGender(mUserId)); + } + if (mPauseConfigurationDispatchCount > 0) { mHasPendingConfigurationChange = true; return; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 810090adbf8e..cbbcd965f4af 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -238,7 +238,7 @@ static SpriteIcon toSpriteIcon(PointerIcon pointerIcon) { // to the underlying bitmap, so even if the java object is released, we will still have access // to it. return SpriteIcon(pointerIcon.bitmap, pointerIcon.style, pointerIcon.hotSpotX, - pointerIcon.hotSpotY); + pointerIcon.hotSpotY, pointerIcon.drawNativeDropShadow); } enum { @@ -1760,13 +1760,14 @@ void NativeInputManager::loadAdditionalMouseResources( animationData.durationPerFrame = milliseconds_to_nanoseconds(pointerIcon.durationPerFrame); animationData.animationFrames.reserve(numFrames); - animationData.animationFrames.push_back(SpriteIcon( - pointerIcon.bitmap, pointerIcon.style, - pointerIcon.hotSpotX, pointerIcon.hotSpotY)); + animationData.animationFrames.emplace_back(pointerIcon.bitmap, pointerIcon.style, + pointerIcon.hotSpotX, pointerIcon.hotSpotY, + pointerIcon.drawNativeDropShadow); for (size_t i = 0; i < numFrames - 1; ++i) { - animationData.animationFrames.push_back(SpriteIcon( - pointerIcon.bitmapFrames[i], pointerIcon.style, - pointerIcon.hotSpotX, pointerIcon.hotSpotY)); + animationData.animationFrames.emplace_back(pointerIcon.bitmapFrames[i], + pointerIcon.style, pointerIcon.hotSpotX, + pointerIcon.hotSpotY, + pointerIcon.drawNativeDropShadow); } } } @@ -2610,7 +2611,7 @@ static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject ico icon = std::make_unique<SpriteIcon>(pointerIcon.bitmap.copy( ANDROID_BITMAP_FORMAT_RGBA_8888), pointerIcon.style, pointerIcon.hotSpotX, - pointerIcon.hotSpotY); + pointerIcon.hotSpotY, pointerIcon.drawNativeDropShadow); } else { icon = pointerIcon.style; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 781ff0dfd439..f87fd8dd0da4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -34,6 +34,7 @@ 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_CERTIFICATES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DISPLAY; @@ -110,6 +111,8 @@ import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEV import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED; +import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED; +import static android.app.admin.DevicePolicyManager.ContentProtectionPolicy; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL; import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL; @@ -6013,10 +6016,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Make sure the caller has any active admin with the right policy or // the required permission. if (isUnicornFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin( + admin = enforcePermissionsAndGetEnforcingAdmin( /* admin= */ null, - /* permission= */ MANAGE_DEVICE_POLICY_LOCK, - USES_POLICY_FORCE_LOCK, + /* permissions= */ new String[]{MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE}, + /* deviceAdminPolicy= */ USES_POLICY_FORCE_LOCK, caller.getPackageName(), getAffectedUser(parent) ).getActiveAdmin(); @@ -23173,6 +23176,90 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private EnforcingAdmin enforceCanCallContentProtectionLocked( + ComponentName who, String callerPackageName) { + CallerIdentity caller = getCallerIdentity(who, callerPackageName); + final int userId = caller.getUserId(); + + EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + who, + MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, + caller.getPackageName(), + userId + ); + if ((isDeviceOwner(caller) || isProfileOwner(caller)) + && !canDPCManagedUserUseLockTaskLocked(userId)) { + throw new SecurityException( + "User " + userId + " is not allowed to use content protection"); + } + return enforcingAdmin; + } + + private void enforceCanQueryContentProtectionLocked( + ComponentName who, String callerPackageName) { + CallerIdentity caller = getCallerIdentity(who, callerPackageName); + final int userId = caller.getUserId(); + + enforceCanQuery(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, caller.getPackageName(), userId); + if ((isDeviceOwner(caller) || isProfileOwner(caller)) + && !canDPCManagedUserUseLockTaskLocked(userId)) { + throw new SecurityException( + "User " + userId + " is not allowed to use content protection"); + } + } + + @Override + public void setContentProtectionPolicy( + ComponentName who, String callerPackageName, @ContentProtectionPolicy int policy) + throws SecurityException { + if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) { + return; + } + + CallerIdentity caller = getCallerIdentity(who, callerPackageName); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_CONTENT_PROTECTION_POLICY); + + EnforcingAdmin enforcingAdmin; + synchronized (getLockObject()) { + enforcingAdmin = enforceCanCallContentProtectionLocked(who, caller.getPackageName()); + } + + if (policy == CONTENT_PROTECTION_DISABLED) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.CONTENT_PROTECTION, + enforcingAdmin, + caller.getUserId()); + } else { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.CONTENT_PROTECTION, + enforcingAdmin, + new IntegerPolicyValue(policy), + caller.getUserId()); + } + } + + @Override + public @ContentProtectionPolicy int getContentProtectionPolicy( + ComponentName who, String callerPackageName) { + if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) { + return CONTENT_PROTECTION_DISABLED; + } + + CallerIdentity caller = getCallerIdentity(who, callerPackageName); + final int userHandle = caller.getUserId(); + + synchronized (getLockObject()) { + enforceCanQueryContentProtectionLocked(who, caller.getPackageName()); + } + Integer policy = mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.CONTENT_PROTECTION, userHandle); + if (policy == null) { + return CONTENT_PROTECTION_DISABLED; + } else { + return policy; + } + } + @Override public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() { synchronized (getLockObject()) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 0fc8c5e7a46a..27f183445cfa 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -341,6 +341,13 @@ final class PolicyDefinition<V> { PolicyEnforcerCallbacks.setUsbDataSignalingEnabled(value, context), new BooleanPolicySerializer()); + static PolicyDefinition<Integer> CONTENT_PROTECTION = new PolicyDefinition<>( + new NoArgsPolicyKey(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY), + new MostRecent<>(), + POLICY_FLAG_LOCAL_ONLY_POLICY, + (Integer value, Context context, Integer userId, PolicyKey policyKey) -> true, + new IntegerPolicySerializer()); + private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>(); private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>(); @@ -374,6 +381,8 @@ final class PolicyDefinition<V> { PERSONAL_APPS_SUSPENDED); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USB_DATA_SIGNALING_POLICY, USB_DATA_SIGNALING); + POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY, + CONTENT_PROTECTION); // User Restriction Policies USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0); diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig new file mode 100644 index 000000000000..4b578afddad2 --- /dev/null +++ b/services/java/com/android/server/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.server" + +flag { + namespace: "system_performance" + name: "telemetry_apis_service" + description: "Control service portion of telemetry APIs feature." + is_fixed_read_only: true + bug: "324153471" +} diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp index d479e52f92d8..682ed91c22dd 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp +++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp @@ -32,6 +32,7 @@ java_test_host { ":BackgroundInstallControlServiceTestApp", ":BackgroundInstallControlMockApp1", ":BackgroundInstallControlMockApp2", + ":BackgroundInstallControlMockApp3", ], test_suites: [ "general-tests", diff --git a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml index 1e7a78aa6f93..a352851d1297 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml +++ b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml @@ -34,6 +34,9 @@ <option name="push-file" key="BackgroundInstallControlMockApp2.apk" value="/data/local/tmp/BackgroundInstallControlMockApp2.apk" /> + <option name="push-file" + key="BackgroundInstallControlMockApp3.apk" + value="/data/local/tmp/BackgroundInstallControlMockApp3.apk" /> </target_preparer> <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java index c99e7129853b..5092a4659eb9 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java +++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java @@ -68,6 +68,20 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNull(); } + @Test + public void testRegisterCallback() throws Exception { + runDeviceTest( + "BackgroundInstallControlServiceTest", + "testRegisterBackgroundInstallControlCallback"); + } + + @Test + public void testUnregisterCallback() throws Exception { + runDeviceTest( + "BackgroundInstallControlServiceTest", + "testUnregisterBackgroundInstallControlCallback"); + } + private void installPackage(String path) throws DeviceNotAvailableException { String cmd = "pm install -t --force-queryable " + path; CommandResult result = getDevice().executeShellV2Command(cmd); diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java index b23f59106881..ac041f492135 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java +++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java @@ -16,38 +16,59 @@ package com.android.server.pm.test.app; -import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES; - import static com.android.compatibility.common.util.SystemUtil.runShellCommand; import static com.google.common.truth.Truth.assertThat; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.IBackgroundInstallControlService; import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.os.Bundle; +import android.os.IRemoteCallback; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.Pair; +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.ShellIdentityUtils; +import com.android.compatibility.common.util.ThrowingRunnable; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.FileInputStream; +import java.io.OutputStream; +import java.util.ArrayList; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.stream.Collectors; @RunWith(AndroidJUnit4.class) public class BackgroundInstallControlServiceTest { private static final String TAG = "BackgroundInstallControlServiceTest"; + private static final String ACTION_INSTALL_COMMIT = + "com.android.server.pm.test.app.BackgroundInstallControlServiceTest" + + ".ACTION_INSTALL_COMMIT"; private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3"; + private static final String TEST_DATA_DIR = "/data/local/tmp/"; + + private static final String MOCK_APK_FILE = "BackgroundInstallControlMockApp3.apk"; private IBackgroundInstallControlService mIBics; @Before @@ -74,10 +95,9 @@ public class BackgroundInstallControlServiceTest { PackageManager.MATCH_ALL, Process.myUserHandle() .getIdentifier()); } catch (RemoteException e) { - throw new RuntimeException(e); + throw e.rethrowFromSystemServer(); } - }, - GET_BACKGROUND_INSTALLED_PACKAGES); + }); assertThat(slice).isNotNull(); var packageList = slice.getList(); @@ -94,4 +114,150 @@ public class BackgroundInstallControlServiceTest { .collect(Collectors.toSet()); assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames); } + + @Test + public void testRegisterBackgroundInstallControlCallback() + throws Exception { + String testPackageName = "test"; + int testUserId = 1; + ArrayList<Pair<String, Integer>> sharedResource = new ArrayList<>(); + IRemoteCallback testCallback = + new IRemoteCallback.Stub() { + private final ArrayList<Pair<String, Integer>> mArray = sharedResource; + + @Override + public void sendResult(Bundle data) throws RemoteException { + mArray.add(new Pair(testPackageName, testUserId)); + } + }; + ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn( + mIBics, + (bics) -> { + try { + bics.registerBackgroundInstallCallback(testCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + }); + installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME); + + assertUntil(() -> sharedResource.size() == 1, 2000); + assertThat(sharedResource.get(0).first).isEqualTo(testPackageName); + assertThat(sharedResource.get(0).second).isEqualTo(testUserId); + } + + @Test + public void testUnregisterBackgroundInstallControlCallback() { + String testValue = "test"; + ArrayList<String> sharedResource = new ArrayList<>(); + IRemoteCallback testCallback = + new IRemoteCallback.Stub() { + private final ArrayList<String> mArray = sharedResource; + + @Override + public void sendResult(Bundle data) throws RemoteException { + mArray.add(testValue); + } + }; + ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn( + mIBics, + (bics) -> { + try { + bics.registerBackgroundInstallCallback(testCallback); + bics.unregisterBackgroundInstallCallback(testCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + }); + installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME); + + assertUntil(sharedResource::isEmpty, 2000); + } + + private static boolean installPackage(String apkPath, String packageName) { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + final CountDownLatch installLatch = new CountDownLatch(1); + final BroadcastReceiver installReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + int packageInstallStatus = + intent.getIntExtra( + PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE_INVALID); + if (packageInstallStatus == PackageInstaller.STATUS_SUCCESS) { + installLatch.countDown(); + } + } + }; + final IntentFilter intentFilter = new IntentFilter(ACTION_INSTALL_COMMIT); + context.registerReceiver(installReceiver, intentFilter, Context.RECEIVER_EXPORTED); + + PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); + PackageInstaller.SessionParams params = + new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED); + try { + int sessionId = packageInstaller.createSession(params); + PackageInstaller.Session session = packageInstaller.openSession(sessionId); + OutputStream out = session.openWrite(packageName, 0, -1); + FileInputStream fis = new FileInputStream(apkPath); + byte[] buffer = new byte[65536]; + int size; + while ((size = fis.read(buffer)) != -1) { + out.write(buffer, 0, size); + } + session.fsync(out); + fis.close(); + out.close(); + + runWithShellPermissionIdentity( + () -> { + session.commit(createPendingIntent(context).getIntentSender()); + installLatch.await(5, TimeUnit.SECONDS); + }); + return true; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static PendingIntent createPendingIntent(Context context) { + PendingIntent pendingIntent = + PendingIntent.getBroadcast( + context, + 1, + new Intent(ACTION_INSTALL_COMMIT) + .setPackage( + BackgroundInstallControlServiceTest.class.getPackageName()), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE); + return pendingIntent; + } + + private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable command) + throws Exception { + InstrumentationRegistry.getInstrumentation() + .getUiAutomation() + .adoptShellPermissionIdentity(); + try { + command.run(); + } finally { + InstrumentationRegistry.getInstrumentation() + .getUiAutomation() + .dropShellPermissionIdentity(); + } + } + + private static void assertUntil(Supplier<Boolean> condition, int timeoutMs) { + long endTime = System.currentTimeMillis() + timeoutMs; + while (System.currentTimeMillis() <= endTime) { + if (condition.get()) return; + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + assertThat(condition.get()).isTrue(); + } }
\ No newline at end of file diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp index 7804f4ce9d02..39b0ff782b72 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp +++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp @@ -50,3 +50,11 @@ android_test_helper_app { "--rename-manifest-package com.android.servicestests.apps.bicmockapp2", ], } + +android_test_helper_app { + name: "BackgroundInstallControlMockApp3", + defaults: ["bic-mock-app-defaults"], + aaptflags: [ + "--rename-manifest-package com.android.servicestests.apps.bicmockapp3", + ], +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt index 2c8b1cd884f1..349b83167793 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt @@ -54,7 +54,8 @@ class ParsedActivityTest : ParsedMainComponentTest( ParsedActivity::getTheme, ParsedActivity::getUiOptions, ParsedActivity::isSupportsSizeChanges, - ParsedActivity::getRequiredDisplayCategory + ParsedActivity::getRequiredDisplayCategory, + ParsedActivity::getRequireContentUriPermissionFromCaller ) override fun mainComponentSubclassExtraParams() = listOf( diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java new file mode 100644 index 000000000000..574f3699edb8 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java @@ -0,0 +1,95 @@ +/* + * 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.pm; + +import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY; +import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_USER_ID_KEY; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.os.Bundle; +import android.os.IRemoteCallback; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link BackgroundInstallControlCallbackHelper} */ +@Presubmit +@RunWith(JUnit4.class) +public class BackgroundInstallControlCallbackHelperTest { + + private final IRemoteCallback mCallback = + spy( + new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle extras) {} + }); + + private BackgroundInstallControlCallbackHelper mCallbackHelper; + + @Before + public void setup() { + mCallbackHelper = new BackgroundInstallControlCallbackHelper(); + } + + @Test + public void registerBackgroundInstallControlCallback_registers_successfully() { + mCallbackHelper.registerBackgroundInstallCallback(mCallback); + + synchronized (mCallbackHelper.mCallbacks) { + assertEquals(1, mCallbackHelper.mCallbacks.getRegisteredCallbackCount()); + assertEquals(mCallback, mCallbackHelper.mCallbacks.getRegisteredCallbackItem(0)); + } + } + + @Test + public void unregisterBackgroundInstallControlCallback_unregisters_successfully() { + synchronized (mCallbackHelper.mCallbacks) { + mCallbackHelper.mCallbacks.register(mCallback); + } + + mCallbackHelper.unregisterBackgroundInstallCallback(mCallback); + + synchronized (mCallbackHelper.mCallbacks) { + assertEquals(0, mCallbackHelper.mCallbacks.getRegisteredCallbackCount()); + } + } + + @Test + public void notifyAllCallbacks_broadcastsToCallbacks() + throws RemoteException { + String testPackageName = "testname"; + int testUserId = 1; + mCallbackHelper.registerBackgroundInstallCallback(mCallback); + + mCallbackHelper.notifyAllCallbacks(testUserId, testPackageName); + + ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mCallback, after(1000).times(1)).sendResult(bundleCaptor.capture()); + Bundle receivedBundle = bundleCaptor.getValue(); + assertEquals(testPackageName, receivedBundle.getString(FLAGGED_PACKAGE_NAME_KEY)); + assertEquals(testUserId, receivedBundle.getInt(FLAGGED_USER_ID_KEY)); + } +} diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index ad6e2c657c96..3743483377b5 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -154,6 +154,7 @@ android_ravenwood_test { "androidx.annotation_annotation", "androidx.test.rules", "services.core", + "flag-junit", ], srcs: [ "src/com/android/server/uri/**/*.java", diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 9cdaec647a43..7a77392c4fa3 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -472,6 +472,7 @@ public class FingerprintAuthenticationClientTest { verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt()); verify(mSideFpsController).hide(anyInt()); + verify(mHal, times(2)).setIgnoreDisplayTouches(false); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index 951c93935e30..3ee54f53d44f 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -327,6 +328,7 @@ public class FingerprintEnrollClientTest { verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt()); verify(mSideFpsController).hide(anyInt()); + verify(mHal, times(2)).setIgnoreDisplayTouches(false); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 99fa30c588db..1d3daccd0d90 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -169,14 +169,14 @@ public class HdmiControlServiceTest { .setEarcSupported(true) .build(); mHdmiPortInfo[3] = - new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_INPUT, 0x3000) + new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_OUTPUT, 0x3000) .setCecSupported(true) .setMhlSupported(false) .setArcSupported(false) .setEarcSupported(false) .build(); mHdmiPortInfo[4] = - new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_OUTPUT, 0x3000) + new HdmiPortInfo.Builder(5, HdmiPortInfo.PORT_OUTPUT, 0x3000) .setCecSupported(true) .setMhlSupported(false) .setArcSupported(false) @@ -841,6 +841,65 @@ public class HdmiControlServiceTest { } @Test + public void onHotPlugIn_CecDisabledOnTv_CecNotAvailable() { + HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback(); + mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); + mHdmiControlServiceSpy.playback().removeAction(DevicePowerStatusAction.class); + mNativeWrapper.clearResultMessages(); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.onHotplug(4, true); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveDevicePowerStatus = + HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(), + Constants.ADDR_TV); + assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); + // Wait for DevicePowerStatusAction to finish. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue(); + assertThat(hdmiControlStatusCallback.mCecAvailable).isFalse(); + } + + @Test + public void onHotPlugIn_CecEnabledOnTv_CecAvailable() { + HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback(); + mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); + mHdmiControlServiceSpy.playback().removeAction(DevicePowerStatusAction.class); + mNativeWrapper.clearResultMessages(); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.onHotplug(4, true); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveDevicePowerStatus = + HdmiCecMessageBuilder.buildGiveDevicePowerStatus( + mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(), + Constants.ADDR_TV); + HdmiCecMessage reportPowerStatus = + HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(), + HdmiControlManager.POWER_STATUS_ON); + assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus); + mNativeWrapper.onCecMessage(reportPowerStatus); + mTestLooper.dispatchAll(); + + assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue(); + assertThat(hdmiControlStatusCallback.mCecAvailable).isTrue(); + } + @Test public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() { // Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus HdmiCecMessage message = HdmiUtils.buildMessage("80:8D:03"); diff --git a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java index f537efd9736d..da8ec2e4ec15 100644 --- a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java @@ -17,12 +17,14 @@ package com.android.server.pdb; import static com.android.server.pdb.PersistentDataBlockService.DIGEST_SIZE_BYTES; +import static com.android.server.pdb.PersistentDataBlockService.FRP_SECRET_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_DATA_BLOCK_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_FRP_CREDENTIAL_HANDLE_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_TEST_MODE_DATA_SIZE; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; @@ -30,7 +32,8 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.junit.Assert.assertThrows; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import android.Manifest; import android.content.Context; @@ -45,7 +48,6 @@ import androidx.test.core.app.ApplicationProvider; import junitparams.JUnitParamsRunner; import junitparams.Parameters; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -54,9 +56,13 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.Files; import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; @RunWith(JUnitParamsRunner.class) public class PersistentDataBlockServiceTest { @@ -64,20 +70,31 @@ public class PersistentDataBlockServiceTest { private static final byte[] SMALL_DATA = "data to write".getBytes(); private static final byte[] ANOTHER_SMALL_DATA = "something else".getBytes(); + public static final int DEFAULT_BLOCK_DEVICE_SIZE = -1; private Context mContext; private PersistentDataBlockService mPdbService; private IPersistentDataBlockService mInterface; private PersistentDataBlockManagerInternal mInternalInterface; private File mDataBlockFile; + private File mFrpSecretFile; + private File mFrpSecretTmpFile; private String mOemUnlockPropertyValue; + private boolean mIsUpgradingFromPreV = false; @Mock private UserManager mUserManager; private class FakePersistentDataBlockService extends PersistentDataBlockService { + FakePersistentDataBlockService(Context context, String dataBlockFile, - long blockDeviceSize) { - super(context, /* isFileBacked */ true, dataBlockFile, blockDeviceSize); + long blockDeviceSize, boolean frpEnabled, String frpSecretFile, + String frpSecretTmpFile) { + super(context, /* isFileBacked */ true, dataBlockFile, blockDeviceSize, frpEnabled, + frpSecretFile, frpSecretTmpFile); + // In the real service, this is done by onStart(), which we don't want to call because + // it registers the service, etc. But we need to signal init done to prevent + // `isFrpActive` from blocking. + signalInitDone(); } @Override @@ -86,18 +103,25 @@ public class PersistentDataBlockServiceTest { assertThat(key).isEqualTo("sys.oem_unlock_allowed"); mOemUnlockPropertyValue = value; } + + @Override + boolean isUpgradingFromPreVRelease() { + return mIsUpgradingFromPreV; + } } @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - @Before - public void setUp() throws Exception { + private void setUp(boolean frpEnabled) throws Exception { MockitoAnnotations.initMocks(this); mDataBlockFile = mTemporaryFolder.newFile(); + mFrpSecretFile = mTemporaryFolder.newFile(); + mFrpSecretTmpFile = mTemporaryFolder.newFile(); mContext = spy(ApplicationProvider.getApplicationContext()); mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(), - /* blockDeviceSize */ -1); + DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretFile.getPath(), + mFrpSecretTmpFile.getPath()); mPdbService.setAllowedUid(Binder.getCallingUid()); mPdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false); mInterface = mPdbService.getInterfaceForTesting(); @@ -119,9 +143,7 @@ public class PersistentDataBlockServiceTest { * a block implementation for the read/write operations. */ public Object[][] getTestParametersForBlocks() { - return new Object[][] { - { - new Block() { + Block simpleReadWrite = new Block() { @Override public int write(byte[] data) throws RemoteException { return service.getInterfaceForTesting().write(data); } @@ -129,10 +151,8 @@ public class PersistentDataBlockServiceTest { @Override public byte[] read() throws RemoteException { return service.getInterfaceForTesting().read(); } - }, - }, - { - new Block() { + }; + Block credHandle = new Block() { @Override public int write(byte[] data) { service.getInternalInterfaceForTesting().setFrpCredentialHandle(data); // The written size isn't returned. Pretend it's fully written in the @@ -143,10 +163,8 @@ public class PersistentDataBlockServiceTest { @Override public byte[] read() { return service.getInternalInterfaceForTesting().getFrpCredentialHandle(); } - }, - }, - { - new Block() { + }; + Block testHarness = new Block() { @Override public int write(byte[] data) { service.getInternalInterfaceForTesting().setTestHarnessModeData(data); // The written size isn't returned. Pretend it's fully written in the @@ -157,14 +175,21 @@ public class PersistentDataBlockServiceTest { @Override public byte[] read() { return service.getInternalInterfaceForTesting().getTestHarnessModeData(); } - }, - }, + }; + return new Object[][] { + { simpleReadWrite, false }, + { simpleReadWrite, true }, + { credHandle, false }, + { credHandle, true }, + { testHarness, false }, + { testHarness, true }, }; } @Test @Parameters(method = "getTestParametersForBlocks") - public void writeThenRead(Block block) throws Exception { + public void writeThenRead(Block block, boolean frpEnabled) throws Exception { + setUp(frpEnabled); block.service = mPdbService; assertThat(block.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length); assertThat(block.read()).isEqualTo(SMALL_DATA); @@ -172,7 +197,8 @@ public class PersistentDataBlockServiceTest { @Test @Parameters(method = "getTestParametersForBlocks") - public void writeWhileAlreadyCorrupted(Block block) throws Exception { + public void writeWhileAlreadyCorrupted(Block block, boolean frpEnabled) throws Exception { + setUp(frpEnabled); block.service = mPdbService; assertThat(block.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length); assertThat(block.read()).isEqualTo(SMALL_DATA); @@ -184,7 +210,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void frpWriteOutOfBound() throws Exception { + @Parameters({"false", "true"}) + public void frpWriteOutOfBound(boolean frpEnabled) throws Exception { + setUp(frpEnabled); byte[] maxData = new byte[mPdbService.getMaximumFrpDataSize()]; assertThat(mInterface.write(maxData)).isEqualTo(maxData.length); @@ -193,7 +221,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void frpCredentialWriteOutOfBound() throws Exception { + @Parameters({"false", "true"}) + public void frpCredentialWriteOutOfBound(boolean frpEnabled) throws Exception { + setUp(frpEnabled); byte[] maxData = new byte[MAX_FRP_CREDENTIAL_HANDLE_SIZE]; mInternalInterface.setFrpCredentialHandle(maxData); @@ -203,7 +233,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void testHardnessWriteOutOfBound() throws Exception { + @Parameters({"false", "true"}) + public void testHardnessWriteOutOfBound(boolean frpEnabled) throws Exception { + setUp(frpEnabled); byte[] maxData = new byte[MAX_TEST_MODE_DATA_SIZE]; mInternalInterface.setTestHarnessModeData(maxData); @@ -213,7 +245,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void readCorruptedFrpData() throws Exception { + @Parameters({"false", "true"}) + public void readCorruptedFrpData(boolean frpEnabled) throws Exception { + setUp(frpEnabled); assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length); assertThat(mInterface.read()).isEqualTo(SMALL_DATA); @@ -224,7 +258,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void readCorruptedFrpCredentialData() throws Exception { + @Parameters({"false", "true"}) + public void readCorruptedFrpCredentialData(boolean frpEnabled) throws Exception { + setUp(frpEnabled); mInternalInterface.setFrpCredentialHandle(SMALL_DATA); assertThat(mInternalInterface.getFrpCredentialHandle()).isEqualTo(SMALL_DATA); @@ -235,7 +271,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void readCorruptedTestHarnessData() throws Exception { + @Parameters({"false", "true"}) + public void readCorruptedTestHarnessData(boolean frpEnabled) throws Exception { + setUp(frpEnabled); mInternalInterface.setTestHarnessModeData(SMALL_DATA); assertThat(mInternalInterface.getTestHarnessModeData()).isEqualTo(SMALL_DATA); @@ -246,14 +284,18 @@ public class PersistentDataBlockServiceTest { } @Test - public void nullWrite() throws Exception { + @Parameters({"false", "true"}) + public void nullWrite(boolean frpEnabled) throws Exception { + setUp(frpEnabled); assertThrows(NullPointerException.class, () -> mInterface.write(null)); mInternalInterface.setFrpCredentialHandle(null); // no exception mInternalInterface.setTestHarnessModeData(null); // no exception } @Test - public void emptyDataWrite() throws Exception { + @Parameters({"false", "true"}) + public void emptyDataWrite(boolean frpEnabled) throws Exception { + setUp(frpEnabled); var empty = new byte[0]; assertThat(mInterface.write(empty)).isEqualTo(0); @@ -264,10 +306,13 @@ public class PersistentDataBlockServiceTest { } @Test - public void frpWriteMoreThan100K() throws Exception { + @Parameters({"false", "true"}) + public void frpWriteMoreThan100K(boolean frpEnabled) throws Exception { + setUp(frpEnabled); File dataBlockFile = mTemporaryFolder.newFile(); PersistentDataBlockService pdbService = new FakePersistentDataBlockService(mContext, - dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000); + dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000, frpEnabled, + /* frpSecretFile */ null, /* frpSecretTmpFile */ null); pdbService.setAllowedUid(Binder.getCallingUid()); pdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false); @@ -278,30 +323,39 @@ public class PersistentDataBlockServiceTest { } @Test - public void frpBlockReadWriteWithoutPermission() throws Exception { + @Parameters({"false", "true"}) + public void frpBlockReadWriteWithoutPermission(boolean frpEnabled) throws Exception { + setUp(frpEnabled); mPdbService.setAllowedUid(Binder.getCallingUid() + 1); // unexpected uid assertThrows(SecurityException.class, () -> mInterface.write(SMALL_DATA)); assertThrows(SecurityException.class, () -> mInterface.read()); } @Test - public void getMaximumDataBlockSizeDenied() throws Exception { + @Parameters({"false", "true"}) + public void getMaximumDataBlockSizeDenied(boolean frpEnabled) throws Exception { + setUp(frpEnabled); mPdbService.setAllowedUid(Binder.getCallingUid() + 1); // unexpected uid assertThrows(SecurityException.class, () -> mInterface.getMaximumDataBlockSize()); } @Test - public void getMaximumDataBlockSize() throws Exception { + @Parameters({"false", "true"}) + public void getMaximumDataBlockSize(boolean frpEnabled) throws Exception { + setUp(frpEnabled); mPdbService.setAllowedUid(Binder.getCallingUid()); assertThat(mInterface.getMaximumDataBlockSize()) .isEqualTo(mPdbService.getMaximumFrpDataSize()); } @Test - public void getMaximumDataBlockSizeOfLargerPartition() throws Exception { + @Parameters({"false", "true"}) + public void getMaximumDataBlockSizeOfLargerPartition(boolean frpEnabled) throws Exception { + setUp(frpEnabled); File dataBlockFile = mTemporaryFolder.newFile(); PersistentDataBlockService pdbService = new FakePersistentDataBlockService(mContext, - dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000); + dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000, frpEnabled, + /* frpSecretFile */null, /* mFrpSecretTmpFile */ null); pdbService.setAllowedUid(Binder.getCallingUid()); pdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false); @@ -310,7 +364,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void getFrpDataBlockSizeGrantedByUid() throws Exception { + @Parameters({"false", "true"}) + public void getFrpDataBlockSizeGrantedByUid(boolean frpEnabled) throws Exception { + setUp(frpEnabled); assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length); mPdbService.setAllowedUid(Binder.getCallingUid()); @@ -323,7 +379,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void getFrpDataBlockSizeGrantedByPermission() throws Exception { + @Parameters({"false", "true"}) + public void getFrpDataBlockSizeGrantedByPermission(boolean frpEnabled) throws Exception { + setUp(frpEnabled); assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length); mPdbService.setAllowedUid(Binder.getCallingUid() + 1); // unexpected uid @@ -338,13 +396,17 @@ public class PersistentDataBlockServiceTest { } @Test - public void wipePermissionCheck() throws Exception { + @Parameters({"false", "true"}) + public void wipePermissionCheck(boolean frpEnabled) throws Exception { + setUp(frpEnabled); denyOemUnlockPermission(); assertThrows(SecurityException.class, () -> mInterface.wipe()); } @Test - public void wipeMakesItNotWritable() throws Exception { + @Parameters({"false", "true"}) + public void wipeMakesItNotWritable(boolean frpEnabled) throws Exception { + setUp(frpEnabled); grantOemUnlockPermission(); mInterface.wipe(); @@ -368,7 +430,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void hasFrpCredentialHandleGrantedByUid() throws Exception { + @Parameters({"false", "true"}) + public void hasFrpCredentialHandle_GrantedByUid(boolean frpEnabled) throws Exception { + setUp(frpEnabled); mPdbService.setAllowedUid(Binder.getCallingUid()); assertThat(mInterface.hasFrpCredentialHandle()).isFalse(); @@ -377,17 +441,51 @@ public class PersistentDataBlockServiceTest { } @Test - public void hasFrpCredentialHandleGrantedByPermission() throws Exception { + @Parameters({"false", "true"}) + public void hasFrpCredentialHandle_GrantedByConfigureFrpPermission(boolean frpEnabled) + throws Exception { + setUp(frpEnabled); + grantConfigureFrpPermission(); + mPdbService.setAllowedUid(Binder.getCallingUid() + 1); // unexpected uid + + if (frpEnabled) { + assertThat(mInterface.hasFrpCredentialHandle()).isFalse(); + mInternalInterface.setFrpCredentialHandle(SMALL_DATA); + assertThat(mInterface.hasFrpCredentialHandle()).isTrue(); + } else { + assertThrows(SecurityException.class, () -> mInterface.hasFrpCredentialHandle()); + } + } + + @Test + @Parameters({"false", "true"}) + public void hasFrpCredentialHandle_GrantedByAccessPdbStatePermission(boolean frpEnabled) + throws Exception { + setUp(frpEnabled); grantAccessPdbStatePermission(); + mPdbService.setAllowedUid(Binder.getCallingUid() + 1); // unexpected uid + assertThat(mInterface.hasFrpCredentialHandle()).isFalse(); mInternalInterface.setFrpCredentialHandle(SMALL_DATA); assertThat(mInterface.hasFrpCredentialHandle()).isTrue(); } @Test - public void clearTestHarnessModeData() throws Exception { + @Parameters({"false", "true"}) + public void hasFrpCredentialHandle_Unauthorized(boolean frpEnabled) throws Exception { + setUp(frpEnabled); + + mPdbService.setAllowedUid(Binder.getCallingUid() + 1); // unexpected uid + + assertThrows(SecurityException.class, () -> mInterface.hasFrpCredentialHandle()); + } + + @Test + @Parameters({"false", "true"}) + public void clearTestHarnessModeData(boolean frpEnabled) throws Exception { + setUp(frpEnabled); mInternalInterface.setTestHarnessModeData(SMALL_DATA); mInternalInterface.clearTestHarnessModeData(); @@ -397,19 +495,25 @@ public class PersistentDataBlockServiceTest { } @Test - public void getAllowedUid() throws Exception { + @Parameters({"false", "true"}) + public void getAllowedUid(boolean frpEnabled) throws Exception { + setUp(frpEnabled); assertThat(mInternalInterface.getAllowedUid()).isEqualTo(Binder.getCallingUid()); } @Test - public void oemUnlockWithoutPermission() throws Exception { + @Parameters({"false", "true"}) + public void oemUnlockWithoutPermission(boolean frpEnabled) throws Exception { + setUp(frpEnabled); denyOemUnlockPermission(); assertThrows(SecurityException.class, () -> mInterface.setOemUnlockEnabled(true)); } @Test - public void oemUnlockNotAdmin() throws Exception { + @Parameters({"false", "true"}) + public void oemUnlockNotAdmin(boolean frpEnabled) throws Exception { + setUp(frpEnabled); grantOemUnlockPermission(); makeUserAdmin(false); @@ -417,7 +521,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void oemUnlock() throws Exception { + @Parameters({"false", "true"}) + public void oemUnlock(boolean frpEnabled) throws Exception { + setUp(frpEnabled); grantOemUnlockPermission(); makeUserAdmin(true); @@ -427,7 +533,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void oemUnlockUserRestriction_OemUnlock() throws Exception { + @Parameters({"false", "true"}) + public void oemUnlockUserRestriction_OemUnlock(boolean frpEnabled) throws Exception { + setUp(frpEnabled); grantOemUnlockPermission(); makeUserAdmin(true); when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_OEM_UNLOCK))) @@ -437,7 +545,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void oemUnlockUserRestriction_FactoryReset() throws Exception { + @Parameters({"false", "true"}) + public void oemUnlockUserRestriction_FactoryReset(boolean frpEnabled) throws Exception { + setUp(frpEnabled); grantOemUnlockPermission(); makeUserAdmin(true); when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_FACTORY_RESET))) @@ -447,7 +557,9 @@ public class PersistentDataBlockServiceTest { } @Test - public void oemUnlockIgnoreTampering() throws Exception { + @Parameters({"false", "true"}) + public void oemUnlockIgnoreTampering(boolean frpEnabled) throws Exception { + setUp(frpEnabled); grantOemUnlockPermission(); makeUserAdmin(true); @@ -460,26 +572,37 @@ public class PersistentDataBlockServiceTest { } @Test - public void getOemUnlockEnabledPermissionCheck_NoPermission() throws Exception { + @Parameters({"false", "true"}) + public void getOemUnlockEnabledPermissionCheck_NoPermission(boolean frpEnabled) + throws Exception { + setUp(frpEnabled); assertThrows(SecurityException.class, () -> mInterface.getOemUnlockEnabled()); } @Test - public void getOemUnlockEnabledPermissionCheck_OemUnlcokState() throws Exception { + @Parameters({"false", "true"}) + public void getOemUnlockEnabledPermissionCheck_OemUnlockState(boolean frpEnabled) + throws Exception { + setUp(frpEnabled); doReturn(PackageManager.PERMISSION_GRANTED).when(mContext) .checkCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE)); assertThat(mInterface.getOemUnlockEnabled()).isFalse(); } @Test - public void getOemUnlockEnabledPermissionCheck_ReadOemUnlcokState() throws Exception { + @Parameters({"false", "true"}) + public void getOemUnlockEnabledPermissionCheck_ReadOemUnlockState(boolean frpEnabled) + throws Exception { + setUp(frpEnabled); doReturn(PackageManager.PERMISSION_GRANTED).when(mContext) .checkCallingOrSelfPermission(eq(Manifest.permission.READ_OEM_UNLOCK_STATE)); assertThat(mInterface.getOemUnlockEnabled()).isFalse(); } @Test - public void forceOemUnlock_RequiresNoPermission() throws Exception { + @Parameters({"false", "true"}) + public void forceOemUnlock_RequiresNoPermission(boolean frpEnabled) throws Exception { + setUp(frpEnabled); denyOemUnlockPermission(); mInternalInterface.forceOemUnlockEnabled(true); @@ -490,24 +613,331 @@ public class PersistentDataBlockServiceTest { } @Test - public void getFlashLockStatePermissionCheck_NoPermission() throws Exception { + @Parameters({"false", "true"}) + public void getFlashLockStatePermissionCheck_NoPermission(boolean frpEnabled) throws Exception { + setUp(frpEnabled); assertThrows(SecurityException.class, () -> mInterface.getFlashLockState()); } @Test - public void getFlashLockStatePermissionCheck_OemUnlcokState() throws Exception { + @Parameters({"false", "true"}) + public void getFlashLockStatePermissionCheck_OemUnlockState(boolean frpEnabled) + throws Exception { + setUp(frpEnabled); doReturn(PackageManager.PERMISSION_GRANTED).when(mContext) .checkCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE)); mInterface.getFlashLockState(); // Do not throw } @Test - public void getFlashLockStatePermissionCheck_ReadOemUnlcokState() throws Exception { + @Parameters({"false", "true"}) + public void getFlashLockStatePermissionCheck_ReadOemUnlockState(boolean frpEnabled) + throws Exception { + setUp(frpEnabled); doReturn(PackageManager.PERMISSION_GRANTED).when(mContext) .checkCallingOrSelfPermission(eq(Manifest.permission.READ_OEM_UNLOCK_STATE)); mInterface.getFlashLockState(); // Do not throw } + @Test + @Parameters({"false", "true"}) + public void frpMagicTest(boolean frpEnabled) throws Exception { + setUp(frpEnabled); + byte[] magicField = mPdbService.readDataBlock(mPdbService.getFrpSecretMagicOffset(), + PersistentDataBlockService.FRP_SECRET_MAGIC.length); + if (frpEnabled) { + assertThat(magicField).isEqualTo(PersistentDataBlockService.FRP_SECRET_MAGIC); + } else { + assertThat(magicField).isNotEqualTo(PersistentDataBlockService.FRP_SECRET_MAGIC); + } + } + + @Test + public void frpSecret_StartsAsDefault() throws Exception { + setUp(/* frpEnabled */ true); + + byte[] secretField = mPdbService.readDataBlock( + mPdbService.getFrpSecretDataOffset(), PersistentDataBlockService.FRP_SECRET_SIZE); + assertThat(secretField).isEqualTo(new byte[PersistentDataBlockService.FRP_SECRET_SIZE]); + } + + @Test + public void frpSecret_SetSecret() throws Exception { + setUp(/* frpEnforcement */ true); + grantConfigureFrpPermission(); + + byte[] hashedSecret = hashStringto32Bytes("secret"); + assertThat(mInterface.setFactoryResetProtectionSecret(hashedSecret)).isTrue(); + + byte[] secretField = mPdbService.readDataBlock( + mPdbService.getFrpSecretDataOffset(), PersistentDataBlockService.FRP_SECRET_SIZE); + assertThat(secretField).isEqualTo(hashedSecret); + + assertThat(mFrpSecretFile.exists()).isTrue(); + byte[] secretFileData = Files.readAllBytes(mFrpSecretFile.toPath()); + assertThat(secretFileData).isEqualTo(hashedSecret); + + assertThat(mFrpSecretTmpFile.exists()).isFalse(); + } + + @Test + public void frpSecret_SetSecretByUnauthorizedCaller() throws Exception { + setUp(/* frpEnforcement */ true); + + mPdbService.setAllowedUid(Binder.getCallingUid() + 1); // unexpected uid + assertThrows(SecurityException.class, + () -> mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret"))); + } + + /** + * Verify that FRP always starts in active state (if flag-enabled), until something is done to + * deactivate it. + */ + @Test + @Parameters({"false", "true"}) + public void frpState_StartsActive(boolean frpEnabled) throws Exception { + setUp(frpEnabled); + // Create a service without calling formatPartition, which deactivates FRP. + PersistentDataBlockService pdbService = new FakePersistentDataBlockService(mContext, + mDataBlockFile.getPath(), DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, + mFrpSecretFile.getPath(), mFrpSecretTmpFile.getPath()); + assertThat(pdbService.isFrpActive()).isEqualTo(frpEnabled); + } + + @Test + public void frpState_AutomaticallyDeactivateWithDefault() throws Exception { + setUp(/* frpEnforcement */ true); + + mPdbService.activateFrp(); + assertThat(mPdbService.isFrpActive()).isTrue(); + + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + } + + @Test + public void frpState_AutomaticallyDeactivateWithPrimaryDataFile() throws Exception { + setUp(/* frpEnforcement */ true); + grantConfigureFrpPermission(); + + mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret")); + + mPdbService.activateFrp(); + assertThat(mPdbService.isFrpActive()).isTrue(); + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + } + + @Test + public void frpState_AutomaticallyDeactivateWithBackupDataFile() throws Exception { + setUp(/* frpEnforcement */ true); + grantConfigureFrpPermission(); + + mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret")); + Files.move(mFrpSecretFile.toPath(), mFrpSecretTmpFile.toPath(), REPLACE_EXISTING); + + mPdbService.activateFrp(); + assertThat(mPdbService.isFrpActive()).isTrue(); + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + } + + @Test + public void frpState_DeactivateWithSecret() throws Exception { + setUp(/* frpEnforcement */ true); + grantConfigureFrpPermission(); + + mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret")); + simulateDataWipe(); + + assertThat(mPdbService.isFrpActive()).isFalse(); + mPdbService.activateFrp(); + assertThat(mPdbService.isFrpActive()).isTrue(); + + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isFalse(); + assertThat(mPdbService.isFrpActive()).isTrue(); + + assertThat(mInterface.deactivateFactoryResetProtection(hashStringto32Bytes("wrongSecret"))) + .isFalse(); + assertThat(mPdbService.isFrpActive()).isTrue(); + + assertThat(mInterface.deactivateFactoryResetProtection(hashStringto32Bytes("secret"))) + .isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + + assertThat(mInterface.setFactoryResetProtectionSecret(new byte[FRP_SECRET_SIZE])).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + + mPdbService.activateFrp(); + assertThat(mPdbService.isFrpActive()).isTrue(); + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + } + + @Test + public void frpState_DeactivateOnUpgradeFromPreV() throws Exception { + setUp(/* frpEnforcement */ true); + grantConfigureFrpPermission(); + + mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret")); + // If the /data files are still present, deactivation will use them. We want to verify + // that deactivation will succeed even if they are not present, so remove them. + simulateDataWipe(); + + // Verify that automatic deactivation fails without the /data files when we're not + // upgrading from pre-V. + mPdbService.activateFrp(); + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isFalse(); + assertThat(mPdbService.isFrpActive()).isTrue(); + + // Verify that automatic deactivation succeeds when upgrading from pre-V. + mIsUpgradingFromPreV = true; + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + } + + /** + * There is code in PersistentDataBlockService to handle a specific corner case, that of a + * device that is upgraded from pre-V to V+, downgraded to pre-V and then upgraded to V+. In + * this scenario, the following happens: + * + * 1. When the device is upgraded to V+ and the user sets an LSKF and GAIA creds, FRP + * enforcement is activated and three copies of the FRP secret are written to: + * a. The FRP secret field in PDB (plaintext). + * b. The GAIA challenge in PDB (encrypted). + * c. The FRP secret file in /data (plaintext). + * 2. When the device is downgraded to pre-V, /data is wiped, so copy (c) is destroyed. When the + * user sets LSKF and GAIA creds, copy (b) is overwritten. Copy (a) survives. + * 3. When the device is upgraded to V and boots the first time, FRP cannot be automatically + * deactivated using copy (c), nor can the user deactivate FRP using copy (b), because both + * are gone. Absent some special handling of this case, the device would be unusable. + * + * To address this problem, if PersistentDataBlockService finds an FRP secret in (a) but none + * in (b) or (c), and PackageManager reports that the device has just upgraded from pre-V to + * V+, it zeros the FRP secret in (a). + * + * This test checks that the service handles this sequence of events correctly. + */ + @Test + public void frpState_TestDowngradeUpgradeSequence() throws Exception { + // Simulate device in V+, with FRP configured. + setUp(/* frpEnforcement */ true); + grantConfigureFrpPermission(); + + assertThat(mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret"))) + .isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + + // Simulate reboot, still in V+. + boolean frpEnabled = true; + mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(), + DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretFile.getPath(), + mFrpSecretTmpFile.getPath()); + assertThat(mPdbService.isFrpActive()).isTrue(); + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + + // Simulate reboot after data wipe and downgrade to pre-V. + simulateDataWipe(); + frpEnabled = false; + mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(), + DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretFile.getPath(), + mFrpSecretTmpFile.getPath()); + assertThat(mPdbService.isFrpActive()).isFalse(); + + // Simulate reboot after upgrade to V+, no data wipe. + frpEnabled = true; + mIsUpgradingFromPreV = true; + mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(), + DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretTmpFile.getPath(), + mFrpSecretTmpFile.getPath()); + mPdbService.setAllowedUid(Binder.getCallingUid()); // Needed for setFrpSecret(). + assertThat(mPdbService.isFrpActive()).isTrue(); + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + assertThat(mPdbService.getInterfaceForTesting() + .setFactoryResetProtectionSecret(new byte[FRP_SECRET_SIZE])).isTrue(); + + // Simulate one more reboot. + mIsUpgradingFromPreV = false; + mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(), + DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretTmpFile.getPath(), + mFrpSecretTmpFile.getPath()); + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + } + + @Test + public void frpState_PrivilegedDeactivationByAuthorizedCaller() throws Exception { + setUp(/* frpEnforcement */ true); + grantConfigureFrpPermission(); + + assertThat(mPdbService.isFrpActive()).isFalse(); + assertThat(mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret"))) + .isTrue(); + + simulateDataWipe(); + mPdbService.activateFrp(); + assertThat(mPdbService.isFrpActive()).isTrue(); + + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isFalse(); + assertThat(mPdbService.isFrpActive()).isTrue(); + + assertThat(mInternalInterface.deactivateFactoryResetProtectionWithoutSecret()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + } + + @Test + public void frpActive_WipeFails() throws Exception { + setUp(/* frpEnforcement */ true); + + grantOemUnlockPermission(); + mPdbService.activateFrp(); + SecurityException e = assertThrows(SecurityException.class, () -> mInterface.wipe()); + assertThat(e).hasMessageThat().contains("FRP is active"); + } + + @Test + public void frpActive_WriteFails() throws Exception { + setUp(/* frpEnforcement */ true); + + mPdbService.activateFrp(); + SecurityException e = + assertThrows(SecurityException.class, () -> mInterface.write("data".getBytes())); + assertThat(e).hasMessageThat().contains("FRP is active"); + } + + @Test + public void frpActive_SetSecretFails() throws Exception { + setUp(/* frpEnforcement */ true); + grantConfigureFrpPermission(); + + mPdbService.activateFrp(); + + byte[] hashedSecret = hashStringto32Bytes("secret"); + SecurityException e = assertThrows(SecurityException.class, () + -> mInterface.setFactoryResetProtectionSecret(hashedSecret)); + assertThat(e).hasMessageThat().contains("FRP is active"); + assertThat(mPdbService.isFrpActive()).isTrue(); + + // Verify that secret we failed to set isn't accepted. + assertThat(mInterface.deactivateFactoryResetProtection(hashedSecret)).isFalse(); + assertThat(mPdbService.isFrpActive()).isTrue(); + + // Default should work, since it should never have been changed. + assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue(); + assertThat(mPdbService.isFrpActive()).isFalse(); + } + + private void simulateDataWipe() throws IOException { + Files.deleteIfExists(mFrpSecretFile.toPath()); + Files.deleteIfExists(mFrpSecretTmpFile.toPath()); + } + + private static byte[] hashStringto32Bytes(String secret) throws NoSuchAlgorithmException { + return MessageDigest.getInstance("SHA-256").digest(secret.getBytes()); + } + private void tamperWithDigest() throws Exception { try (var ch = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.WRITE)) { ch.write(ByteBuffer.wrap("tampered-digest".getBytes())); @@ -542,6 +972,14 @@ public class PersistentDataBlockServiceTest { .checkCallingPermission(eq(Manifest.permission.ACCESS_PDB_STATE)); } + private void grantConfigureFrpPermission() { + doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission( + eq(Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION)); + doNothing().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION), + anyString()); + } + private ByteBuffer readBackingFile(long position, int size) throws Exception { try (var ch = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.READ)) { var buffer = ByteBuffer.allocate(size); diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java index 8656f60afc1e..bf87e3ac1f7e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java @@ -111,6 +111,8 @@ public final class BackgroundInstallControlServiceTest { private UsageStatsManagerInternal mUsageStatsManagerInternal; @Mock private PermissionManagerServiceInternal mPermissionManager; + @Mock + private BackgroundInstallControlCallbackHelper mCallbackHelper; @Captor private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor; @@ -982,5 +984,11 @@ public final class BackgroundInstallControlServiceTest { public File getDiskFile() { return mFile; } + + + @Override + public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() { + return mCallbackHelper; + } } } diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java index 321858685e38..24abc183cad1 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.uri; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT; + import static com.android.server.uri.UriGrantsMockContext.FLAG_PERSISTABLE; import static com.android.server.uri.UriGrantsMockContext.FLAG_PREFIX; import static com.android.server.uri.UriGrantsMockContext.FLAG_READ; @@ -57,22 +59,49 @@ import android.content.pm.ProviderInfo; import android.net.Uri; import android.os.Process; import android.os.UserHandle; +import android.platform.test.flag.junit.FlagsParameterization; +import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArraySet; -import androidx.test.InstrumentationRegistry; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; import java.util.Arrays; +import java.util.List; import java.util.Set; +@RunWith(Parameterized.class) public class UriGrantsManagerServiceTest { @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); + /** + * Why this class needs to test all combinations of + * {@link android.security.Flags#FLAG_CONTENT_URI_PERMISSION_APIS}: + * + * <p>Although tests in this class don't directly query the flag, its value + * is needed for {@link UriGrantsManagerInternal#checkGrantUriPermissionFromIntent}. This is + * particularly important for host side tests (Ravenwood), which cannot read flag values from + * the device and must have them set explicitly. + */ + @Parameters(name = "{0}") + public static List<FlagsParameterization> getFlags() { + return FlagsParameterization.allCombinationsOf( + android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS); + } + + public UriGrantsManagerServiceTest(FlagsParameterization flags) { + mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT, flags); + } + + @Rule + public final SetFlagsRule mSetFlagsRule; + private UriGrantsMockContext mContext; private UriGrantsManagerInternal mService; diff --git a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java index 4d4f5ed15ad6..611c51463246 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java @@ -33,8 +33,10 @@ import static com.android.server.uri.UriPermission.STRENGTH_PERSISTABLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import android.os.SystemClock; import android.platform.test.ravenwood.RavenwoodRule; import org.junit.Before; @@ -154,10 +156,12 @@ public class UriPermissionTest { assertEquals(FLAG_WRITE, perm.persistableModeFlags); assertEquals(FLAG_WRITE, perm.persistedModeFlags); - // Attempting to take a second time should be a no-op + // Attempting to take a second time should "touch" timestamp, per public API + // docs on ContentResolver.takePersistableUriPermission() final long createTime = perm.persistedCreateTime; + SystemClock.sleep(10); assertFalse(perm.takePersistableModes(FLAG_WRITE)); - assertEquals(createTime, perm.persistedCreateTime); + assertNotEquals(createTime, perm.persistedCreateTime); assertTrue(perm.releasePersistableModes(FLAG_WRITE)); assertEquals(FLAG_WRITE, perm.persistableModeFlags); diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 90493d4dc95f..295b124c06e0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -346,6 +346,7 @@ public class SystemServicesTestRule implements TestRule { doReturn(true).when(amInternal).hasStartedUserState(anyInt()); doReturn(false).when(amInternal).shouldConfirmCredentials(anyInt()); doReturn(false).when(amInternal).isActivityStartsLoggingEnabled(); + doReturn(true).when(amInternal).getThemeOverlayReadiness(); LocalServices.addService(ActivityManagerInternal.class, amInternal); final ActivityManagerService amService = diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java index 331caa1bad7a..7ad26c901188 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -16,7 +16,10 @@ package android.telecom; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.media.ToneGenerator; import android.os.Parcel; import android.os.Parcelable; @@ -25,6 +28,8 @@ import android.telephony.PreciseDisconnectCause; import android.telephony.ims.ImsReasonInfo; import android.text.TextUtils; +import com.android.server.telecom.flags.Flags; + import java.util.Objects; /** @@ -169,7 +174,9 @@ public final class DisconnectCause implements Parcelable { } /** - * Creates a new DisconnectCause instance. + * Creates a new DisconnectCause instance. This is used by Telephony to pass in extra debug + * info to Telecom regarding the disconnect cause. + * * @param code The code for the disconnect cause. * @param label The localized label to show to the user to explain the disconnect. * @param description The localized description to show to the user to explain the disconnect. @@ -180,7 +187,10 @@ public final class DisconnectCause implements Parcelable { * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available. * @hide */ - public DisconnectCause(int code, CharSequence label, CharSequence description, String reason, + @SystemApi + @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) + public DisconnectCause(int code, @NonNull CharSequence label, + @NonNull CharSequence description, @NonNull String reason, int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause, @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause, @Nullable ImsReasonInfo imsReasonInfo) { @@ -241,28 +251,40 @@ public final class DisconnectCause implements Parcelable { } /** - * Returns the telephony {@link android.telephony.DisconnectCause} for the call. + * Returns the telephony {@link android.telephony.DisconnectCause} for the call. This is only + * used internally by Telecom for providing extra debug information from Telephony. + * * @return The disconnect cause. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) public @Annotation.DisconnectCauses int getTelephonyDisconnectCause() { return mTelephonyDisconnectCause; } /** - * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call. + * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call. This is + * only used internally by Telecom for providing extra debug information from Telephony. + * * @return The precise disconnect cause. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) public @Annotation.PreciseDisconnectCauses int getTelephonyPreciseDisconnectCause() { return mTelephonyPreciseDisconnectCause; } /** - * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection. + * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection. This is + * only used internally by Telecom for providing extra debug information from Telephony. + * * @return The {@link ImsReasonInfo} or {@code null} if not known. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES) public @Nullable ImsReasonInfo getImsReasonInfo() { return mImsReasonInfo; } diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index b59e855825b9..5af2c3458368 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -19,6 +19,7 @@ import static android.telephony.euicc.EuiccCardManager.ResetOption; import android.Manifest; import android.annotation.CallSuper; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -38,6 +39,8 @@ import android.telephony.euicc.EuiccManager.OtaStatus; import android.text.TextUtils; import android.util.Log; +import com.android.internal.telephony.flags.Flags; + import java.io.PrintWriter; import java.io.StringWriter; import java.lang.annotation.Retention; @@ -759,6 +762,20 @@ public abstract class EuiccService extends Service { public abstract int onRetainSubscriptionsForFactoryReset(int slotId); /** + * Return the available memory in bytes of the eUICC. + * + * @param slotId ID of the SIM slot being queried. + * @return the available memory in bytes. + * @see android.telephony.euicc.EuiccManager#getAvailableMemoryInBytes + */ + @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY) + public long onGetAvailableMemoryInBytes(int slotId) { + // stub implementation, LPA needs to implement this + throw new UnsupportedOperationException("The connected LPA does not implement" + + "EuiccService#onGetAvailableMemoryInBytes(int)"); + } + + /** * Dump to a provided printWriter. */ public void dump(@NonNull PrintWriter printWriter) { @@ -834,6 +851,22 @@ public abstract class EuiccService extends Service { } @Override + @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY) + public void getAvailableMemoryInBytes( + int slotId, IGetAvailableMemoryInBytesCallback callback) { + mExecutor.execute( + () -> { + long availableMemoryInBytes = + EuiccService.this.onGetAvailableMemoryInBytes(slotId); + try { + callback.onSuccess(availableMemoryInBytes); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + }); + } + + @Override public void startOtaIfNecessary( int slotId, IOtaStatusChangedCallback statusChangedCallback) { mExecutor.execute(new Runnable() { diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl index f8d5ae9ca86d..0f8c72bea5de 100644 --- a/telephony/java/android/service/euicc/IEuiccService.aidl +++ b/telephony/java/android/service/euicc/IEuiccService.aidl @@ -19,6 +19,7 @@ package android.service.euicc; import android.service.euicc.IDeleteSubscriptionCallback; import android.service.euicc.IDownloadSubscriptionCallback; import android.service.euicc.IEraseSubscriptionsCallback; +import android.service.euicc.IGetAvailableMemoryInBytesCallback; import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback; import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback; import android.service.euicc.IGetEidCallback; @@ -60,4 +61,5 @@ oneway interface IEuiccService { void retainSubscriptionsForFactoryReset( int slotId, in IRetainSubscriptionsForFactoryResetCallback callback); void dump(in IEuiccServiceDumpResultCallback callback); -}
\ No newline at end of file + void getAvailableMemoryInBytes(int slotId, in IGetAvailableMemoryInBytesCallback callback); +} diff --git a/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl new file mode 100644 index 000000000000..bd6d19b81d47 --- /dev/null +++ b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl @@ -0,0 +1,22 @@ +/* + * 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.service.euicc; + +/** @hide */ +oneway interface IGetAvailableMemoryInBytesCallback { + void onSuccess(long availableMemoryInBytes); +} diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java index 0f54e8d9457e..3c11da5f2daa 100644 --- a/telephony/java/android/telephony/DomainSelectionService.java +++ b/telephony/java/android/telephony/DomainSelectionService.java @@ -90,7 +90,7 @@ import java.util.function.Consumer; */ @SystemApi @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) -public class DomainSelectionService extends Service { +public abstract class DomainSelectionService extends Service { private static final String LOG_TAG = "DomainSelectionService"; @@ -152,7 +152,7 @@ public class DomainSelectionService extends Service { private boolean mIsExitedFromAirplaneMode; private @Nullable ImsReasonInfo mImsReasonInfo; private @PreciseDisconnectCauses int mCause; - private @Nullable EmergencyRegResult mEmergencyRegResult; + private @Nullable EmergencyRegistrationResult mEmergencyRegistrationResult; /** * @param slotIndex The logical slot index. @@ -172,7 +172,7 @@ public class DomainSelectionService extends Service { @Nullable Uri address, @SelectorType int selectorType, boolean video, boolean emergency, boolean isTest, boolean exited, @Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause, - @Nullable EmergencyRegResult regResult) { + @Nullable EmergencyRegistrationResult regResult) { mSlotIndex = slotIndex; mSubId = subscriptionId; mCallId = callId; @@ -184,7 +184,7 @@ public class DomainSelectionService extends Service { mIsExitedFromAirplaneMode = exited; mImsReasonInfo = imsReasonInfo; mCause = cause; - mEmergencyRegResult = regResult; + mEmergencyRegistrationResult = regResult; } /** @@ -204,7 +204,7 @@ public class DomainSelectionService extends Service { mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode; mImsReasonInfo = s.mImsReasonInfo; mCause = s.mCause; - mEmergencyRegResult = s.mEmergencyRegResult; + mEmergencyRegistrationResult = s.mEmergencyRegistrationResult; } /** @@ -296,8 +296,8 @@ public class DomainSelectionService extends Service { /** * @return The current registration state of cellular network. */ - public @Nullable EmergencyRegResult getEmergencyRegResult() { - return mEmergencyRegResult; + public @Nullable EmergencyRegistrationResult getEmergencyRegistrationResult() { + return mEmergencyRegistrationResult; } @Override @@ -313,7 +313,7 @@ public class DomainSelectionService extends Service { + ", airplaneMode=" + mIsExitedFromAirplaneMode + ", reasonInfo=" + mImsReasonInfo + ", cause=" + mCause - + ", regResult=" + mEmergencyRegResult + + ", regResult=" + mEmergencyRegistrationResult + " }"; } @@ -331,14 +331,15 @@ public class DomainSelectionService extends Service { && mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode && equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo) && mCause == that.mCause - && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult); + && equalsHandlesNulls(mEmergencyRegistrationResult, + that.mEmergencyRegistrationResult); } @Override public int hashCode() { return Objects.hash(mCallId, mAddress, mImsReasonInfo, mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber, mIsExitedFromAirplaneMode, - mEmergencyRegResult, mSlotIndex, mSubId, mSelectorType, mCause); + mEmergencyRegistrationResult, mSlotIndex, mSubId, mSelectorType, mCause); } @Override @@ -359,7 +360,7 @@ public class DomainSelectionService extends Service { out.writeBoolean(mIsExitedFromAirplaneMode); out.writeParcelable(mImsReasonInfo, 0); out.writeInt(mCause); - out.writeParcelable(mEmergencyRegResult, 0); + out.writeParcelable(mEmergencyRegistrationResult, 0); } private void readFromParcel(@NonNull Parcel in) { @@ -376,8 +377,9 @@ public class DomainSelectionService extends Service { mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(), android.telephony.ims.ImsReasonInfo.class); mCause = in.readInt(); - mEmergencyRegResult = in.readParcelable(EmergencyRegResult.class.getClassLoader(), - EmergencyRegResult.class); + mEmergencyRegistrationResult = in.readParcelable( + EmergencyRegistrationResult.class.getClassLoader(), + EmergencyRegistrationResult.class); } public static final @NonNull Creator<SelectionAttributes> CREATOR = @@ -413,7 +415,7 @@ public class DomainSelectionService extends Service { private boolean mIsExitedFromAirplaneMode; private @Nullable ImsReasonInfo mImsReasonInfo; private @PreciseDisconnectCauses int mCause; - private @Nullable EmergencyRegResult mEmergencyRegResult; + private @Nullable EmergencyRegistrationResult mEmergencyRegistrationResult; /** * Default constructor for Builder. @@ -430,7 +432,7 @@ public class DomainSelectionService extends Service { * @param callId The call identifier. * @return The same instance of the builder. */ - public @NonNull Builder setCallId(@NonNull String callId) { + public @NonNull Builder setCallId(@Nullable String callId) { mCallId = callId; return this; } @@ -441,7 +443,7 @@ public class DomainSelectionService extends Service { * @param address The dialed address. * @return The same instance of the builder. */ - public @NonNull Builder setAddress(@NonNull Uri address) { + public @NonNull Builder setAddress(@Nullable Uri address) { mAddress = address; return this; } @@ -497,7 +499,7 @@ public class DomainSelectionService extends Service { * @param info The reason why the last PS attempt failed. * @return The same instance of the builder. */ - public @NonNull Builder setPsDisconnectCause(@NonNull ImsReasonInfo info) { + public @NonNull Builder setPsDisconnectCause(@Nullable ImsReasonInfo info) { mImsReasonInfo = info; return this; } @@ -519,8 +521,9 @@ public class DomainSelectionService extends Service { * @param regResult The current registration result for emergency services. * @return The same instance of the builder. */ - public @NonNull Builder setEmergencyRegResult(@NonNull EmergencyRegResult regResult) { - mEmergencyRegResult = regResult; + public @NonNull Builder setEmergencyRegistrationResult( + @Nullable EmergencyRegistrationResult regResult) { + mEmergencyRegistrationResult = regResult; return this; } @@ -532,7 +535,7 @@ public class DomainSelectionService extends Service { return new SelectionAttributes(mSlotIndex, mSubId, mCallId, mAddress, mSelectorType, mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber, mIsExitedFromAirplaneMode, mImsReasonInfo, - mCause, mEmergencyRegResult); + mCause, mEmergencyRegistrationResult); } } } @@ -697,7 +700,7 @@ public class DomainSelectionService extends Service { public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks, @EmergencyScanType int scanType, boolean resetScan, @NonNull CancellationSignal signal, - @NonNull Consumer<EmergencyRegResult> consumer) { + @NonNull Consumer<EmergencyRegistrationResult> consumer) { try { if (signal != null) signal.setOnCancelListener(this); mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor); @@ -721,17 +724,18 @@ public class DomainSelectionService extends Service { private class IWwanSelectorResultCallbackAdapter extends IWwanSelectorResultCallback.Stub { - private final @NonNull Consumer<EmergencyRegResult> mConsumer; + private final @NonNull Consumer<EmergencyRegistrationResult> mConsumer; private final @NonNull Executor mExecutor; - IWwanSelectorResultCallbackAdapter(@NonNull Consumer<EmergencyRegResult> consumer, + IWwanSelectorResultCallbackAdapter( + @NonNull Consumer<EmergencyRegistrationResult> consumer, @NonNull Executor executor) { mConsumer = consumer; mExecutor = executor; } @Override - public void onComplete(@NonNull EmergencyRegResult result) { + public void onComplete(@NonNull EmergencyRegistrationResult result) { if (mConsumer == null) return; executeMethodAsyncNoException(mExecutor, @@ -759,9 +763,8 @@ public class DomainSelectionService extends Service { * @param attr Required to determine the domain. * @param callback The callback instance being registered. */ - public void onDomainSelection(@NonNull SelectionAttributes attr, - @NonNull TransportSelectorCallback callback) { - } + public abstract void onDomainSelection(@NonNull SelectionAttributes attr, + @NonNull TransportSelectorCallback callback); /** * Notifies the change in {@link ServiceState} for a specific logical slot index. @@ -836,7 +839,7 @@ public class DomainSelectionService extends Service { /** @hide */ @Override - public @Nullable IBinder onBind(@Nullable Intent intent) { + public final @Nullable IBinder onBind(@Nullable Intent intent) { if (intent == null) return null; if (SERVICE_INTERFACE.equals(intent.getAction())) { Log.i(LOG_TAG, "DomainSelectionService Bound."); @@ -863,7 +866,7 @@ public class DomainSelectionService extends Service { * @return {@link Executor} instance. * @hide */ - public @NonNull Executor getCachedExecutor() { + public final @NonNull Executor getCachedExecutor() { synchronized (mExecutorLock) { if (mExecutor == null) { Executor e = onCreateExecutor(); diff --git a/telephony/java/android/telephony/EmergencyRegResult.aidl b/telephony/java/android/telephony/EmergencyRegistrationResult.aidl index f7229621c0c1..3056031d6f03 100644 --- a/telephony/java/android/telephony/EmergencyRegResult.aidl +++ b/telephony/java/android/telephony/EmergencyRegistrationResult.aidl @@ -16,4 +16,4 @@ package android.telephony; -parcelable EmergencyRegResult; +parcelable EmergencyRegistrationResult; diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegistrationResult.java index 15579be2d786..7041f5b3b556 100644 --- a/telephony/java/android/telephony/EmergencyRegResult.java +++ b/telephony/java/android/telephony/EmergencyRegistrationResult.java @@ -35,7 +35,7 @@ import java.util.Objects; */ @SystemApi @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) -public final class EmergencyRegResult implements Parcelable { +public final class EmergencyRegistrationResult implements Parcelable { /** * Indicates the cellular network type of the acquired system. @@ -101,7 +101,7 @@ public final class EmergencyRegResult implements Parcelable { * @param iso The ISO-3166-1 alpha-2 country code equivalent, empty string if unknown. * @hide */ - public EmergencyRegResult( + public EmergencyRegistrationResult( @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork, @NetworkRegistrationInfo.RegistrationState int regState, @NetworkRegistrationInfo.Domain int domain, @@ -125,7 +125,7 @@ public final class EmergencyRegResult implements Parcelable { * @param s Source emergency scan result * @hide */ - public EmergencyRegResult(@NonNull EmergencyRegResult s) { + public EmergencyRegistrationResult(@NonNull EmergencyRegistrationResult s) { mAccessNetworkType = s.mAccessNetworkType; mRegState = s.mRegState; mDomain = s.mDomain; @@ -139,9 +139,9 @@ public final class EmergencyRegResult implements Parcelable { } /** - * Construct a EmergencyRegResult object from the given parcel. + * Construct a EmergencyRegistrationResult object from the given parcel. */ - private EmergencyRegResult(@NonNull Parcel in) { + private EmergencyRegistrationResult(@NonNull Parcel in) { readFromParcel(in); } @@ -258,7 +258,7 @@ public final class EmergencyRegResult implements Parcelable { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - EmergencyRegResult that = (EmergencyRegResult) o; + EmergencyRegistrationResult that = (EmergencyRegistrationResult) o; return mAccessNetworkType == that.mAccessNetworkType && mRegState == that.mRegState && mDomain == that.mDomain @@ -311,16 +311,16 @@ public final class EmergencyRegResult implements Parcelable { mCountryIso = in.readString8(); } - public static final @NonNull Creator<EmergencyRegResult> CREATOR = - new Creator<EmergencyRegResult>() { - @Override - public EmergencyRegResult createFromParcel(@NonNull Parcel in) { - return new EmergencyRegResult(in); - } - - @Override - public EmergencyRegResult[] newArray(int size) { - return new EmergencyRegResult[size]; - } - }; + public static final @NonNull Creator<EmergencyRegistrationResult> CREATOR = + new Creator<EmergencyRegistrationResult>() { + @Override + public EmergencyRegistrationResult createFromParcel(@NonNull Parcel in) { + return new EmergencyRegistrationResult(in); + } + + @Override + public EmergencyRegistrationResult[] newArray(int size) { + return new EmergencyRegistrationResult[size]; + } + }; } diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java index ea83815c146d..b900af3a986b 100644 --- a/telephony/java/android/telephony/WwanSelectorCallback.java +++ b/telephony/java/android/telephony/WwanSelectorCallback.java @@ -48,7 +48,8 @@ public interface WwanSelectorCallback { */ void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks, @EmergencyScanType int scanType, boolean resetScan, - @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer); + @NonNull CancellationSignal signal, + @NonNull Consumer<EmergencyRegistrationResult> consumer); /** * Notifies the FW that the domain has been selected. After this method is called, diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 0fe43b3fbf71..7935d243397c 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.app.Activity; import android.app.PendingIntent; @@ -863,6 +864,10 @@ public class EuiccManager { */ public static final int ERROR_INVALID_PORT = 10017; + /** Temporary failure to retrieve available memory because eUICC is not ready. */ + @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY) + public static final long EUICC_MEMORY_FIELD_UNAVAILABLE = -1L; + /** * Apps targeting on Android T and beyond will get exception whenever switchToSubscription * without portIndex is called for disable subscription. @@ -963,6 +968,35 @@ public class EuiccManager { } /** + * Returns the available memory in bytes of the eUICC. + * + * @return the available memory in bytes. May be {@link #EUICC_MEMORY_FIELD_UNAVAILABLE} if the + * eUICC is not ready. Check {@link #isEnabled} for more information. + * @throws UnsupportedOperationException If the device does not have + * {@link PackageManager#FEATURE_TELEPHONY_EUICC} or + * device doesn't support querying this information from the eUICC. + */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY) + @RequiresPermission( + anyOf = { + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + "carrier privileges" + }) + public long getAvailableMemoryInBytes() { + if (!isEnabled()) { + return EUICC_MEMORY_FIELD_UNAVAILABLE; + } + try { + return getIEuiccController() + .getAvailableMemoryInBytes(mCardId, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the current status of eUICC OTA. * * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. diff --git a/telephony/java/android/telephony/satellite/EnableRequestAttributes.java b/telephony/java/android/telephony/satellite/EnableRequestAttributes.java new file mode 100644 index 000000000000..bc9d23081214 --- /dev/null +++ b/telephony/java/android/telephony/satellite/EnableRequestAttributes.java @@ -0,0 +1,145 @@ +/* + * 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.telephony.satellite; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.telephony.flags.Flags; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * EnableRequestAttributes is used to store the attributes of the request + * {@link SatelliteManager#requestEnabled(EnableRequestAttributes, Executor, Consumer)} + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +public class EnableRequestAttributes { + /** {@code true} to enable satellite and {@code false} to disable satellite */ + private boolean mIsEnabled; + /** + * {@code true} to enable demo mode and {@code false} to disable. When disabling satellite, + * {@code mIsDemoMode} is always considered as {@code false} by Telephony. + */ + private boolean mIsDemoMode; + /** + * {@code true} means satellite is enabled for emergency mode, {@code false} otherwise. When + * disabling satellite, {@code isEmergencyMode} is always considered as {@code false} by + * Telephony. + */ + private boolean mIsEmergencyMode; + + /** + * Constructor from builder. + * + * @param builder Builder of {@link EnableRequestAttributes}. + */ + private EnableRequestAttributes(@NonNull Builder builder) { + this.mIsEnabled = builder.mIsEnabled; + this.mIsDemoMode = builder.mIsDemoMode; + this.mIsEmergencyMode = builder.mIsEmergencyMode; + } + + /** + * @return Whether satellite is to be enabled + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public boolean isEnabled() { + return mIsEnabled; + } + + /** + * @return Whether demo mode is to be enabled + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public boolean isDemoMode() { + return mIsDemoMode; + } + + /** + * @return Whether satellite is to be enabled for emergency mode + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public boolean isEmergencyMode() { + return mIsEmergencyMode; + } + + /** + * The builder class of {@link EnableRequestAttributes} + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final class Builder { + private boolean mIsEnabled; + private boolean mIsDemoMode = false; + private boolean mIsEmergencyMode = false; + + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public Builder(boolean isEnabled) { + mIsEnabled = isEnabled; + } + + /** + * Set demo mode + * + * @param isDemoMode {@code true} to enable demo mode and {@code false} to disable. When + * disabling satellite, {@code isDemoMode} is always considered as + * {@code false} by Telephony. + * @return The builder object + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NonNull + public Builder setDemoMode(boolean isDemoMode) { + if (mIsEnabled) { + mIsDemoMode = isDemoMode; + } + return this; + } + + /** + * Set emergency mode + * + * @param isEmergencyMode {@code true} means satellite is enabled for emergency mode, + * {@code false} otherwise. When disabling satellite, + * {@code isEmergencyMode} is always considered as {@code false} by + * Telephony. + * @return The builder object + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NonNull + public Builder setEmergencyMode(boolean isEmergencyMode) { + if (mIsEnabled) { + mIsEmergencyMode = isEmergencyMode; + } + return this; + } + + /** + * Build the {@link EnableRequestAttributes} + * + * @return The {@link EnableRequestAttributes} instance. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @NonNull + public EnableRequestAttributes build() { + return new EnableRequestAttributes(this); + } + } +} diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index b97822a9d913..4a6111444f31 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -162,6 +162,13 @@ public final class SatelliteManager { /** * Bundle key to get the response from + * {@link #requestIsEmergencyModeEnabled(Executor, OutcomeReceiver)}. + * @hide + */ + public static final String KEY_EMERGENCY_MODE_ENABLED = "emergency_mode_enabled"; + + /** + * Bundle key to get the response from * {@link #requestIsSupported(Executor, OutcomeReceiver)}. * @hide */ @@ -341,6 +348,13 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; + /** + * Telephony framework timeout to receive ACK or response from the satellite modem after + * sending a request to the modem. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; + /** @hide */ @IntDef(prefix = {"SATELLITE_RESULT_"}, value = { SATELLITE_RESULT_SUCCESS, @@ -366,7 +380,8 @@ public final class SatelliteManager { SATELLITE_RESULT_NOT_SUPPORTED, SATELLITE_RESULT_REQUEST_IN_PROGRESS, SATELLITE_RESULT_MODEM_BUSY, - SATELLITE_RESULT_ILLEGAL_STATE + SATELLITE_RESULT_ILLEGAL_STATE, + SATELLITE_RESULT_MODEM_TIMEOUT }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteResult {} @@ -482,20 +497,18 @@ public final class SatelliteManager { * aligned with the satellite, user can send a message and also receive a reply in demo mode. * If enableSatellite is {@code false}, enableDemoMode has no impact on the behavior. * - * @param enableSatellite {@code true} to enable the satellite modem and - * {@code false} to disable. - * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable. + * @param attributes The attributes of the enable request. * @param executor The executor on which the error code listener will be called. * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) - public void requestEnabled(boolean enableSatellite, boolean enableDemoMode, + public void requestEnabled(@NonNull EnableRequestAttributes attributes, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { + Objects.requireNonNull(attributes); Objects.requireNonNull(executor); Objects.requireNonNull(resultListener); @@ -509,14 +522,17 @@ public final class SatelliteManager { () -> resultListener.accept(result))); } }; - telephony.requestSatelliteEnabled(mSubId, enableSatellite, enableDemoMode, - errorCallback); + telephony.requestSatelliteEnabled(mSubId, attributes.isEnabled(), + attributes.isDemoMode(), attributes.isEmergencyMode(), errorCallback); } else { - throw new IllegalStateException("telephony service is null."); + Rlog.e(TAG, "requestEnabled() invalid telephony"); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { - Rlog.e(TAG, "requestSatelliteEnabled() RemoteException: ", ex); - ex.rethrowAsRuntimeException(); + Rlog.e(TAG, "requestEnabled() exception: ", ex); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } @@ -566,12 +582,14 @@ public final class SatelliteManager { }; telephony.requestIsSatelliteEnabled(mSubId, receiver); } else { + loge("requestIsEnabled() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { - loge("requestIsSatelliteEnabled() RemoteException: " + ex); - ex.rethrowAsRuntimeException(); + loge("requestIsEnabled() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } @@ -621,11 +639,68 @@ public final class SatelliteManager { }; telephony.requestIsDemoModeEnabled(mSubId, receiver); } else { + loge("requestIsDemoModeEnabled() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestIsDemoModeEnabled() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); + } + } + + /** + * Request to get whether the satellite service is enabled for emergency mode. + * + * @param executor The executor on which the callback will be called. + * @param callback The callback object to which the result will be delivered. + * If the request is successful, {@link OutcomeReceiver#onResult(Object)} + * will return a {@code boolean} with value {@code true} if satellite is enabled + * for emergency mode and {@code false} otherwise. + * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} + * will return a {@link SatelliteException} with the {@link SatelliteResult}. + * + * @throws SecurityException if the caller doesn't have required permission. + */ + @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public void requestIsEmergencyModeEnabled(@NonNull @CallbackExecutor Executor executor, + @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == SATELLITE_RESULT_SUCCESS) { + if (resultData.containsKey(KEY_EMERGENCY_MODE_ENABLED)) { + boolean isEmergencyModeEnabled = + resultData.getBoolean(KEY_EMERGENCY_MODE_ENABLED); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onResult(isEmergencyModeEnabled))); + } else { + loge("KEY_EMERGENCY_MODE_ENABLED does not exist."); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException( + SATELLITE_RESULT_REQUEST_FAILED)))); + } + } else { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onError(new SatelliteException(resultCode)))); + } + } + }; + telephony.requestIsEmergencyModeEnabled(mSubId, receiver); + } else { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); + } + } catch (RemoteException ex) { + loge("requestIsEmergencyModeEnabled() RemoteException: " + ex); ex.rethrowAsRuntimeException(); } } @@ -678,12 +753,14 @@ public final class SatelliteManager { }; telephony.requestIsSatelliteSupported(mSubId, receiver); } else { + loge("requestIsSupported() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { - loge("requestIsSatelliteSupported() RemoteException: " + ex); - ex.rethrowAsRuntimeException(); + loge("requestIsSupported() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } @@ -733,12 +810,14 @@ public final class SatelliteManager { }; telephony.requestSatelliteCapabilities(mSubId, receiver); } else { + loge("requestCapabilities() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { - loge("requestSatelliteCapabilities() RemoteException: " + ex); - ex.rethrowAsRuntimeException(); + loge("requestCapabilities() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } @@ -1014,12 +1093,14 @@ public final class SatelliteManager { telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback, internalCallback); } else { + loge("startTransmissionUpdates() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { - loge("startSatelliteTransmissionUpdates() RemoteException: " + ex); - ex.rethrowAsRuntimeException(); + loge("startTransmissionUpdates() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } @@ -1069,12 +1150,14 @@ public final class SatelliteManager { () -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS))); } } else { + loge("stopTransmissionUpdates() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { - loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex); - ex.rethrowAsRuntimeException(); + loge("stopTransmissionUpdates() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } @@ -1092,7 +1175,6 @@ public final class SatelliteManager { * @param resultListener Listener for the {@link SatelliteResult} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. - * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1119,12 +1201,14 @@ public final class SatelliteManager { cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData, errorCallback); } else { + loge("provisionService() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { - loge("provisionSatelliteService() RemoteException=" + ex); - ex.rethrowAsRuntimeException(); + loge("provisionService() RemoteException=" + ex); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } if (cancellationSignal != null) { cancellationSignal.setRemote(cancelRemote); @@ -1168,12 +1252,14 @@ public final class SatelliteManager { }; telephony.deprovisionSatelliteService(mSubId, token, errorCallback); } else { + loge("deprovisionService() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { - loge("deprovisionSatelliteService() RemoteException=" + ex); - ex.rethrowAsRuntimeException(); + loge("deprovisionService() RemoteException ex=" + ex); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } @@ -1215,7 +1301,7 @@ public final class SatelliteManager { throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { - loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex); + loge("registerForProvisionStateChanged() RemoteException: " + ex); ex.rethrowAsRuntimeException(); } return SATELLITE_RESULT_REQUEST_FAILED; @@ -1302,12 +1388,14 @@ public final class SatelliteManager { }; telephony.requestIsSatelliteProvisioned(mSubId, receiver); } else { + loge("requestIsProvisioned() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { - loge("requestIsSatelliteProvisioned() RemoteException: " + ex); - ex.rethrowAsRuntimeException(); + loge("requestIsProvisioned() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } @@ -1347,7 +1435,7 @@ public final class SatelliteManager { throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { - loge("registerForSatelliteModemStateChanged() RemoteException:" + ex); + loge("registerForModemStateChanged() RemoteException:" + ex); ex.rethrowAsRuntimeException(); } return SATELLITE_RESULT_REQUEST_FAILED; @@ -1516,12 +1604,14 @@ public final class SatelliteManager { }; telephony.pollPendingDatagrams(mSubId, internalCallback); } else { + loge("pollPendingDatagrams() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("pollPendingDatagrams() RemoteException:" + ex); - ex.rethrowAsRuntimeException(); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } @@ -1573,12 +1663,14 @@ public final class SatelliteManager { telephony.sendDatagram(mSubId, datagramType, datagram, needFullScreenPointingUI, internalCallback); } else { + loge("sendDatagram() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("sendDatagram() RemoteException:" + ex); - ex.rethrowAsRuntimeException(); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } @@ -1628,16 +1720,16 @@ public final class SatelliteManager { } } }; - telephony.requestIsCommunicationAllowedForCurrentLocation(mSubId, - receiver); + telephony.requestIsCommunicationAllowedForCurrentLocation(mSubId, receiver); } else { + loge("requestIsCommunicationAllowedForCurrentLocation() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { - loge("requestIsCommunicationAllowedForCurrentLocation() RemoteException: " - + ex); - ex.rethrowAsRuntimeException(); + loge("requestIsCommunicationAllowedForCurrentLocation() RemoteException: " + ex); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } @@ -1688,12 +1780,14 @@ public final class SatelliteManager { }; telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver); } else { + loge("requestTimeForNextSatelliteVisibility() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex); - ex.rethrowAsRuntimeException(); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } @@ -1720,7 +1814,7 @@ public final class SatelliteManager { throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { - loge("informDeviceAlignedToSatellite() RemoteException:" + ex); + loge("setDeviceAlignedWithSatellite() RemoteException:" + ex); ex.rethrowAsRuntimeException(); } } @@ -1830,12 +1924,14 @@ public final class SatelliteManager { }; telephony.addAttachRestrictionForCarrier(subId, reason, errorCallback); } else { + loge("addAttachRestrictionForCarrier() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("addAttachRestrictionForCarrier() RemoteException:" + ex); - ex.rethrowAsRuntimeException(); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } @@ -1873,12 +1969,14 @@ public final class SatelliteManager { }; telephony.removeAttachRestrictionForCarrier(subId, reason, errorCallback); } else { + loge("removeAttachRestrictionForCarrier() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity( () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } catch (RemoteException ex) { loge("removeAttachRestrictionForCarrier() RemoteException:" + ex); - ex.rethrowAsRuntimeException(); + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE))); } } @@ -1939,10 +2037,7 @@ public final class SatelliteManager { * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no * signal strength data available. * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a - * {@link SatelliteException} with the {@link SatelliteResult}, or return a - * {@link IllegalStateException} if the Telephony process is not currently available or - * satellite is not supported, or return a {@link RuntimeException} when remote procedure call - * has failed. + * {@link SatelliteException} with the {@link SatelliteResult}. * * @throws SecurityException if the caller doesn't have required permission. */ @@ -1980,12 +2075,14 @@ public final class SatelliteManager { }; telephony.requestNtnSignalStrength(mSubId, receiver); } else { + loge("requestNtnSignalStrength() invalid telephony"); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } catch (RemoteException ex) { loge("requestNtnSignalStrength() RemoteException: " + ex); - ex.rethrowAsRuntimeException(); + executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError( + new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE)))); } } @@ -2187,14 +2284,11 @@ public final class SatelliteManager { return new ArrayList<>(); } - private static ITelephony getITelephony() { + @Nullable private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer .getTelephonyServiceManager() .getTelephonyServiceRegisterer() .get()); - if (binder == null) { - throw new RuntimeException("Could not find Telephony Service."); - } return binder; } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 213fbc55d597..bd47b1fc2dc0 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2742,14 +2742,19 @@ interface ITelephony { * Request to enable or disable the satellite modem. * * @param subId The subId of the subscription to enable or disable the satellite modem for. - * @param enable True to enable the satellite modem and false to disable. - * @param isDemoModeEnabled True if demo mode is enabled and false otherwise. + * @param enableSatellite True to enable the satellite modem and false to disable. + * @param enableDemoMode True if demo mode is enabled and false otherwise. When + * disabling satellite, {@code enableDemoMode} is always considered as + * {@code false} by Telephony. + * @param isEmergency {@code true} means satellite is enabled for emergency mode, {@code false} + * otherwise. When disabling satellite, {@code isEmergency} is always + * considered as {@code false} by Telephony. * @param callback The callback to get the result of the request. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void requestSatelliteEnabled(int subId, boolean enable, boolean isDemoModeEnabled, - in IIntegerConsumer callback); + void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode, + boolean isEmergency, in IIntegerConsumer callback); /** * Request to get whether the satellite modem is enabled. @@ -2775,6 +2780,18 @@ interface ITelephony { void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver); /** + * Request to get whether the satellite service is enabled with emergency mode. + * + * @param subId The subId of the subscription to request whether the satellite demo mode is + * enabled for. + * @param receiver Result receiver to get the error code of the request and whether the + * satellite is enabled with emergency mode. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.SATELLITE_COMMUNICATION)") + void requestIsEmergencyModeEnabled(int subId, in ResultReceiver receiver); + + /** * Request to get whether the satellite service is supported on the device. * * @param subId The subId of the subscription to check whether satellite is supported for. diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl index 0d61fcbb266e..091974a30184 100644 --- a/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl +++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl @@ -16,8 +16,8 @@ package com.android.internal.telephony; -import android.telephony.EmergencyRegResult; +import android.telephony.EmergencyRegistrationResult; oneway interface IWwanSelectorResultCallback { - void onComplete(in EmergencyRegResult result); + void onComplete(in EmergencyRegistrationResult result); } diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index d41777256b64..053bc7d0eece 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -59,4 +59,5 @@ interface IEuiccController { boolean isCompatChangeEnabled(String callingPackage, long changeId); void setPsimConversionSupportedCarriers(in int[] carrierIds); boolean isPsimConversionSupported(in int carrierId); + long getAvailableMemoryInBytes(int cardId, String callingPackage); } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt index 73cc2f2b4d18..f628af14a0b9 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt @@ -340,6 +340,14 @@ open class PipAppHelper(instrumentation: Instrumentation) : wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify() } + open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) { + val windowRect = getWindowRect(wmHelper) + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + // search and interact with the dismiss button + val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") + uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) + } + /** Close the pip window by pressing the expand button */ fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) { val windowRect = getWindowRect(wmHelper) |