diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 1c9785f34b79f70a506eb7c162029f1b514433f0..c8046ab5bb0e5f0994f0a084251ebe01eb4df978 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -22,6 +22,7 @@ aconfig_srcjars = [ ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", + ":android.service.notification.flags-aconfig-java{.generated_srcjars}", ":android.view.flags-aconfig-java{.generated_srcjars}", ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}", ":camera_platform_flags_core_java_lib{.generated_srcjars}", @@ -151,6 +152,11 @@ aconfig_declarations { java_aconfig_library { name: "android.nfc.flags-aconfig-java", aconfig_declarations: "android.nfc.flags-aconfig", + min_sdk_version: "VanillaIceCream", + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + ], defaults: ["framework-minus-apex-aconfig-java-defaults"], } @@ -288,6 +294,12 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +rust_aconfig_library { + name: "libandroid_security_flags_rust", + crate_name: "android_security_flags", + aconfig_declarations: "android.security.flags-aconfig", +} + // Package Manager aconfig_declarations { name: "android.content.pm.flags-aconfig", @@ -544,3 +556,16 @@ cc_aconfig_library { name: "device_policy_aconfig_flags_c_lib", aconfig_declarations: "device_policy_aconfig_flags", } + +// Notifications +aconfig_declarations { + name: "android.service.notification.flags-aconfig", + package: "android.service.notification", + srcs: ["core/java/android/service/notification/flags.aconfig"], +} + +java_aconfig_library { + name: "android.service.notification.flags-aconfig-java", + aconfig_declarations: "android.service.notification.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index 45bb161840692de730884ea7b522787ec5d8953c..e7adf203334e509c615dfbe626d3986cc02eaa21 100644 --- a/ProtoLibraries.bp +++ b/ProtoLibraries.bp @@ -77,6 +77,42 @@ gensrcs { output_extension: "proto.h", } +// ==== nfc framework java library ============================== +gensrcs { + name: "framework-nfc-javastream-protos", + + tools: [ + "aprotoc", + "protoc-gen-javastream", + "soong_zip", + ], + + cmd: "mkdir -p $(genDir)/$(in) " + + "&& $(location aprotoc) " + + " --plugin=$(location protoc-gen-javastream) " + + " --javastream_out=$(genDir)/$(in) " + + " -Iexternal/protobuf/src " + + " -I . " + + " $(in) " + + "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)", + + srcs: [ + "core/proto/android/app/pendingintent.proto", + "core/proto/android/content/component_name.proto", + "core/proto/android/content/intent.proto", + "core/proto/android/nfc/*.proto", + "core/proto/android/os/patternmatcher.proto", + "core/proto/android/os/persistablebundle.proto", + "core/proto/android/privacy.proto", + ], + + data: [ + ":libprotobuf-internal-protos", + ], + + output_extension: "srcjar", +} + // ==== java proto host library ============================== java_library_host { name: "platformprotos", diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index a143d6fd42507ae91118a3fbbccb765c4e63af3e..bbe1485ddcd49d2940256bd2498920da67b4d24c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1555,7 +1555,7 @@ public class JobSchedulerService extends com.android.server.SystemService private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive; - public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, + public int scheduleAsPackage(JobInfo job, JobWorkItem work, int callingUid, String packageName, int userId, @Nullable String namespace, String tag) { // Rate limit excessive schedule() calls. final String servicePkg = job.getService().getPackageName(); @@ -1608,12 +1608,12 @@ public class JobSchedulerService extends com.android.server.SystemService mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG); } - if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) { - Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString() + if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, servicePkg)) { + Slog.w(TAG, "Not scheduling job for " + callingUid + ":" + job.toString() + " -- package not allowed to start"); Counter.logIncrementWithUid( "job_scheduler.value_cntr_w_uid_schedule_failure_app_start_mode_disabled", - uId); + callingUid); return JobScheduler.RESULT_FAILURE; } @@ -1623,7 +1623,7 @@ public class JobSchedulerService extends com.android.server.SystemService job.getEstimatedNetworkDownloadBytes())); sInitialJobEstimatedNetworkUploadKBLogger.logSample( safelyScaleBytesToKBForHistogram(job.getEstimatedNetworkUploadBytes())); - sJobMinimumChunkKBLogger.logSampleWithUid(uId, + sJobMinimumChunkKBLogger.logSampleWithUid(callingUid, safelyScaleBytesToKBForHistogram(job.getMinimumNetworkChunkBytes())); if (work != null) { sInitialJwiEstimatedNetworkDownloadKBLogger.logSample( @@ -1632,7 +1632,7 @@ public class JobSchedulerService extends com.android.server.SystemService sInitialJwiEstimatedNetworkUploadKBLogger.logSample( safelyScaleBytesToKBForHistogram( work.getEstimatedNetworkUploadBytes())); - sJwiMinimumChunkKBLogger.logSampleWithUid(uId, + sJwiMinimumChunkKBLogger.logSampleWithUid(callingUid, safelyScaleBytesToKBForHistogram( work.getMinimumNetworkChunkBytes())); } @@ -1640,11 +1640,12 @@ public class JobSchedulerService extends com.android.server.SystemService if (work != null) { Counter.logIncrementWithUid( - "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", uId); + "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", callingUid); } synchronized (mLock) { - final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, namespace, job.getId()); + final JobStatus toCancel = + mJobs.getJobByUidAndJobId(callingUid, namespace, job.getId()); if (work != null && toCancel != null) { // Fast path: we are adding work to an existing job, and the JobInfo is not @@ -1664,7 +1665,7 @@ public class JobSchedulerService extends com.android.server.SystemService // TODO(273758274): improve JobScheduler's resilience and memory management if (toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS && toCancel.isPersisted()) { - Slog.w(TAG, "Too many JWIs for uid " + uId); + Slog.w(TAG, "Too many JWIs for uid " + callingUid); throw new IllegalStateException("Apps may not persist more than " + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS + " JobWorkItems per job"); @@ -1682,7 +1683,8 @@ public class JobSchedulerService extends com.android.server.SystemService | JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ); } mJobs.touchJob(toCancel); - sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, toCancel.getWorkCount()); + sEnqueuedJwiHighWaterMarkLogger + .logSampleWithUid(callingUid, toCancel.getWorkCount()); // If any of work item is enqueued when the source is in the foreground, // exempt the entire job. @@ -1692,8 +1694,8 @@ public class JobSchedulerService extends com.android.server.SystemService } } - JobStatus jobStatus = - JobStatus.createFromJobInfo(job, uId, packageName, userId, namespace, tag); + JobStatus jobStatus = JobStatus.createFromJobInfo( + job, callingUid, packageName, userId, namespace, tag); // Return failure early if expedited job quota used up. if (jobStatus.isRequestedExpeditedJob()) { @@ -1702,7 +1704,7 @@ public class JobSchedulerService extends com.android.server.SystemService && !mQuotaController.isWithinEJQuotaLocked(jobStatus))) { Counter.logIncrementWithUid( "job_scheduler.value_cntr_w_uid_schedule_failure_ej_out_of_quota", - uId); + callingUid); return JobScheduler.RESULT_FAILURE; } } @@ -1716,10 +1718,10 @@ public class JobSchedulerService extends com.android.server.SystemService if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString()); // Jobs on behalf of others don't apply to the per-app job cap if (packageName == null) { - if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) { - Slog.w(TAG, "Too many jobs for uid " + uId); + if (mJobs.countJobsForUid(callingUid) > MAX_JOBS_PER_APP) { + Slog.w(TAG, "Too many jobs for uid " + callingUid); Counter.logIncrementWithUid( - "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", uId); + "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", callingUid); throw new IllegalStateException("Apps may not schedule more than " + MAX_JOBS_PER_APP + " distinct jobs"); } @@ -1743,7 +1745,7 @@ public class JobSchedulerService extends com.android.server.SystemService // TODO(273758274): improve JobScheduler's resilience and memory management if (work != null && toCancel.isPersisted() && toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS) { - Slog.w(TAG, "Too many JWIs for uid " + uId); + Slog.w(TAG, "Too many JWIs for uid " + callingUid); throw new IllegalStateException("Apps may not persist more than " + mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS + " JobWorkItems per job"); @@ -1759,13 +1761,14 @@ public class JobSchedulerService extends com.android.server.SystemService if (work != null) { // If work has been supplied, enqueue it into the new job. jobStatus.enqueueWorkLocked(work); - sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, jobStatus.getWorkCount()); + sEnqueuedJwiHighWaterMarkLogger + .logSampleWithUid(callingUid, jobStatus.getWorkCount()); } - final int sourceUid = uId; + final int sourceUid = jobStatus.getSourceUid(); FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, jobStatus.isProxyJob() - ? new int[]{sourceUid, jobStatus.getUid()} : new int[]{sourceUid}, + ? new int[]{sourceUid, callingUid} : new int[]{sourceUid}, // Given that the source tag is set by the calling app, it should be connected // to the calling app in the attribution for a proxied job. jobStatus.isProxyJob() diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index d48d84ba6980e8ae4239364718b1360e7a564c01..1fdf906fd4ea7495582e3ae30e11db53360f7433 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -75,6 +75,7 @@ import java.util.StringJoiner; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.regex.Pattern; /** * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by @@ -99,6 +100,8 @@ public final class JobStore { private static final long SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS = 30 * 60_000L; @VisibleForTesting static final String JOB_FILE_SPLIT_PREFIX = "jobs_"; + private static final Pattern SPLIT_FILE_PATTERN = + Pattern.compile("^" + JOB_FILE_SPLIT_PREFIX + "\\d+.xml$"); private static final int ALL_UIDS = -1; @VisibleForTesting static final int INVALID_UID = -2; @@ -1121,6 +1124,11 @@ public final class JobStore { int numDuplicates = 0; synchronized (mLock) { for (File file : files) { + if (!file.getName().equals("jobs.xml") + && !SPLIT_FILE_PATTERN.matcher(file.getName()).matches()) { + // Skip temporary or other files. + continue; + } final AtomicFile aFile = createJobFile(file); try (FileInputStream fis = aFile.openRead()) { jobs = readJobMapImpl(fis, rtcGood, nowElapsed); diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index e1621008cc33800742309eca7052dcbe97793057..e086bfe5cbb2ca478e92608a7686458f13c4c418 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -139,9 +139,22 @@ droidstubs { // using droiddoc ///////////////////////////////////////////////////////////////////// -framework_docs_only_args = " -android -manifest $(location :frameworks-base-core-AndroidManifest.xml) " + +// doclava contains checks for a few issues that are have been migrated to metalava. +// disable them in doclava, to avoid mistriggering or double triggering. +ignore_doclava_errors_checked_by_metalava = "" + + "-hide 111 " + // HIDDEN_SUPERCLASS + "-hide 113 " + // DEPRECATION_MISMATCH + "-hide 125 " + // REQUIRES_PERMISSION + "-hide 126 " + // BROADCAST_BEHAVIOR + "-hide 127 " + // SDK_CONSTANT + "-hide 128 " // TODO + +framework_docs_only_args = "-android " + + "-manifest $(location :frameworks-base-core-AndroidManifest.xml) " + "-metalavaApiSince " + - "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " + + "-werror " + + "-lerror " + + ignore_doclava_errors_checked_by_metalava + "-overview $(location :frameworks-base-java-overview) " + // Federate Support Library references against local API file. "-federate SupportLib https://developer.android.com " + diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 7e41660cf1a2488d21bc7ca083b99da3cbecd699..7e78185b2659153e4d47fe358340464281d136b3 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -739,7 +739,9 @@ java_api_library { "android_stubs_current_contributions", "android_system_stubs_current_contributions", "android_test_frameworks_core_stubs_current_contributions", - "stub-annotation-defaults", + ], + libs: [ + "stub-annotations", ], api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", diff --git a/core/api/current.txt b/core/api/current.txt index 66aeb0f7cbafb1ab718f38ad514778cd1b11edf9..7d5417d985f0fa27633bbddb6c8b2f504efb1681 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -668,6 +668,7 @@ package android { field public static final int debuggable = 16842767; // 0x101000f field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562 field public static final int defaultHeight = 16844021; // 0x10104f5 + field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale; field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504 field public static final int defaultValue = 16843245; // 0x10101ed field public static final int defaultWidth = 16844020; // 0x10104f4 @@ -6188,6 +6189,7 @@ package android.app { ctor public LocaleConfig(@NonNull android.os.LocaleList); method public int describeContents(); method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context); + method @FlaggedApi("android.content.res.default_locale") @Nullable public java.util.Locale getDefaultLocale(); method public int getStatus(); method @Nullable public android.os.LocaleList getSupportedLocales(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -9530,7 +9532,8 @@ package android.companion { method @Nullable public CharSequence getDisplayName(); method public int getId(); method public int getSystemDataSyncFlags(); - method @Nullable public String getTag(); + method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag(); + method public boolean isSelfManaged(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR; } @@ -9600,7 +9603,7 @@ package android.companion { method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException; method @Nullable public android.content.IntentSender buildAssociationCancellationIntent(); method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException; - method public void clearAssociationTag(int); + method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int); method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException; method public void disableSystemDataSyncForTypes(int, int); method @Deprecated public void disassociate(@NonNull String); @@ -9610,7 +9613,7 @@ package android.companion { method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations(); method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName); method public void requestNotificationAccess(android.content.ComponentName); - method public void setAssociationTag(int, @NonNull String); + method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException; method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; @@ -11719,7 +11722,7 @@ package android.content.om { method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String); method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String); method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String); - method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String); + method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String); method public void setTargetOverlayable(@Nullable String); } @@ -12675,6 +12678,7 @@ package android.content.pm { method public boolean isDeviceUpgrading(); method public abstract boolean isInstantApp(); method public abstract boolean isInstantApp(@NonNull String); + method @FlaggedApi("android.content.pm.quarantined_enabled") public boolean isPackageQuarantined(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean isPackageSuspended(); @@ -12913,6 +12917,7 @@ package android.content.pm { field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000 field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200 field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000 + field @FlaggedApi("android.content.pm.quarantined_enabled") public static final long MATCH_QUARANTINED_COMPONENTS = 4294967296L; // 0x100000000L field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000 field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000 field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L @@ -14352,14 +14357,14 @@ package android.database.sqlite { public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable { method public void beginTransaction(); method public void beginTransactionNonExclusive(); - method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly(); + method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionReadOnly(); method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener); method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener); - method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener); + method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener); method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException; method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory); method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams); - method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String); + method @FlaggedApi("android.database.sqlite.sqlite_apis_35") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String); method public int delete(@NonNull String, @Nullable String, @Nullable String[]); method public static boolean deleteDatabase(@NonNull java.io.File); method public void disableWriteAheadLogging(); @@ -14370,13 +14375,13 @@ package android.database.sqlite { method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException; method public static String findEditTable(String); method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs(); - method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount(); - method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId(); + method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastChangedRowCount(); + method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastInsertRowId(); method public long getMaximumSize(); method public long getPageSize(); method public String getPath(); method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables(); - method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount(); + method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getTotalChangedRowCount(); method public int getVersion(); method public boolean inTransaction(); method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues); @@ -14598,7 +14603,7 @@ package android.database.sqlite { method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]); } - @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable { + @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable { method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException; method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException; method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException; @@ -18547,12 +18552,12 @@ package android.hardware.biometrics { ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential); ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession); ctor @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") public BiometricPrompt.CryptoObject(@NonNull javax.crypto.KeyAgreement); - method public javax.crypto.Cipher getCipher(); + method @Nullable public javax.crypto.Cipher getCipher(); method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential(); method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement(); - method public javax.crypto.Mac getMac(); + method @Nullable public javax.crypto.Mac getMac(); method @Nullable public android.security.identity.PresentationSession getPresentationSession(); - method public java.security.Signature getSignature(); + method @Nullable public java.security.Signature getSignature(); } } @@ -23775,6 +23780,8 @@ package android.media { field public static final int TYPE_DOCK = 13; // 0xd field public static final int TYPE_GROUP = 2000; // 0x7d0 field public static final int TYPE_HDMI = 9; // 0x9 + field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_ARC = 10; // 0xa + field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_EARC = 29; // 0x1d field public static final int TYPE_HEARING_AID = 23; // 0x17 field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb field public static final int TYPE_REMOTE_CAR = 1008; // 0x3f0 @@ -45702,6 +45709,7 @@ package android.telephony.data { field public static final int TYPE_IMS = 64; // 0x40 field public static final int TYPE_MCX = 1024; // 0x400 field public static final int TYPE_MMS = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000 field public static final int TYPE_SUPL = 4; // 0x4 field public static final int TYPE_VSIM = 4096; // 0x1000 field public static final int TYPE_XCAP = 2048; // 0x800 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5d1f6dc3e675d3ce507c41a497fac6e883d44ab1..ac61107dd75274c9af03ed5a520ee3e2d79babcd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3149,7 +3149,6 @@ package android.companion { public final class AssociationInfo implements android.os.Parcelable { method @NonNull public String getPackageName(); - method public boolean isSelfManaged(); } public final class CompanionDeviceManager { @@ -9538,7 +9537,7 @@ package android.net.wifi.sharedconnectivity.app { method public int getDeviceType(); method @NonNull public android.os.Bundle getExtras(); method @NonNull public String getModelName(); - method public boolean isBatteryCharging(); + method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR; field public static final int DEVICE_TYPE_AUTO = 5; // 0x5 @@ -9552,7 +9551,7 @@ package android.net.wifi.sharedconnectivity.app { public static final class NetworkProviderInfo.Builder { ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build(); - method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean); + method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String); @@ -9641,6 +9640,7 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener); field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff @@ -10482,7 +10482,7 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle); - method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible(); + method public boolean isUserVisible(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle); @@ -14717,6 +14717,7 @@ package android.telephony.data { field public static final String TYPE_IMS_STRING = "ims"; field public static final String TYPE_MCX_STRING = "mcx"; field public static final String TYPE_MMS_STRING = "mms"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs"; field public static final String TYPE_SUPL_STRING = "supl"; field public static final String TYPE_VSIM_STRING = "vsim"; field public static final String TYPE_XCAP_STRING = "xcap"; diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 4fb7b6bddaaced0a06f1157ba5c5c6713645f81b..a5010312e764644e3c5daca9e98b0fea18248744 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -251,10 +251,6 @@ UnflaggedApi: android.media.audiopolicy.AudioMixingRule#writeToParcel(android.os New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioMixingRule.writeToParcel(android.os.Parcel,int) UnflaggedApi: android.media.audiopolicy.AudioPolicy#updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>): New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioPolicy.updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>) -UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo#isBatteryCharging(): - New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.isBatteryCharging() -UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder#setBatteryCharging(boolean): - New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder.setBatteryCharging(boolean) UnflaggedApi: android.nfc.cardemulation.AidGroup#CONTENTS_FILE_DESCRIPTOR: New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.AidGroup.CONTENTS_FILE_DESCRIPTOR UnflaggedApi: android.nfc.cardemulation.AidGroup#PARCELABLE_WRITE_RETURN_VALUE: diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 306bdeb84e4d17e0306d6584b9398dca1943352d..03a58bed3389bbb949d52f1ac0c6c13b746523a0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -851,7 +851,7 @@ package android.companion { method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean); method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean); method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int); - method @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String); + method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String); method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long); } @@ -2117,7 +2117,7 @@ package android.net.wifi.sharedconnectivity.app { public class SharedConnectivityManager { method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String); - method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver(); + method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver(); method @Nullable public android.content.ServiceConnection getServiceConnection(); method public void setService(@Nullable android.os.IInterface); } @@ -2148,7 +2148,7 @@ package android.os { } public final class BugreportParams { - field public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7 + field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7 } public class Build { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 93e39d5f41f7fada46461f39c635d29e6124bf2f..105e7645ac4bd73eddfa2b023db8ec516882829a 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -257,8 +257,6 @@ UnflaggedApi: android.media.soundtrigger.SoundTriggerManager#loadSoundModel(andr New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel) UnflaggedApi: android.media.soundtrigger.SoundTriggerManager.Model#getSoundModel(): New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.Model.getSoundModel() -UnflaggedApi: android.net.wifi.sharedconnectivity.app.SharedConnectivityManager#getBroadcastReceiver(): - New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.SharedConnectivityManager.getBroadcastReceiver() UnflaggedApi: android.os.BatteryManager#BATTERY_PLUGGED_ANY: New API must be flagged with @FlaggedApi: field android.os.BatteryManager.BATTERY_PLUGGED_ANY UnflaggedApi: android.os.BugreportParams#BUGREPORT_MODE_MAX_VALUE: diff --git a/core/java/Android.bp b/core/java/Android.bp index 0293f66061c148e8d0efd36d7b0da1101b82cdfa..ddb221f422d9048b42cfe59d6a8f1a6b7fef2122 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -14,6 +14,15 @@ aidl_library { hdrs: ["android/hardware/HardwareBuffer.aidl"], } +// TODO (b/303286040): Remove this once |ENABLE_NFC_MAINLINE_FLAG| is rolled out +filegroup { + name: "framework-core-nfc-infcadapter-sources", + srcs: [ + "android/nfc/INfcAdapter.aidl", + ], + visibility: ["//frameworks/base/services/core"], +} + filegroup { name: "framework-core-sources", srcs: [ diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index a538247998e6c3cb4f0265dad36002feeb9e5ef2..08c18c8b7448c34f820f391de912395e1c47bbfb 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -3483,12 +3483,10 @@ class ContextImpl extends Context { // only do this if the user already has more than one preferred locale if (r.getConfiguration().getLocales().size() > 1) { - LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this); - mResourcesManager.setLocaleList(lc != null - && lc.getSupportedLocales() != null - && !lc.getSupportedLocales().isEmpty() - ? lc.getSupportedLocales() - : null); + LocaleConfig lc = getUserId() < 0 + ? LocaleConfig.fromContextIgnoringOverride(this) + : new LocaleConfig(this); + mResourcesManager.setLocaleConfig(lc); } } diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index a3b82e9356734d7c6169424c0f8bd82977c04e66..d7d654672abc2ec9d2af285f4970860512930d69 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -161,12 +161,6 @@ interface IWallpaperManager { */ boolean isWallpaperBackupEligible(int which, int userId); - /* - * Keyguard: register a callback for being notified that lock-state relevant - * wallpaper content has changed. - */ - boolean setLockWallpaperCallback(IWallpaperManagerCallback cb); - /** * Returns the colors used by the lock screen or system wallpaper. * @@ -252,13 +246,6 @@ interface IWallpaperManager { */ boolean isStaticWallpaper(int which); - /** - * Temporary method for project b/197814683. - * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image. - * @hide - */ - boolean isLockscreenLiveWallpaperEnabled(); - /** * Temporary method for project b/270726737. * Return true if the wallpaper supports different crops for different display dimensions. diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 1fdc51687433bd663a790ddac5da09966af36ab5..369a78144fd39017c2ce709b7bf1e527d5288bb9 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -16,9 +16,11 @@ package android.app; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -30,6 +32,7 @@ import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; +import com.android.internal.R; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParserException; @@ -39,7 +42,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; @@ -66,6 +69,8 @@ public class LocaleConfig implements Parcelable { public static final String TAG_LOCALE_CONFIG = "locale-config"; public static final String TAG_LOCALE = "locale"; private LocaleList mLocales; + + private Locale mDefaultLocale; private int mStatus = STATUS_NOT_SPECIFIED; /** @@ -193,8 +198,17 @@ public class LocaleConfig implements Parcelable { XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG); int outerDepth = parser.getDepth(); AttributeSet attrs = Xml.asAttributeSet(parser); - // LinkedHashSet to preserve insertion order - Set<String> localeNames = new LinkedHashSet<>(); + + String defaultLocale = null; + if (android.content.res.Flags.defaultLocale()) { + TypedArray att = res.obtainAttributes( + attrs, com.android.internal.R.styleable.LocaleConfig); + defaultLocale = att.getString( + R.styleable.LocaleConfig_defaultLocale); + att.recycle(); + } + + Set<String> localeNames = new HashSet<>(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (TAG_LOCALE.equals(parser.getName())) { final TypedArray attributes = res.obtainAttributes( @@ -209,6 +223,15 @@ public class LocaleConfig implements Parcelable { } mStatus = STATUS_SUCCESS; mLocales = LocaleList.forLanguageTags(String.join(",", localeNames)); + if (defaultLocale != null) { + if (localeNames.contains(defaultLocale)) { + mDefaultLocale = Locale.forLanguageTag(defaultLocale); + } else { + Slog.w(TAG, "Default locale specified that is not contained in the list: " + + defaultLocale); + mStatus = STATUS_PARSING_FAILED; + } + } } /** @@ -223,6 +246,17 @@ public class LocaleConfig implements Parcelable { return mLocales; } + /** + * Returns the default locale if specified, otherwise null + * + * @return The default Locale or null + */ + @SuppressLint("UseIcu") + @FlaggedApi(android.content.res.Flags.FLAG_DEFAULT_LOCALE) + public @Nullable Locale getDefaultLocale() { + return mDefaultLocale; + } + /** * Get the status of reading the resource file where the LocaleConfig was stored. * diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 1ecb5d33fba2cfa115c952ba8b608fa67fc7e64e..6009c29ae53cedde82383b12e1f08862db7459e5 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -120,9 +120,9 @@ public class ResourcesManager { private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>(); /** - * The list of locales the app declares it supports. + * The localeConfig of the app. */ - private LocaleList mLocaleList = LocaleList.getEmptyLocaleList(); + private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList()); private static class ApkKey { public final String path; @@ -1612,18 +1612,19 @@ public class ResourcesManager { } /** - * Returns the LocaleList current set + * Returns the LocaleConfig current set */ - public LocaleList getLocaleList() { - return mLocaleList; + public LocaleConfig getLocaleConfig() { + return mLocaleConfig; } /** - * Sets the LocaleList of app's supported locales + * Sets the LocaleConfig of the app */ - public void setLocaleList(LocaleList localeList) { - if ((localeList != null) && !localeList.isEmpty()) { - mLocaleList = localeList; + public void setLocaleConfig(LocaleConfig localeConfig) { + if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null) + && !localeConfig.getSupportedLocales().isEmpty()) { + mLocaleConfig = localeConfig; } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index e7a5b72eafc56e3f4ce1a53c123a45d62493a06a..d660078a9ae79fd6e36010544161c27f5335fe96 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -313,7 +313,6 @@ public class WallpaperManager { private final Context mContext; private final boolean mWcgEnabled; private final ColorManagementProxy mCmProxy; - private static Boolean sIsLockscreenLiveWallpaperEnabled = null; private static Boolean sIsMultiCropEnabled = null; /** @@ -841,29 +840,14 @@ public class WallpaperManager { } /** + * TODO (b/305908217) remove * Temporary method for project b/197814683. * @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image * @hide */ @TestApi public boolean isLockscreenLiveWallpaperEnabled() { - return isLockscreenLiveWallpaperEnabledHelper(); - } - - private static boolean isLockscreenLiveWallpaperEnabledHelper() { - if (sGlobals == null) { - sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean( - "persist.wm.debug.lockscreen_live_wallpaper", true); - } - if (sIsLockscreenLiveWallpaperEnabled == null) { - try { - sIsLockscreenLiveWallpaperEnabled = - sGlobals.mService.isLockscreenLiveWallpaperEnabled(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - return sIsLockscreenLiveWallpaperEnabled; + return true; } /** @@ -2446,12 +2430,7 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void clearWallpaper() { - if (isLockscreenLiveWallpaperEnabled()) { - clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId()); - return; - } - clearWallpaper(FLAG_LOCK, mContext.getUserId()); - clearWallpaper(FLAG_SYSTEM, mContext.getUserId()); + clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId()); } /** @@ -2787,11 +2766,7 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void clear() throws IOException { - if (isLockscreenLiveWallpaperEnabled()) { - clear(FLAG_SYSTEM | FLAG_LOCK); - return; - } - setStream(openDefaultWallpaper(mContext, FLAG_SYSTEM), null, false); + clear(FLAG_SYSTEM | FLAG_LOCK); } /** @@ -2816,16 +2791,7 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void clear(@SetWallpaperFlags int which) throws IOException { - if (isLockscreenLiveWallpaperEnabled()) { - clearWallpaper(which, mContext.getUserId()); - return; - } - if ((which & FLAG_SYSTEM) != 0) { - clear(); - } - if ((which & FLAG_LOCK) != 0) { - clearWallpaper(FLAG_LOCK, mContext.getUserId()); - } + clearWallpaper(which, mContext.getUserId()); } /** @@ -2840,16 +2806,12 @@ public class WallpaperManager { public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) { final String whichProp; final int defaultResId; - if (which == FLAG_LOCK && !isLockscreenLiveWallpaperEnabledHelper()) { - /* Factory-default lock wallpapers are not yet supported - whichProp = PROP_LOCK_WALLPAPER; - defaultResId = com.android.internal.R.drawable.default_lock_wallpaper; - */ - return null; - } else { - whichProp = PROP_WALLPAPER; - defaultResId = com.android.internal.R.drawable.default_wallpaper; - } + /* Factory-default lock wallpapers are not yet supported. + whichProp = which == FLAG_LOCK ? PROP_LOCK_WALLPAPER : PROP_WALLPAPER; + defaultResId = which == FLAG_LOCK ? R.drawable.default_lock_wallpaper : .... + */ + whichProp = PROP_WALLPAPER; + defaultResId = R.drawable.default_wallpaper; final String path = SystemProperties.get(whichProp); final InputStream wallpaperInputStream = getWallpaperInputStream(path); if (wallpaperInputStream != null) { @@ -2987,25 +2949,6 @@ public class WallpaperManager { return true; } - /** - * Register a callback for lock wallpaper observation. Only the OS may use this. - * - * @return true on success; false on error. - * @hide - */ - public boolean setLockWallpaperCallback(IWallpaperManagerCallback callback) { - if (sGlobals.mService == null) { - Log.w(TAG, "WallpaperService not running"); - throw new RuntimeException(new DeadSystemException()); - } - - try { - return sGlobals.mService.setLockWallpaperCallback(callback); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Is the current system wallpaper eligible for backup? * diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index c145c0255b0aa89bdb7bdcc1e1816d6e2752bde0..f99615fd2ef4f6652977a7f6e8ee7168731cbf4a 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -19,4 +19,11 @@ flag { namespace: "enterprise" description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports." bug: "302517677" -} \ No newline at end of file +} + +flag { + name: "cross_user_suspension_enabled" + namespace: "enterprise" + description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users." + bug: "263464464" +} diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index f401a7607364f0228250b20f8f77666d20dba393..4f1c65b1e3957935276f5f79e21f4b655a743485 100644 --- a/core/java/android/app/usage/flags.aconfig +++ b/core/java/android/app/usage/flags.aconfig @@ -13,3 +13,11 @@ flag { description: "Feature flag for the new REPORT_USAGE_STATS permission." bug: "296056771" } + +flag { + name: "use_dedicated_handler_thread" + namespace: "backstage_power" + description: "Flag to use a dedicated thread for usage event process" + is_fixed_read_only: true + bug: "299336442" +} diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 6393c456bdcdcecfa3f46aba0d7d1ccffba31705..161fa799f2f5fdb1c98b43b271567dcb1f7e33a3 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -144,6 +144,7 @@ public final class AssociationInfo implements Parcelable { * @return the tag of this association. * @see CompanionDeviceManager#setAssociationTag(int, String) */ + @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG) @Nullable public String getTag() { return mTag; @@ -205,9 +206,8 @@ public final class AssociationInfo implements Parcelable { /** * @return whether the association is managed by the companion application it belongs to. * @see AssociationRequest.Builder#setSelfManaged(boolean) - * @hide */ - @SystemApi + @SuppressLint("UnflaggedApi") // promoting from @SystemApi public boolean isSelfManaged() { return mSelfManaged; } @@ -459,6 +459,7 @@ public final class AssociationInfo implements Parcelable { } /** @hide */ + @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG) @TestApi @NonNull public Builder setTag(@Nullable String tag) { diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index dbc67fc5a3a3683777e6b1bee5b11ad781025010..70811bb329ec906c33411e4696ddaa2126071726 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -1435,6 +1435,7 @@ public final class CompanionDeviceManager { * of the companion device recorded by CompanionDeviceManager * @param tag the tag of this association */ + @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG) @UserHandleAware public void setAssociationTag(int associationId, @NonNull String tag) { Objects.requireNonNull(tag, "tag cannot be null"); @@ -1459,6 +1460,7 @@ public final class CompanionDeviceManager { * of the companion device recorded by CompanionDeviceManager * @see CompanionDeviceManager#setAssociationTag(int, String) */ + @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG) @UserHandleAware public void clearAssociationTag(int associationId) { try { diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig index 1b4234b5205ccb5484394f1563e558a844a55b96..4f9c849865fccba9a0f4b3306b66c8dec3984043 100644 --- a/core/java/android/companion/flags.aconfig +++ b/core/java/android/companion/flags.aconfig @@ -12,4 +12,11 @@ flag { namespace: "companion" description: "Grants access to the companion transport apis." bug: "288297505" +} + +flag { + name: "association_tag" + namespace: "companion" + description: "Enable Association tag APIs " + bug: "289241123" } \ No newline at end of file diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index bb9cc0bef6717b74a2388c415ee54533b76e0f4c..7b6bad31539af4030708e80f9436ab65954b6da7 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4291,6 +4291,14 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_SHOW_BRIGHTNESS_DIALOG = "com.android.intent.action.SHOW_BRIGHTNESS_DIALOG"; + /** + * Intent Extra: holds boolean that determines whether brightness dialog is full width when + * in landscape mode. + * @hide + */ + public static final String EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH = + "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH"; + /** * Activity Action: Shows the contrast setting dialog. * @hide diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java index c4547b8acc2b454392540a370262fdaabdab547f..df2d7e70880f31da4aaddb9c1557d25f17677756 100644 --- a/core/java/android/content/om/FabricatedOverlay.java +++ b/core/java/android/content/om/FabricatedOverlay.java @@ -16,6 +16,7 @@ package android.content.om; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -546,6 +547,7 @@ public class FabricatedOverlay { * @param configuration The string representation of the config this overlay is enabled for */ @NonNull + @FlaggedApi(android.content.res.Flags.FLAG_ASSET_FILE_DESCRIPTOR_FRRO) public void setResourceValue( @NonNull String resourceName, @NonNull AssetFileDescriptor value, diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 1b60f8ed904f0b87d1459c616d828701d348fa93..b15c9e4fa15bd7bfdc3626d54a77b92a1e27cea6 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1256,8 +1256,10 @@ public abstract class PackageManager { public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32; /** - * @hide + * Querying flag: always match components of packages in quarantined state. + * @see #isPackageQuarantined */ + @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L; /** @@ -9902,12 +9904,16 @@ public abstract class PackageManager { /** * Query if an app is currently quarantined. + * A misbehaving app can be quarantined by e.g. a system of another privileged entity. + * Quarantined apps are similar to disabled, but still visible in e.g. Launcher. + * Only activities of such apps can still be queried, but not services etc. + * Quarantined apps can't be bound to, and won't receive broadcasts. + * They can't be resolved, unless {@link #MATCH_QUARANTINED_COMPONENTS} specified. * * @return {@code true} if the given package is quarantined, {@code false} otherwise * @throws NameNotFoundException if the package could not be found. - * - * @hide */ + @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException { throw new UnsupportedOperationException("isPackageQuarantined not implemented"); } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 5cc3b92da3056baa05014a2784bc99e3e84cb332..c7790bd96c6285728642f6804e71ebf29ef61d58 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -27,6 +27,8 @@ import android.annotation.PluralsRes; import android.annotation.RawRes; import android.annotation.StyleRes; import android.annotation.StyleableRes; +import android.app.LocaleConfig; +import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; @@ -426,38 +428,59 @@ public class ResourcesImpl { String[] selectedLocales = null; String defaultLocale = null; + LocaleConfig lc = ResourcesManager.getInstance().getLocaleConfig(); if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) { if (locales.size() > 1) { - String[] availableLocales; - // The LocaleList has changed. We must query the AssetManager's - // available Locales and figure out the best matching Locale in the new - // LocaleList. - availableLocales = mAssets.getNonSystemLocales(); - if (LocaleList.isPseudoLocalesOnly(availableLocales)) { - // No app defined locales, so grab the system locales. - availableLocales = mAssets.getLocales(); + if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) { + Locale[] intersection = + locales.getIntersection(lc.getSupportedLocales()); + mConfiguration.setLocales(new LocaleList(intersection)); + selectedLocales = new String[intersection.length]; + for (int i = 0; i < intersection.length; i++) { + selectedLocales[i] = + adjustLanguageTag(intersection[i].toLanguageTag()); + } + defaultLocale = + adjustLanguageTag(lc.getDefaultLocale().toLanguageTag()); + } else { + String[] availableLocales; + // The LocaleList has changed. We must query the AssetManager's + // available Locales and figure out the best matching Locale in the new + // LocaleList. + availableLocales = mAssets.getNonSystemLocales(); if (LocaleList.isPseudoLocalesOnly(availableLocales)) { - availableLocales = null; + // No app defined locales, so grab the system locales. + availableLocales = mAssets.getLocales(); + if (LocaleList.isPseudoLocalesOnly(availableLocales)) { + availableLocales = null; + } } - } - if (availableLocales != null) { - final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( - availableLocales); - if (bestLocale != null) { - selectedLocales = new String[]{ - adjustLanguageTag(bestLocale.toLanguageTag())}; - if (!bestLocale.equals(locales.get(0))) { - mConfiguration.setLocales( - new LocaleList(bestLocale, locales)); + if (availableLocales != null) { + final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( + availableLocales); + if (bestLocale != null) { + selectedLocales = new String[]{ + adjustLanguageTag(bestLocale.toLanguageTag())}; + if (!bestLocale.equals(locales.get(0))) { + mConfiguration.setLocales( + new LocaleList(bestLocale, locales)); + } } } } } } if (selectedLocales == null) { - selectedLocales = new String[]{ - adjustLanguageTag(locales.get(0).toLanguageTag())}; + if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) { + selectedLocales = new String[locales.size()]; + for (int i = 0; i < locales.size(); i++) { + selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag()); + } + } else { + selectedLocales = new String[]{ + adjustLanguageTag(locales.get(0).toLanguageTag())}; + } } if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index 0c2c0f494257659abfad0d7c32153a512680988f..1b8eb0748737121b919a4f0605ccc86b1412ab7c 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -8,3 +8,10 @@ flag { # fixed_read_only or device wont boot because of permission issues accessing flags during boot is_fixed_read_only: true } + +flag { + name: "asset_file_descriptor_frro" + namespace: "resource_manager" + description: "Feature flag for passing in an AssetFileDescriptor to create an frro" + bug: "304478666" +} diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index b003e75b4e50b698bc4a595096380d7a65591171..8a4f678b52f29fc9beac316c27962b8ef00c4e3b 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -701,7 +701,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </pre> */ - @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) + @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public void beginTransactionReadOnly() { beginTransactionWithListenerReadOnly(null); } @@ -785,7 +785,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </pre> */ - @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) + @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public void beginTransactionWithListenerReadOnly( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED); @@ -2224,7 +2224,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws IllegalStateException if a transaction is not in progress. * @throws SQLiteException if the SQL cannot be compiled. */ - @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) + @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) @NonNull public SQLiteRawStatement createRawStatement(@NonNull String sql) { Objects.requireNonNull(sql); @@ -2244,7 +2244,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return The ROWID of the last row to be inserted under this connection. * @throws IllegalStateException if there is no current transaction. */ - @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) + @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public long getLastInsertRowId() { return getThreadSession().getLastInsertRowId(); } @@ -2258,7 +2258,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return The number of rows changed by the most recent sql statement * @throws IllegalStateException if there is no current transaction. */ - @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) + @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public long getLastChangedRowCount() { return getThreadSession().getLastChangedRowCount(); } @@ -2286,7 +2286,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return The number of rows changed on the current connection. * @throws IllegalStateException if there is no current transaction. */ - @FlaggedApi(Flags.FLAG_SQLITE_APIS_15) + @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public long getTotalChangedRowCount() { return getThreadSession().getTotalChangedRowCount(); } diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java index 827420f96fedd448b7053d33da98e23e5438adf8..33f602bbd40e7fdc1f1a92f55a21a926205c9c77 100644 --- a/core/java/android/database/sqlite/SQLiteRawStatement.java +++ b/core/java/android/database/sqlite/SQLiteRawStatement.java @@ -71,7 +71,7 @@ import java.util.Objects; * * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a> */ -@FlaggedApi(Flags.FLAG_SQLITE_APIS_15) +@FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public final class SQLiteRawStatement implements Closeable { private static final String TAG = "SQLiteRawStatement"; diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig index 564df039456b02195d2424bcb18ddd9085d48f5e..62a51236a2e24f9714660d7b39673c4f948169cd 100644 --- a/core/java/android/database/sqlite/flags.aconfig +++ b/core/java/android/database/sqlite/flags.aconfig @@ -1,7 +1,7 @@ package: "android.database.sqlite" flag { - name: "sqlite_apis_15" + name: "sqlite_apis_35" namespace: "system_performance" is_fixed_read_only: true description: "SQLite APIs held back for Android 15" diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl index 1333f0da725f460da2e89e0b52e8cfd35305204a..a9742cb6c084800e1828aede2052cc008a183800 100644 --- a/core/java/android/hardware/HardwareBuffer.aidl +++ b/core/java/android/hardware/HardwareBuffer.aidl @@ -16,4 +16,4 @@ package android.hardware; -@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h"; +@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h" rust_type "nativewindow::HardwareBuffer"; diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 490ff640885ef60a9187dcc9923b3d1670da2d16..7a43286692c44f5b4725377ffdeb9fe3c095ee23 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -805,7 +805,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. */ - public Signature getSignature() { + public @Nullable Signature getSignature() { return super.getSignature(); } @@ -813,7 +813,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * Get {@link Cipher} object. * @return {@link Cipher} object or null if this doesn't contain one. */ - public Cipher getCipher() { + public @Nullable Cipher getCipher() { return super.getCipher(); } @@ -821,7 +821,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * Get {@link Mac} object. * @return {@link Mac} object or null if this doesn't contain one. */ - public Mac getMac() { + public @Nullable Mac getMac() { return super.getMac(); } diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java index 6ac1efb4983919977294fedb63f6408bf24873b7..39fbe83b6abbd96bd66e4a714bba974ac66351d3 100644 --- a/core/java/android/hardware/biometrics/CryptoObject.java +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -20,6 +20,7 @@ import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OB import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.Nullable; import android.security.identity.IdentityCredential; import android.security.identity.PresentationSession; import android.security.keystore2.AndroidKeyStoreProvider; @@ -33,20 +34,35 @@ import javax.crypto.Mac; /** * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager. * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, - * {@link IdentityCredential}, and {@link PresentationSession} objects. + * {@link KeyAgreement}, {@link IdentityCredential}, and {@link PresentationSession} objects. * @hide */ public class CryptoObject { private final Object mCrypto; + /** + * Create from a {@link Signature} object. + * + * @param signature a {@link Signature} object. + */ public CryptoObject(@NonNull Signature signature) { mCrypto = signature; } + /** + * Create from a {@link Cipher} object. + * + * @param cipher a {@link Cipher} object. + */ public CryptoObject(@NonNull Cipher cipher) { mCrypto = cipher; } + /** + * Create from a {@link Mac} object. + * + * @param mac a {@link Mac} object. + */ public CryptoObject(@NonNull Mac mac) { mCrypto = mac; } @@ -62,10 +78,20 @@ public class CryptoObject { mCrypto = credential; } + /** + * Create from a {@link PresentationSession} object. + * + * @param session a {@link PresentationSession} object. + */ public CryptoObject(@NonNull PresentationSession session) { mCrypto = session; } + /** + * Create from a {@link KeyAgreement} object. + * + * @param keyAgreement a {@link KeyAgreement} object. + */ @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT) public CryptoObject(@NonNull KeyAgreement keyAgreement) { mCrypto = keyAgreement; @@ -75,7 +101,7 @@ public class CryptoObject { * Get {@link Signature} object. * @return {@link Signature} object or null if this doesn't contain one. */ - public Signature getSignature() { + public @Nullable Signature getSignature() { return mCrypto instanceof Signature ? (Signature) mCrypto : null; } @@ -83,7 +109,7 @@ public class CryptoObject { * Get {@link Cipher} object. * @return {@link Cipher} object or null if this doesn't contain one. */ - public Cipher getCipher() { + public @Nullable Cipher getCipher() { return mCrypto instanceof Cipher ? (Cipher) mCrypto : null; } @@ -91,7 +117,7 @@ public class CryptoObject { * Get {@link Mac} object. * @return {@link Mac} object or null if this doesn't contain one. */ - public Mac getMac() { + public @Nullable Mac getMac() { return mCrypto instanceof Mac ? (Mac) mCrypto : null; } @@ -101,7 +127,7 @@ public class CryptoObject { * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}. */ @Deprecated - public IdentityCredential getIdentityCredential() { + public @Nullable IdentityCredential getIdentityCredential() { return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null; } @@ -109,16 +135,18 @@ public class CryptoObject { * Get {@link PresentationSession} object. * @return {@link PresentationSession} object or null if this doesn't contain one. */ - public PresentationSession getPresentationSession() { + public @Nullable PresentationSession getPresentationSession() { return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null; } /** - * Get {@link KeyAgreement} object. + * Get {@link KeyAgreement} object. A key-agreement protocol is a protocol whereby + * two or more parties can agree on a shared secret using public key cryptography. + * * @return {@link KeyAgreement} object or null if this doesn't contain one. */ @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT) - public KeyAgreement getKeyAgreement() { + public @Nullable KeyAgreement getKeyAgreement() { return mCrypto instanceof KeyAgreement ? (KeyAgreement) mCrypto : null; } diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 8decd50664b3d9e7e36ad61ed562eb21c681e291..2b5f5ee35a265cf741b428c32d8fb6b82c18c42c 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -220,7 +220,7 @@ public final class DisplayManagerGlobal { registerCallbackIfNeededLocked(); - if (DEBUG || extraLogging()) { + if (DEBUG) { Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info); } return info; @@ -402,7 +402,7 @@ public final class DisplayManagerGlobal { } private void maybeLogAllDisplayListeners() { - if (!sExtraDisplayListenerLogging) { + if (!extraLogging()) { return; } @@ -1222,7 +1222,7 @@ public final class DisplayManagerGlobal { private void handleMessage(Message msg) { if (extraLogging()) { - Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what) + Slog.i(TAG, "DLD(" + eventToString(msg.what) + ", display=" + msg.arg1 + ", mEventsMask=" + Long.toBinaryString(mEventsMask) + ", mPackageName=" + mPackageName @@ -1231,9 +1231,10 @@ public final class DisplayManagerGlobal { } if (DEBUG) { Trace.beginSection( - "DisplayListenerDelegate(" + eventToString(msg.what) + TextUtils.trimToSize( + "DLD(" + eventToString(msg.what) + ", display=" + msg.arg1 - + ", listener=" + mListener.getClass() + ")"); + + ", listener=" + mListener.getClass() + ")", 127)); } switch (msg.what) { case EVENT_DISPLAY_ADDED: @@ -1422,11 +1423,12 @@ public final class DisplayManagerGlobal { sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME) && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName); } - return sExtraDisplayListenerLogging; + // TODO: b/306170135 - return sExtraDisplayListenerLogging instead + return true; } private static boolean extraLogging() { - return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals( - sCurrentPackageName); + // TODO: b/306170135 - return sExtraDisplayListenerLogging & package name check instead + return true; } } diff --git a/core/java/android/nfc/Constants.java b/core/java/android/nfc/Constants.java new file mode 100644 index 0000000000000000000000000000000000000000..f76833063605d9e3e86bda2866ee86903aa19972 --- /dev/null +++ b/core/java/android/nfc/Constants.java @@ -0,0 +1,29 @@ +/* + * 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 android.nfc; + +/** + * @hide + * TODO(b/303286040): Holds @hide API constants. Formalize these APIs. + */ +public final class Constants { + private Constants() { } + + public static final String SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND = "nfc_payment_foreground"; + public static final String SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; + public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any"; +} diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 46586308e3cfa14b0621a8fc54b0517145918bb2..4a7bd3f294584fc6c34dc607754ed69341fbb061 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.UserIdInt; import android.app.Activity; @@ -37,6 +38,7 @@ import android.nfc.tech.MifareClassic; import android.nfc.tech.Ndef; import android.nfc.tech.NfcA; import android.nfc.tech.NfcF; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -1594,6 +1596,40 @@ public final class NfcAdapter { mNfcActivityManager.disableReaderMode(activity); } + // Flags arguments to NFC adapter to enable/disable NFC + private static final int DISABLE_POLLING_FLAGS = 0x1000; + private static final int ENABLE_POLLING_FLAGS = 0x0000; + + /** + * Privileged API to enable disable reader polling. + * Note: Use with caution! The app is responsible for ensuring that the polling state is + * returned to normal. + * + * @see #enableReaderMode(Activity, ReaderCallback, int, Bundle) for more detailed + * documentation. + * + * @param enablePolling whether to enable or disable polling. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) + @SuppressLint("VisiblySynchronized") + public void setReaderMode(boolean enablePolling) { + synchronized (NfcAdapter.class) { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + } + Binder token = new Binder(); + int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS; + try { + NfcAdapter.sService.setReaderMode(token, null, flags, null); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + } + } + /** * Manually invoke Android Beam to share data. * diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index 4909b0830eebdfdd4709df29685cc6a5ef979240..32c2a1b40530a78483c7617169e26c9535cc0db6 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -26,6 +26,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.nfc.Constants; import android.nfc.INfcCardEmulation; import android.nfc.NfcAdapter; import android.os.RemoteException; @@ -274,7 +275,7 @@ public final class CardEmulation { try { preferForeground = Settings.Secure.getInt( contextAsUser.getContentResolver(), - Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0; + Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0; } catch (SettingNotFoundException e) { } return preferForeground; diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index 47ad72fe8d0e57e2c49a8817a09c9eb892a84db8..e8ad303c42084eccf1c35eab674ab1fd0a91a6cf 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -133,6 +134,7 @@ public final class BugreportParams { * The maximum value of supported bugreport mode. * @hide */ + @FlaggedApi(android.os.Flags.FLAG_BUGREPORT_MODE_MAX_VALUE) @TestApi public static final int BUGREPORT_MODE_MAX_VALUE = BUGREPORT_MODE_ONBOARDING; diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java index 82cdd280a0f3ca4a27c568171d56705d05f064ce..d7e440b66e13223d8bb1228340edf45dcf7e9cd7 100644 --- a/core/java/android/os/LocaleList.java +++ b/core/java/android/os/LocaleList.java @@ -153,21 +153,21 @@ public final class LocaleList implements Parcelable { /** * Find the intersection between this LocaleList and another - * @return a String array of the Locales in both LocaleLists + * @return an array of the Locales in both LocaleLists * {@hide} */ @NonNull - public String[] getIntersection(@NonNull LocaleList other) { - List<String> intersection = new ArrayList<>(); + public Locale[] getIntersection(@NonNull LocaleList other) { + List<Locale> intersection = new ArrayList<>(); for (Locale l1 : mList) { for (Locale l2 : other.mList) { if (matchesLanguageAndScript(l2, l1)) { - intersection.add(l1.toLanguageTag()); + intersection.add(l1); break; } } } - return intersection.toArray(new String[0]); + return intersection.toArray(new Locale[0]); } /** diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java index 151a65fdfaf54d3bcc63bac846d8c0964b0afff5..ca1d49a93defdfaf2c1eae1a0e3c2ffb6e805dbc 100644 --- a/core/java/android/os/OomKillRecord.java +++ b/core/java/android/os/OomKillRecord.java @@ -15,10 +15,15 @@ */ package android.os; +import com.android.internal.util.FrameworkStatsLog; /** + * Activity manager communication with kernel out-of-memory (OOM) data handling + * and statsd atom logging. + * * Expected data to get back from the OOM event's file. - * Note that this should be equivalent to the struct <b>OomKill</b> inside + * Note that this class fields' should be equivalent to the struct + * <b>OomKill</b> inside * <pre> * system/memory/libmeminfo/libmemevents/include/memevents.h * </pre> @@ -41,6 +46,18 @@ public final class OomKillRecord { this.mOomScoreAdj = oomScoreAdj; } + /** + * Logs the event when the kernel OOM killer claims a victims to reduce + * memory pressure. + * KernelOomKillOccurred = 754 + */ + public void logKillOccurred() { + FrameworkStatsLog.write( + FrameworkStatsLog.KERNEL_OOM_KILL_OCCURRED, + mUid, mPid, mOomScoreAdj, mTimeStampInMillis, + mProcessName); + } + public long getTimestampMilli() { return mTimeStampInMillis; } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 4c8ef97a7437a868b5f7a5dfb811e9f01a312b17..9034ff10286b8f7da2cdf133db5c81ba81b39d92 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3330,7 +3330,10 @@ public class UserManager { * * @return whether the context user is running in the foreground. */ - @UserHandleAware + @UserHandleAware( + requiresAnyOfPermissionsIfNotCaller = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isUserForeground() { try { return mService.isUserForeground(mUserId); @@ -3404,11 +3407,10 @@ public class UserManager { * @hide */ @SystemApi - @UserHandleAware - @RequiresPermission(anyOf = { - "android.permission.INTERACT_ACROSS_USERS", - "android.permission.MANAGE_USERS" - }) + @UserHandleAware( + requiresAnyOfPermissionsIfNotCaller = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isUserVisible() { try { return mService.isUserVisible(mUserId); diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index b7f2e065bac81381976c14c994c2cbf2452bcef8..c4521c036329d8db614b5c43df1ac972aeadb543 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -27,3 +27,10 @@ flag { description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion." bug: "299069460" } + +flag { + name: "bugreport_mode_max_value" + namespace: "telephony" + description: "Introduce a constant as maximum value of bugreport mode." + bug: "305067125" +} diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index a39157103f716ae066345e8a87e2584c9be9e7bc..27ad45de69e60c7fb0d925d7de1b6157e8945670 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -3195,6 +3195,16 @@ public final class Telephony { */ public static final String ALWAYS_ON = "always_on"; + /** + * The infrastructure bitmask which the APN can be used on. For example, some APNs can only + * be used when the device is on cellular, on satellite, or both. The default value is + * 1 (INFRASTRUCTURE_CELLULAR). + * + * <P>Type: INTEGER</P> + * @hide + */ + public static final String INFRASTRUCTURE_BITMASK = "infrastructure_bitmask"; + /** * MVNO type: * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}. diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index c82a4cabaddd3093eb3da0d26001636c71036f39..2a4cbaf79a75e71aab47375e6abfb15cee268eab 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -28,8 +28,6 @@ import android.system.OsConstants; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; - import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -59,8 +57,7 @@ public class NotificationRankingUpdate implements Parcelable { * @hide */ public NotificationRankingUpdate(Parcel in) { - if (SystemUiSystemPropertiesFlags.getResolver().isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) { + if (Flags.rankingUpdateAshmem()) { // Recover the ranking map from the SharedMemory and store it in mapParcel. final Parcel mapParcel = Parcel.obtain(); ByteBuffer buffer = null; @@ -176,8 +173,7 @@ public class NotificationRankingUpdate implements Parcelable { */ @Override public void writeToParcel(@NonNull Parcel out, int flags) { - if (SystemUiSystemPropertiesFlags.getResolver().isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) { + if (Flags.rankingUpdateAshmem()) { final Parcel mapParcel = Parcel.obtain(); ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>(); Bundle smartActionsBundle = new Bundle(); diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..293143595b5e5b1c845ba4f882c2935827115d6e --- /dev/null +++ b/core/java/android/service/notification/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.service.notification" + +flag { + name: "ranking_update_ashmem" + namespace: "systemui" + description: "This flag controls moving ranking update contents into ashmem" + bug: "284297289" +} + diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 2f3d73a3642566cb71c34d01375cd48861b59eba..42a0c9a9cfe9c26a8b9e4f300cead5de07b6af02 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -70,6 +70,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -796,7 +797,7 @@ public final class SurfaceControl implements Parcelable { if (nativeObject != 0) { // Only add valid surface controls to the registry. This is called at the end of this // method since its information is dumped if the process threshold is reached. - addToRegistry(); + SurfaceControlRegistry.getProcessInstance().add(this); } } @@ -1501,7 +1502,7 @@ public final class SurfaceControl implements Parcelable { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); } - removeFromRegistry(); + SurfaceControlRegistry.getProcessInstance().remove(this); } finally { super.finalize(); } @@ -1519,6 +1520,10 @@ public final class SurfaceControl implements Parcelable { */ public void release() { if (mNativeObject != 0) { + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "release", null, this, null); + } mFreeNativeResources.run(); mNativeObject = 0; mNativeHandle = 0; @@ -1532,7 +1537,7 @@ public final class SurfaceControl implements Parcelable { mChoreographer = null; } } - removeFromRegistry(); + SurfaceControlRegistry.getProcessInstance().remove(this); } } @@ -2765,8 +2770,10 @@ public final class SurfaceControl implements Parcelable { private Transaction(long nativeObject) { mNativeObject = nativeObject; - mFreeNativeResources = - sRegistry.registerNativeAllocation(this, mNativeObject); + mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject); + if (!SurfaceControlRegistry.sCallStackDebuggingInitialized) { + SurfaceControlRegistry.initializeCallStackDebugging(); + } } private Transaction(Parcel in) { @@ -2845,6 +2852,11 @@ public final class SurfaceControl implements Parcelable { applyResizedSurfaces(); notifyReparentedSurfaces(); nativeApplyTransaction(mNativeObject, sync, oneWay); + + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "apply", this, null, null); + } } /** @@ -2920,6 +2932,10 @@ public final class SurfaceControl implements Parcelable { @UnsupportedAppUsage public Transaction show(SurfaceControl sc) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "show", this, sc, null); + } nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN); return this; } @@ -2934,6 +2950,10 @@ public final class SurfaceControl implements Parcelable { @UnsupportedAppUsage public Transaction hide(SurfaceControl sc) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "hide", this, sc, null); + } nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN); return this; } @@ -2950,6 +2970,10 @@ public final class SurfaceControl implements Parcelable { @NonNull public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setPosition", this, sc, "x=" + x + " y=" + y); + } nativeSetPosition(mNativeObject, sc.mNativeObject, x, y); return this; } @@ -2968,6 +2992,10 @@ public final class SurfaceControl implements Parcelable { checkPreconditions(sc); Preconditions.checkArgument(scaleX >= 0, "Negative value passed in for scaleX"); Preconditions.checkArgument(scaleY >= 0, "Negative value passed in for scaleY"); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setScale", this, sc, "sx=" + scaleX + " sy=" + scaleY); + } nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY); return this; } @@ -2985,6 +3013,10 @@ public final class SurfaceControl implements Parcelable { public Transaction setBufferSize(@NonNull SurfaceControl sc, @IntRange(from = 0) int w, @IntRange(from = 0) int h) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setBufferSize", this, sc, "w=" + w + " h=" + h); + } mResizedSurfaces.put(sc, new Point(w, h)); return this; } @@ -3005,6 +3037,10 @@ public final class SurfaceControl implements Parcelable { public Transaction setFixedTransformHint(@NonNull SurfaceControl sc, @Surface.Rotation int transformHint) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setFixedTransformHint", this, sc, "hint=" + transformHint); + } nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint); return this; } @@ -3018,6 +3054,10 @@ public final class SurfaceControl implements Parcelable { @NonNull public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "unsetFixedTransformHint", this, sc, null); + } nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */); return this; } @@ -3035,6 +3075,10 @@ public final class SurfaceControl implements Parcelable { public Transaction setLayer(@NonNull SurfaceControl sc, @IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setLayer", this, sc, "z=" + z); + } nativeSetLayer(mNativeObject, sc.mNativeObject, z); return this; } @@ -3044,6 +3088,10 @@ public final class SurfaceControl implements Parcelable { */ public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setRelativeLayer", this, sc, "relTo=" + relativeTo + " z=" + z); + } nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.mNativeObject, z); return this; } @@ -3053,6 +3101,10 @@ public final class SurfaceControl implements Parcelable { */ public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "unsetFixedTransformHint", this, sc, "region=" + transparentRegion); + } nativeSetTransparentRegionHint(mNativeObject, sc.mNativeObject, transparentRegion); return this; @@ -3069,6 +3121,10 @@ public final class SurfaceControl implements Parcelable { public Transaction setAlpha(@NonNull SurfaceControl sc, @FloatRange(from = 0.0, to = 1.0) float alpha) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setAlpha", this, sc, "alpha=" + alpha); + } nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha); return this; } @@ -3124,6 +3180,11 @@ public final class SurfaceControl implements Parcelable { public Transaction setMatrix(SurfaceControl sc, float dsdx, float dtdx, float dtdy, float dsdy) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setMatrix", this, sc, + "dsdx=" + dsdx + " dtdx=" + dtdx + " dtdy=" + dtdy + " dsdy=" + dsdy); + } nativeSetMatrix(mNativeObject, sc.mNativeObject, dsdx, dtdx, dtdy, dsdy); return this; @@ -3189,6 +3250,10 @@ public final class SurfaceControl implements Parcelable { @UnsupportedAppUsage public Transaction setWindowCrop(SurfaceControl sc, Rect crop) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setWindowCrop", this, sc, "crop=" + crop); + } if (crop != null) { nativeSetWindowCrop(mNativeObject, sc.mNativeObject, crop.left, crop.top, crop.right, crop.bottom); @@ -3211,6 +3276,10 @@ public final class SurfaceControl implements Parcelable { */ public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setCrop", this, sc, "crop=" + crop); + } if (crop != null) { Preconditions.checkArgument(crop.isValid(), "Crop isn't valid."); nativeSetWindowCrop(mNativeObject, sc.mNativeObject, @@ -3233,6 +3302,10 @@ public final class SurfaceControl implements Parcelable { */ public Transaction setWindowCrop(SurfaceControl sc, int width, int height) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setCornerRadius", this, sc, "w=" + width + " h=" + height); + } nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height); return this; } @@ -3247,6 +3320,10 @@ public final class SurfaceControl implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setCornerRadius", this, sc, "cornerRadius=" + cornerRadius); + } nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius); return this; @@ -3262,6 +3339,10 @@ public final class SurfaceControl implements Parcelable { */ public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setBackgroundBlurRadius", this, sc, "radius=" + radius); + } nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius); return this; } @@ -3318,6 +3399,10 @@ public final class SurfaceControl implements Parcelable { public Transaction reparent(@NonNull SurfaceControl sc, @Nullable SurfaceControl newParent) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "reparent", this, sc, "newParent=" + newParent); + } long otherObject = 0; if (newParent != null) { newParent.checkNotReleased(); @@ -3337,6 +3422,11 @@ public final class SurfaceControl implements Parcelable { @UnsupportedAppUsage public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "reparent", this, sc, + "r=" + color[0] + " g=" + color[1] + " b=" + color[2]); + } nativeSetColor(mNativeObject, sc.mNativeObject, color); return this; } @@ -3347,6 +3437,10 @@ public final class SurfaceControl implements Parcelable { */ public Transaction unsetColor(SurfaceControl sc) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "unsetColor", this, sc, null); + } nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR); return this; } @@ -3358,6 +3452,10 @@ public final class SurfaceControl implements Parcelable { */ public Transaction setSecure(SurfaceControl sc, boolean isSecure) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setSecure", this, sc, "secure=" + isSecure); + } if (isSecure) { nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE); } else { @@ -3411,6 +3509,10 @@ public final class SurfaceControl implements Parcelable { @NonNull public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setOpaque", this, sc, "opaque=" + isOpaque); + } if (isOpaque) { nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE); } else { @@ -3580,6 +3682,10 @@ public final class SurfaceControl implements Parcelable { */ public Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) { checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setShadowRadius", this, sc, "radius=" + shadowRadius); + } nativeSetShadowRadius(mNativeObject, sc.mNativeObject, shadowRadius); return this; } @@ -4463,26 +4569,6 @@ public final class SurfaceControl implements Parcelable { return -1; } - /** - * Adds this surface control to the registry for this process if it is created. - */ - private void addToRegistry() { - final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance(); - if (registry != null) { - registry.add(this); - } - } - - /** - * Removes this surface control from the registry for this process. - */ - private void removeFromRegistry() { - final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance(); - if (registry != null) { - registry.remove(this); - } - } - // Called by native private static void invokeReleaseCallback(Consumer<SyncFence> callback, long nativeFencePtr) { SyncFence fence = new SyncFence(nativeFencePtr); diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index 0f35048cac673741473de333bf6352cc7ec4f86d..52be8f6a76fd4a937169989fdb39dd071fe5b33e 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -23,7 +23,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.content.Context; +import android.os.Build; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -104,6 +106,9 @@ public class SurfaceControlRegistry { // Number of surface controls to dump when the max threshold is exceeded private static final int DUMP_LIMIT = 256; + // An instance of a registry that is a no-op + private static final SurfaceControlRegistry NO_OP_REGISTRY = new NoOpRegistry(); + // Static lock, must be held for all registry operations private static final Object sLock = new Object(); @@ -113,6 +118,22 @@ public class SurfaceControlRegistry { // The registry for a given process private static volatile SurfaceControlRegistry sProcessRegistry; + // Whether call stack debugging has been initialized. This is evaluated only once per process + // instance when the first SurfaceControl.Transaction object is created + static boolean sCallStackDebuggingInitialized; + + // Whether call stack debugging is currently enabled, ie. whether there is a valid match string + // for either a specific surface control name or surface control transaction method + static boolean sCallStackDebuggingEnabled; + + // The name of the surface control to log stack traces for. Always non-null if + // sCallStackDebuggingEnabled is true. Can be combined with the match call. + private static String sCallStackDebuggingMatchName; + + // The surface control transaction method name to log stack traces for. Always non-null if + // sCallStackDebuggingEnabled is true. Can be combined with the match name. + private static String sCallStackDebuggingMatchCall; + // Mapping of the active SurfaceControls to the elapsed time when they were registered @GuardedBy("sLock") private final WeakHashMap<SurfaceControl, Long> mSurfaceControls; @@ -160,6 +181,12 @@ public class SurfaceControlRegistry { } } + @VisibleForTesting + public void setCallStackDebuggingParams(String matchName, String matchCall) { + sCallStackDebuggingMatchName = matchName.toLowerCase(); + sCallStackDebuggingMatchCall = matchCall.toLowerCase(); + } + /** * Creates and initializes the registry for all SurfaceControls in this process. The caller must * hold the READ_FRAME_BUFFER permission. @@ -196,11 +223,9 @@ public class SurfaceControlRegistry { * createProcessInstance(Context) was previously called from a valid caller. * @hide */ - @Nullable - @VisibleForTesting public static SurfaceControlRegistry getProcessInstance() { synchronized (sLock) { - return sProcessRegistry; + return sProcessRegistry != null ? sProcessRegistry : NO_OP_REGISTRY; } } @@ -247,6 +272,91 @@ public class SurfaceControlRegistry { } } + /** + * Initializes global call stack debugging if this is a debug build and a filter is specified. + * This is a no-op if + * + * Usage: + * adb shell setprop persist.wm.debug.sc.tx.log_match_call <call or \"\" to unset> + * adb shell setprop persist.wm.debug.sc.tx.log_match_name <name or \"\" to unset> + * adb reboot + */ + final static void initializeCallStackDebugging() { + if (sCallStackDebuggingInitialized || !Build.IS_DEBUGGABLE) { + // Return early if already initialized or this is not a debug build + return; + } + + sCallStackDebuggingInitialized = true; + sCallStackDebuggingMatchCall = + SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null) + .toLowerCase(); + sCallStackDebuggingMatchName = + SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null) + .toLowerCase(); + // Only enable stack debugging if any of the match filters are set + sCallStackDebuggingEnabled = (!sCallStackDebuggingMatchCall.isEmpty() + || !sCallStackDebuggingMatchName.isEmpty()); + if (sCallStackDebuggingEnabled) { + Log.d(TAG, "Enabling transaction call stack debugging:" + + " matchCall=" + sCallStackDebuggingMatchCall + + " matchName=" + sCallStackDebuggingMatchName); + } + } + + /** + * Dumps the callstack if it matches the global debug properties. Caller should first verify + * {@link #sCallStackDebuggingEnabled} is true. + * + * @param call the name of the call + * @param tx (optional) the transaction associated with this call + * @param sc the affected surface + * @param details additional details to print with the stack track + */ + final void checkCallStackDebugging(@NonNull String call, + @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc, + @Nullable String details) { + if (!sCallStackDebuggingEnabled) { + return; + } + if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) { + return; + } + final String txMsg = tx != null ? "tx=" + tx.getId() + " ": ""; + final String scMsg = sc != null ? " sc=" + sc.getName() + "": ""; + final String msg = details != null + ? call + " (" + txMsg + scMsg + ") " + details + : call + " (" + txMsg + scMsg + ")"; + Log.e(TAG, msg, new Throwable()); + } + + /** + * Tests whether the given surface control name/method call matches the filters set for the + * call stack debugging. + */ + @VisibleForTesting + public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) { + final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty(); + if (matchCall && !call.toLowerCase().contains(sCallStackDebuggingMatchCall)) { + // Skip if target call doesn't match requested caller + return false; + } + final boolean matchName = !sCallStackDebuggingMatchName.isEmpty(); + if (matchName && (name == null + || !name.toLowerCase().contains(sCallStackDebuggingMatchName))) { + // Skip if target surface doesn't match requested surface + return false; + } + return true; + } + + /** + * Returns whether call stack debugging is enabled for this process. + */ + final static boolean isCallStackDebuggingEnabled() { + return sCallStackDebuggingEnabled; + } + /** * Forces the gc and finalizers to run, used prior to dumping to ensure we only dump strongly * referenced surface controls. @@ -272,7 +382,27 @@ public class SurfaceControlRegistry { synchronized (sLock) { if (sProcessRegistry != null) { sDefaultReporter.onMaxLayersExceeded(sProcessRegistry.mSurfaceControls, limit, pw); + pw.println("sCallStackDebuggingInitialized=" + sCallStackDebuggingInitialized); + pw.println("sCallStackDebuggingEnabled=" + sCallStackDebuggingEnabled); + pw.println("sCallStackDebuggingMatchName=" + sCallStackDebuggingMatchName); + pw.println("sCallStackDebuggingMatchCall=" + sCallStackDebuggingMatchCall); } } } + + /** + * A no-op implementation of the registry. + */ + private static class NoOpRegistry extends SurfaceControlRegistry { + + @Override + public void setReportingThresholds(int maxLayersReportingThreshold, + int resetReportingThreshold, Reporter reporter) {} + + @Override + void add(SurfaceControl sc) {} + + @Override + void remove(SurfaceControl sc) {} + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 310bdd5819baee80fcf4bfe1f2ce98c5b9b4868c..4a10cab4ffe46cd0bd9002611de1dfd74d575e65 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -655,6 +655,10 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mCheckIfCanDraw = false; + private boolean mWasLastDrawCanceled; + private boolean mLastTraversalWasVisible = true; + private boolean mLastDrawScreenOff; + private boolean mDrewOnceForSync = false; int mSyncSeqId = 0; @@ -1016,7 +1020,8 @@ public final class ViewRootImpl implements ViewParent, mDisplay = display; mBasePackageName = context.getBasePackageName(); final String name = DisplayProperties.debug_vri_package().orElse(null); - mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName); + // TODO: b/306170135 - return to using textutils check on package name. + mExtraDisplayListenerLogging = true; mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); mLocation.fillInStackTrace(); @@ -1890,12 +1895,19 @@ public final class ViewRootImpl implements ViewParent, } void handleAppVisibility(boolean visible) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple( + "%s visibilityChanged oldVisibility=%b newVisibility=%b", mTag, + mAppVisible, visible)); + } if (mAppVisible != visible) { final boolean previousVisible = getHostVisibility() == View.VISIBLE; mAppVisible = visible; final boolean currentVisible = getHostVisibility() == View.VISIBLE; // Root view only cares about whether it is visible or not. if (previousVisible != currentVisible) { + Log.d(mTag, "visibilityChanged oldVisibility=" + previousVisible + " newVisibility=" + + currentVisible); mAppVisibilityChanged = true; scheduleTraversals(); } @@ -2003,6 +2015,10 @@ public final class ViewRootImpl implements ViewParent, Slog.i(mTag, "DisplayState - old: " + oldDisplayState + ", new: " + newDisplayState); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.traceCounter(Trace.TRACE_TAG_WINDOW_MANAGER, + "vri#screenState[" + mTag + "] state=", newDisplayState); + } if (oldDisplayState != newDisplayState) { mAttachInfo.mDisplayState = newDisplayState; pokeDrawLockIfNeeded(); @@ -3252,8 +3268,8 @@ public final class ViewRootImpl implements ViewParent, || mForceNextWindowRelayout) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, - TextUtils.formatSimple("relayoutWindow#" - + "first=%b/resize=%b/vis=%b/params=%b/force=%b", + TextUtils.formatSimple("%s-relayoutWindow#" + + "first=%b/resize=%b/vis=%b/params=%b/force=%b", mTag, mFirst, windowShouldResize, viewVisibilityChanged, params != null, mForceNextWindowRelayout)); } @@ -3842,11 +3858,7 @@ public final class ViewRootImpl implements ViewParent, boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw(); boolean cancelAndRedraw = cancelDueToPreDrawListener || (cancelDraw && mDrewOnceForSync); - if (cancelAndRedraw) { - Log.d(mTag, "Cancelling draw." - + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener - + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync)); - } + if (!cancelAndRedraw) { // A sync was already requested before the WMS requested sync. This means we need to // sync the buffer, regardless if WMS wants to sync the buffer. @@ -3870,6 +3882,9 @@ public final class ViewRootImpl implements ViewParent, } if (!isViewVisible) { + if (mLastTraversalWasVisible) { + logAndTrace("Not drawing due to not visible"); + } mLastPerformTraversalsSkipDrawReason = "view_not_visible"; if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { @@ -3881,12 +3896,23 @@ public final class ViewRootImpl implements ViewParent, handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, mPendingTransaction, "view not visible"); } else if (cancelAndRedraw) { + if (!mWasLastDrawCanceled) { + logAndTrace("Canceling draw." + + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener + + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync)); + } mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason() : "cancel_" + cancelReason; // Try again scheduleTraversals(); } else { + if (mWasLastDrawCanceled) { + logAndTrace("Draw frame after cancel"); + } + if (!mLastTraversalWasVisible) { + logAndTrace("Start draw after previous draw not visible"); + } if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); @@ -3898,6 +3924,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransaction, mLastPerformDrawSkippedReason); } } + mWasLastDrawCanceled = cancelAndRedraw; + mLastTraversalWasVisible = isViewVisible; if (mAttachInfo.mContentCaptureEvents != null) { notifyContentCaptureEvents(); @@ -4687,10 +4715,7 @@ public final class ViewRootImpl implements ViewParent, return didProduceBuffer -> { if (!didProduceBuffer) { - Trace.instant(Trace.TRACE_TAG_VIEW, - "Transaction not synced due to no frame drawn-" + mTag); - Log.d(mTag, "Pending transaction will not be applied in sync with a draw " - + "because there was nothing new to draw"); + logAndTrace("Transaction not synced due to no frame drawn"); mBlastBufferQueue.applyPendingTransactions(frame); } }; @@ -4707,17 +4732,26 @@ public final class ViewRootImpl implements ViewParent, mLastPerformDrawSkippedReason = null; if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { mLastPerformDrawSkippedReason = "screen_off"; + if (!mLastDrawScreenOff) { + logAndTrace("Not drawing due to screen off"); + } + mLastDrawScreenOff = true; return false; } else if (mView == null) { mLastPerformDrawSkippedReason = "no_root_view"; return false; } + if (mLastDrawScreenOff) { + logAndTrace("Resumed drawing after screen turned on"); + mLastDrawScreenOff = false; + } + final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null; mFullRedrawNeeded = false; mIsDrawing = true; - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); + Trace.traceBegin(Trace.TRACE_TAG_VIEW, mTag + "-draw"); addFrameCommitCallbackIfNeeded(); @@ -11426,8 +11460,7 @@ public final class ViewRootImpl implements ViewParent, @Override public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) { if (mRemoved || !isHardwareEnabled()) { - Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw applyImmediately-" + mTag); - Log.d(mTag, "applyTransactionOnDraw: Applying transaction immediately"); + logAndTrace("applyTransactionOnDraw applyImmediately"); t.apply(); } else { Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw-" + mTag); diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index cc612ed93b2f157b9477d4ad43f96489e2857d86..6888b50bb74438d462ac161fcf43e93913d4cce6 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -1,10 +1,12 @@ package: "android.view.accessibility" +# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. + flag { + name: "a11y_overlay_callbacks" namespace: "accessibility" - name: "force_invert_color" - description: "Enable force force-dark for smart inversion and dark theme everywhere" - bug: "282821643" + description: "Whether to allow the passing of result callbacks when attaching a11y overlays." + bug: "304478691" } flag { @@ -15,8 +17,8 @@ flag { } flag { - name: "a11y_overlay_callbacks" namespace: "accessibility" - description: "Whether to allow the passing of result callbacks when attaching a11y overlays." - bug: "304478691" + name: "force_invert_color" + description: "Enable force force-dark for smart inversion and dark theme everywhere" + bug: "282821643" } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 103725da9af032a445c480ba47d4e4aa4c5945be..f19a2f961391b8578f789a85ad45ff3d3b94dc55 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -685,8 +685,9 @@ public class RemoteViews implements Parcelable, Filter { return false; } + /** See {@link RemoteViews#visitUris(Consumer)}. **/ public void visitUris(@NonNull Consumer<Uri> visitor) { - // Nothing to visit by default + // Nothing to visit by default. } } @@ -761,9 +762,11 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Note all {@link Uri} that are referenced internally, with the expectation - * that Uri permission grants will need to be issued to ensure the recipient - * of this object is able to render its contents. + * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission + * grants will need to be issued to ensure the recipient of this object is able to render its + * contents. + * See b/281044385 for more context and examples about what happens when this isn't done + * correctly. * * @hide */ @@ -1088,6 +1091,13 @@ public class RemoteViews implements Parcelable, Filter { public String getUniqueKey() { return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId); } + + @Override + public void visitUris(@NonNull Consumer<Uri> visitor) { + for (RemoteViews remoteViews : mList) { + remoteViews.visitUris(visitor); + } + } } /** @@ -1289,6 +1299,12 @@ public class RemoteViews implements Parcelable, Filter { public String getUniqueKey() { return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId); } + + @Override + public void visitUris(@NonNull Consumer<Uri> visitor) { + RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture); + items.visitUris(visitor); + } } private class SetRemoteViewsAdapterIntent extends Action { @@ -1359,6 +1375,13 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG; } + + @Override + public void visitUris(@NonNull Consumer<Uri> visitor) { + // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated + // visitUris method in the Intent class, since it can contain other intents. Otherwise, + // the basic thing to do here would be just visitor.accept(intent.getData()). + } } /** @@ -1434,6 +1457,11 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_ON_CLICK_RESPONSE_TAG; } + + @Override + public void visitUris(@NonNull Consumer<Uri> visitor) { + // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse. + } } /** @@ -1504,6 +1532,11 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_ON_CHECKED_CHANGE_RESPONSE_TAG; } + + @Override + public void visitUris(@NonNull Consumer<Uri> visitor) { + // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse. + } } /** @hide **/ @@ -2063,6 +2096,7 @@ public class RemoteViews implements Parcelable, Filter { final Icon icon = (Icon) getParameterValue(null); if (icon != null) visitIconUri(icon, visitor); break; + // TODO(b/281044385): Should we do anything about type BUNDLE? } } } @@ -2812,7 +2846,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public final void visitUris(@NonNull Consumer<Uri> visitor) { + public void visitUris(@NonNull Consumer<Uri> visitor) { mNestedViews.visitUris(visitor); } } @@ -7262,6 +7296,15 @@ public class RemoteViews implements Parcelable, Filter { Math.max(mViewTypeCount, 1)); } } + + /** + * See {@link RemoteViews#visitUris(Consumer)}. + */ + private void visitUris(@NonNull Consumer<Uri> visitor) { + for (RemoteViews view : mViews) { + view.visitUris(visitor); + } + } } /** diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 17c82b63c443a20799002d69c16099081b83e8cf..a0628c4b95804d2ee043d2e0c217e45a4b1339d5 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9854,7 +9854,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); - + if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled() + && isAutoHandwritingEnabled()) { + outAttrs.setStylusHandwritingEnabled(true); + } ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>(); gestures.add(SelectGesture.class); gestures.add(SelectRangeGesture.class); diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 7cfd35b075473ff17995b5829d4e42e65647e1e6..79b3b4f686b40383a26626edc4a3fdb0ca520efc 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -7,6 +7,14 @@ flag { bug: "232195501" } +flag { + name: "defer_display_updates" + namespace: "window_manager" + description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running" + bug: "259220649" + is_fixed_read_only: true +} + flag { name: "close_to_square_config_includes_status_bar" namespace: "windowing_frontend" diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index b1d22e069d9d95c2dac2eed64aa514312d5bd385..77e150239803c1364b319b042cbed4f9098a97b2 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -81,11 +81,6 @@ public class SystemUiSystemPropertiesFlags { public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag( "persist.sysui.notification.propagate_channel_updates_to_conversations"); - // TODO: b/291907312 - remove feature flags - /** Gating the NMS->NotificationAttentionHelper buzzBeepBlink refactor */ - public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag( - "persist.debug.sysui.notification.enable_attention_helper_refactor"); - // TODO b/291899544: for released flags, use resource config values /** Value used by polite notif. feature */ public static final Flag NOTIF_COOLDOWN_T1 = devFlag( diff --git a/core/proto/android/nfc/Android.bp b/core/proto/android/nfc/Android.bp deleted file mode 100644 index 6a62c917f2405698fde7c2e8bddb430c44415244..0000000000000000000000000000000000000000 --- a/core/proto/android/nfc/Android.bp +++ /dev/null @@ -1,43 +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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -filegroup { - name: "srcs_nfc_proto", - srcs: [ - "*.proto", - ], -} - -// Will be statically linked by `framework-nfc`. -java_library { - name: "nfc-proto-java-gen", - installable: false, - proto: { - type: "stream", - include_dirs: [ - "external/protobuf/src", - ], - }, - srcs: [ - ":srcs_nfc_proto", - ], - sdk_version: "current", - min_sdk_version: "current", -} diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 04fd70a96201b4d03f259a0e567b7a63e3573ec9..3496994fe1738f8c330605391146b375e62ed06a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -10090,6 +10090,15 @@ <!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. --> <attr name="lStar" format="float"/> + <!-- The attributes of the {@code <locale-config>} tag. --> + <!-- @FlaggedApi("android.content.res.default_locale") --> + <declare-styleable name="LocaleConfig"> + <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a> + the strings in values/strings.xml (the default strings in the directory with no locale + qualifier) are in. --> + <attr name="defaultLocale" format="string"/> + </declare-styleable> + <!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. --> <declare-styleable name="LocaleConfig_Locale"> <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index adc7fe0922aab2617f66d6f3d16114c06e130bac..4cd4f638e191ba044e2681124180657a87ad2954 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -110,6 +110,8 @@ <eat-comment/> <staging-public-group type="attr" first-id="0x01bd0000"> + <!-- @FlaggedApi("android.content.res.default_locale") --> + <public name="defaultLocale"/> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java index 1f00a7a12c542e2ec2035a556f9bca0a6b5b8c5c..88fc8267f5b2b3a6f19be4dd7ef6b55f1aca9283 100644 --- a/core/tests/coretests/src/android/os/LocaleListTest.java +++ b/core/tests/coretests/src/android/os/LocaleListTest.java @@ -81,4 +81,49 @@ public class LocaleListTest extends TestCase { // restore the original values LocaleList.setDefault(originalLocaleList, originalLocaleIndex); } + + @SmallTest + public void testIntersection() { + LocaleList localesWithN = new LocaleList( + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + Locale.ITALIAN, + Locale.JAPANESE, + Locale.KOREAN, + Locale.CHINESE, + Locale.SIMPLIFIED_CHINESE, + Locale.TRADITIONAL_CHINESE, + Locale.FRANCE, + Locale.GERMANY, + Locale.JAPAN, + Locale.CANADA, + Locale.CANADA_FRENCH); + LocaleList localesWithE = new LocaleList( + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + Locale.JAPANESE, + Locale.KOREAN, + Locale.CHINESE, + Locale.SIMPLIFIED_CHINESE, + Locale.TRADITIONAL_CHINESE, + Locale.FRANCE, + Locale.GERMANY, + Locale.CANADA_FRENCH); + LocaleList localesWithNAndE = new LocaleList( + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + Locale.JAPANESE, + Locale.KOREAN, + Locale.CHINESE, + Locale.SIMPLIFIED_CHINESE, + Locale.TRADITIONAL_CHINESE, + Locale.FRANCE, + Locale.GERMANY, + Locale.CANADA_FRENCH); + + assertEquals(localesWithNAndE, new LocaleList(localesWithE.getIntersection(localesWithN))); + } } diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java index 517aeae53784e135a24e2bd6b42f45887b7efcd3..0855268411ebceeb4e61f536c38bfd61d124ebca 100644 --- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java +++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java @@ -20,8 +20,6 @@ import static android.service.notification.NotificationListenerService.Ranking.U import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM; - import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -42,16 +40,12 @@ import android.content.pm.ShortcutInfo; import android.os.Bundle; import android.os.Parcel; import android.os.SharedMemory; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.TestableContext; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; -import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag; -import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver; - -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -71,6 +65,9 @@ public class NotificationRankingUpdateTest { private NotificationChannel mNotificationChannel; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + // TODO(b/284297289): remove this flag set once resolved. @Parameterized.Parameters(name = "rankingUpdateAshmem={0}") public static Boolean[] getRankingUpdateAshmem() { @@ -424,30 +421,11 @@ public class NotificationRankingUpdateTest { mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT); - SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() { - @Override - public boolean isEnabled(Flag flag) { - if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) { - return mRankingUpdateAshmem; - } - return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag); - } - - @Override - public int getIntValue(Flag flag) { - return 0; - } - - @Override - public String getStringValue(Flag flag) { - return null; - } - }; - } - - @After - public void tearDown() { - SystemUiSystemPropertiesFlags.TEST_RESOLVER = null; + if (mRankingUpdateAshmem) { + mSetFlagsRule.enableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM); + } else { + mSetFlagsRule.disableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM); + } } /** @@ -497,8 +475,7 @@ public class NotificationRankingUpdateTest { parcel.setDataPosition(0); NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel); // The rankingUpdate file descriptor is only non-null in the new path. - if (SystemUiSystemPropertiesFlags.getResolver().isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) { + if (Flags.rankingUpdateAshmem()) { assertTrue(nru1.isFdNotNullAndClosed()); } detailedAssertEquals(nru, nru1); @@ -636,7 +613,7 @@ public class NotificationRankingUpdateTest { @Test public void testRankingUpdate_writesSmartActionToParcel() { - if (!mRankingUpdateAshmem) { + if (!Flags.rankingUpdateAshmem()) { return; } ArrayList<Notification.Action> actions = new ArrayList<>(); @@ -674,7 +651,7 @@ public class NotificationRankingUpdateTest { @Test public void testRankingUpdate_handlesEmptySmartActionList() { - if (!mRankingUpdateAshmem) { + if (!Flags.rankingUpdateAshmem()) { return; } ArrayList<Notification.Action> actions = new ArrayList<>(); @@ -697,7 +674,7 @@ public class NotificationRankingUpdateTest { @Test public void testRankingUpdate_handlesNullSmartActionList() { - if (!mRankingUpdateAshmem) { + if (!Flags.rankingUpdateAshmem()) { return; } NotificationListenerService.Ranking ranking = diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java index e117051ba9deb481bbe120ccd8aa25213e62bf16..71bdce4ecb0ea3e37cf89d29246b57f2c4d5df65 100644 --- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java +++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java @@ -23,6 +23,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; @@ -148,6 +149,28 @@ public class SurfaceControlRegistryTests { reporter.assertLastReportedSetEquals(sc5, sc6, sc7, sc8); } + @Test + public void testCallStackDebugging_matchesFilters() { + SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance(); + + // Specific name, any call + registry.setCallStackDebuggingParams("com.android.app1", ""); + assertFalse(registry.matchesForCallStackDebugging("com.android.noMatchApp", "setAlpha")); + assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha")); + + // Any name, specific call + registry.setCallStackDebuggingParams("", "setAlpha"); + assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer")); + assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha")); + assertTrue(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha")); + + // Specific name, specific call + registry.setCallStackDebuggingParams("com.android.app1", "setAlpha"); + assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer")); + assertFalse(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha")); + assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha")); + } + private SurfaceControl buildTestSurface() { return new SurfaceControl.Builder() .setContainerLayer() diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index b71aaf3fc2e679d2e4f359f2d882c50369280afc..cc73ecee2146574c6a209fea2aa620eee4d09ca8 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2887,6 +2887,12 @@ "group": "WM_DEBUG_RESIZE", "at": "com\/android\/server\/wm\/WindowState.java" }, + "419378610": { + "message": "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d", + "level": "VERBOSE", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "422634333": { "message": "First draw done in potential wallpaper target %s", "level": "VERBOSE", @@ -4339,12 +4345,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, - "1936800105": { - "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d", - "level": "VERBOSE", - "group": "WM_DEBUG_CONTENT_RECORDING", - "at": "com\/android\/server\/wm\/ContentRecorder.java" - }, "1945495497": { "message": "Focused window didn't have a valid surface drawn.", "level": "DEBUG", diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig index e030dad6bf14680baac1180c12ae134b38f75320..9a0a22a08a0c1dc4c1ef3776076bdf96f60fa147 100644 --- a/graphics/java/android/framework_graphics.aconfig +++ b/graphics/java/android/framework_graphics.aconfig @@ -2,7 +2,7 @@ package: "com.android.graphics.flags" flag { name: "exact_compute_bounds" - namespace: "framework_graphics" + namespace: "core_graphics" description: "Add a function without unused exact param for computeBounds." bug: "304478551" } \ No newline at end of file diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java new file mode 100644 index 0000000000000000000000000000000000000000..ff49cdcab349e7d79127a46611555febd607c5b2 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java @@ -0,0 +1,119 @@ +/* + * 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 androidx.window.extensions.embedding; + +import static java.util.Objects.requireNonNull; + +import android.graphics.Rect; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +/** + * The parameter to create an overlay container that retrieved from + * {@link android.app.ActivityOptions} bundle. + */ +class OverlayCreateParams { + + // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack. + @VisibleForTesting + static final String KEY_OVERLAY_CREATE_PARAMS = + "androidx.window.extensions.OverlayCreateParams"; + + @VisibleForTesting + static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID = + "androidx.window.extensions.OverlayCreateParams.taskId"; + + @VisibleForTesting + static final String KEY_OVERLAY_CREATE_PARAMS_TAG = + "androidx.window.extensions.OverlayCreateParams.tag"; + + @VisibleForTesting + static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS = + "androidx.window.extensions.OverlayCreateParams.bounds"; + + private final int mTaskId; + + @NonNull + private final String mTag; + + @NonNull + private final Rect mBounds; + + OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) { + mTaskId = taskId; + mTag = requireNonNull(tag); + mBounds = requireNonNull(bounds); + } + + int getTaskId() { + return mTaskId; + } + + @NonNull + String getTag() { + return mTag; + } + + @NonNull + Rect getBounds() { + return mBounds; + } + + @Override + public int hashCode() { + int result = mTaskId; + result = 31 * result + mTag.hashCode(); + result = 31 * result + mBounds.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof OverlayCreateParams thatParams)) return false; + return mTaskId == thatParams.mTaskId + && mTag.equals(thatParams.mTag) + && mBounds.equals(thatParams.mBounds); + } + + @Override + public String toString() { + return OverlayCreateParams.class.getSimpleName() + ": {" + + "taskId=" + mTaskId + + ", tag=" + mTag + + ", bounds=" + mBounds + + "}"; + } + + /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */ + @Nullable + static OverlayCreateParams fromBundle(@NonNull Bundle bundle) { + final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS); + if (paramsBundle == null) { + return null; + } + final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID); + final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG)); + final Rect bounds = requireNonNull(paramsBundle.getParcelable( + KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class)); + + return new OverlayCreateParams(taskId, tag, bounds); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index cdfc4c87d27182e23ad3984b468eab205c28424c..2f1eec15e4155291004d85b976df0b526a2d78b1 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -40,9 +40,10 @@ import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceh import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; +import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair; import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; -import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics; +import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; import android.app.Activity; @@ -87,6 +88,7 @@ import androidx.window.extensions.embedding.TransactionManager.TransactionRecord import androidx.window.extensions.layout.WindowLayoutComponentImpl; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.Collections; @@ -123,8 +125,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * and unregistered via {@link #clearSplitAttributesCalculator()}. * This is called when: * <ul> - * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer, - * WindowContainerTransaction)}</li> + * <li>{@link SplitPresenter#updateSplitContainer}</li> * <li>There's a started Activity which matches {@link SplitPairRule} </li> * <li>Checking whether the place holder should be launched if there's a Activity matches * {@link SplitPlaceholderRule} </li> @@ -759,6 +760,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (targetContainer == null) { // When there is no embedding rule matched, try to place it in the top container // like a normal launch. + // TODO(b/301034784): Check if it makes sense to place the activity in overlay + // container. targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer(); } if (targetContainer == null) { @@ -1007,6 +1010,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return; } + // TODO(b/301034784): Check if it makes sense to place the activity in overlay container. final TaskFragmentContainer targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer(); if (targetContainer == null) { @@ -1166,7 +1170,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity)); if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() && canReuseContainer(splitRule, splitContainer.getSplitRule(), - getTaskWindowMetrics(taskProperties.getConfiguration()), + taskProperties.getTaskMetrics(), calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) { // Can launch in the existing secondary container if the rules share the same // presentation. @@ -1408,6 +1412,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private TaskFragmentContainer createEmptyExpandedContainer( @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, @Nullable Activity launchingActivity) { + return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity, + null /* overlayTag */); + } + + /** + * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into. + * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an + * overlay container. + */ + @VisibleForTesting + @GuardedBy("mLock") + @Nullable + TaskFragmentContainer createEmptyContainer( + @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId, + @NonNull Rect bounds, @Nullable Activity launchingActivity, + @Nullable String overlayTag) { // We need an activity in the organizer process in the same Task to use as the owner // activity, as well as to get the Task window info. final Activity activityInTask; @@ -1423,13 +1443,46 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Can't find any activity in the Task that we can use as the owner activity. return null; } - final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask, - taskId); - mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(), - activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); - mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(), + final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */, + intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag); + final IBinder taskFragmentToken = container.getTaskFragmentToken(); + // Note that taskContainer will not exist before calling #newContainer if the container + // is the first embedded TF in the task. + final TaskContainer taskContainer = container.getTaskContainer(); + final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds(); + final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds); + final int windowingMode = taskContainer + .getWindowingModeForSplitTaskFragment(sanitizedBounds); + mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(), + sanitizedBounds, windowingMode); + mPresenter.updateAnimationParams(wct, taskFragmentToken, TaskFragmentAnimationParams.DEFAULT); - return expandedContainer; + mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken, + overlayTag != null && !sanitizedBounds.isEmpty()); + + return container; + } + + /** + * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully + * covered by the task bounds. Otherwise, returns {@code bounds}. + */ + @NonNull + private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent, + @NonNull Rect taskBounds) { + if (bounds.isEmpty()) { + // Don't need to check if the bounds follows the task bounds. + return bounds; + } + if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) { + // Expand the bounds if the bounds are smaller than minimum dimensions. + return new Rect(); + } + if (!taskBounds.contains(bounds)) { + // Expand the bounds if the bounds exceed the task bounds. + return new Rect(); + } + return bounds; } /** @@ -1449,8 +1502,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer); final TaskContainer.TaskProperties taskProperties = mPresenter .getTaskProperties(primaryActivity); - final WindowMetrics taskWindowMetrics = getTaskWindowMetrics( - taskProperties.getConfiguration()); + final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics(); final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes( taskProperties, splitRule, splitRule.getDefaultSplitAttributes(), getActivityIntentMinDimensionsPair(primaryActivity, intent)); @@ -1519,14 +1571,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, @NonNull Activity activityInTask, int taskId) { return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, - activityInTask, taskId, null /* pairedPrimaryContainer */); + activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */); } @GuardedBy("mLock") TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, - activityInTask, taskId, null /* pairedPrimaryContainer */); + activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */); + } + + @GuardedBy("mLock") + TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, + @NonNull Activity activityInTask, int taskId, + @NonNull TaskFragmentContainer pairedPrimaryContainer) { + return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, + activityInTask, taskId, pairedPrimaryContainer, null /* tag */); } /** @@ -1540,11 +1600,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * @param taskId parent Task of the new TaskFragment. * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is * set, the new container will be added right above it. + * @param overlayTag The tag for the new created overlay container. It must be + * needed if {@code isOverlay} is {@code true}. Otherwise, + * it should be {@code null}. */ @GuardedBy("mLock") TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId, - @Nullable TaskFragmentContainer pairedPrimaryContainer) { + @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) { if (activityInTask == null) { throw new IllegalArgumentException("activityInTask must not be null,"); } @@ -1553,7 +1616,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final TaskContainer taskContainer = mTaskContainers.get(taskId); final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, - pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer); + pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag); return container; } @@ -1754,13 +1817,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes} * are {@code null}, the {@link SplitAttributes} will be calculated with - * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}. + * {@link SplitPresenter#computeSplitAttributes}. * * @param splitContainer The {@link SplitContainer} to update * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}. * Otherwise, use the value calculated by - * {@link SplitPresenter#computeSplitAttributes( - * TaskContainer.TaskProperties, SplitRule, Pair)} + * {@link SplitPresenter#computeSplitAttributes} * * @return {@code true} if the update succeed. Otherwise, returns {@code false}. */ @@ -2255,6 +2317,96 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return shouldRetainAssociatedContainer(finishingContainer, associatedContainer); } + /** + * Gets all overlay containers from all tasks in this process, or an empty list if there's + * no overlay container. + * <p> + * Note that we only support one overlay container for each task, but an app could have multiple + * tasks. + */ + @VisibleForTesting + @GuardedBy("mLock") + @NonNull + List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() { + final List<TaskFragmentContainer> overlayContainers = new ArrayList<>(); + for (int i = 0; i < mTaskContainers.size(); i++) { + final TaskContainer taskContainer = mTaskContainers.valueAt(i); + final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer(); + if (overlayContainer != null) { + overlayContainers.add(overlayContainer); + } + } + return overlayContainers; + } + + @VisibleForTesting + // Suppress GuardedBy warning because lint ask to mark this method as + // @GuardedBy(container.mController.mLock), which is mLock itself + @SuppressWarnings("GuardedBy") + @GuardedBy("mLock") + @Nullable + TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded( + @NonNull WindowContainerTransaction wct, + @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId, + @NonNull Intent intent, @NonNull Activity launchActivity) { + final int taskId = overlayCreateParams.getTaskId(); + if (taskId != launchTaskId) { + // The task ID doesn't match the launch activity's. Cannot determine the host task + // to launch the overlay. + throw new IllegalArgumentException("The task ID of " + + "OverlayCreateParams#launchingActivity must match the task ID of " + + "the activity to #startActivity with the activity options that takes " + + "OverlayCreateParams."); + } + final List<TaskFragmentContainer> overlayContainers = + getAllOverlayTaskFragmentContainers(); + final String overlayTag = overlayCreateParams.getTag(); + + // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions + // specified by Intent, expand the overlay container to fill the parent task instead. + final Rect bounds = overlayCreateParams.getBounds(); + final Size minDimensions = getMinDimensions(intent); + final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds, + minDimensions); + if (!overlayContainers.isEmpty()) { + for (final TaskFragmentContainer overlayContainer : overlayContainers) { + if (!overlayTag.equals(overlayContainer.getOverlayTag()) + && taskId == overlayContainer.getTaskId()) { + // If there's an overlay container with different tag shown in the same + // task, dismiss the existing overlay container. + overlayContainer.finish(false /* shouldFinishDependant */, mPresenter, + wct, SplitController.this); + } + if (overlayTag.equals(overlayContainer.getOverlayTag()) + && taskId != overlayContainer.getTaskId()) { + // If there's an overlay container with same tag in a different task, + // dismiss the overlay container since the tag must be unique per process. + overlayContainer.finish(false /* shouldFinishDependant */, mPresenter, + wct, SplitController.this); + } + if (overlayTag.equals(overlayContainer.getOverlayTag()) + && taskId == overlayContainer.getTaskId()) { + // If there's an overlay container with the same tag and task ID, we treat + // the OverlayCreateParams as the update to the container. + final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties() + .getTaskMetrics().getBounds(); + final IBinder overlayToken = overlayContainer.getTaskFragmentToken(); + final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds); + mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds); + mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken, + !sanitizedBounds.isEmpty()); + // We can just return the updated overlay container and don't need to + // check other condition since we only have one OverlayCreateParams, and + // if the tag and task are matched, it's impossible to match another task + // or tag since tags and tasks are all unique. + return overlayContainer; + } + } + } + return createEmptyContainer(wct, intent, taskId, + (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag); + } + private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter { @Override @@ -2417,8 +2569,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer launchedInTaskFragment; if (launchingActivity != null) { final int taskId = getTaskId(launchingActivity); - launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, - launchingActivity); + final OverlayCreateParams overlayCreateParams = + OverlayCreateParams.fromBundle(options); + if (Flags.activityEmbeddingOverlayPresentationFlag() + && overlayCreateParams != null) { + launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct, + overlayCreateParams, taskId, intent, launchingActivity); + } else { + launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, + launchingActivity); + } } else { launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct, intent); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index d894487fafb60c005d9c117138a71b6c32fb309a..66e76c59565200a8303074d36fc193303c153476 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -30,12 +30,10 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; -import android.util.DisplayMetrics; import android.util.LayoutDirection; import android.util.Pair; import android.util.Size; import android.view.View; -import android.view.WindowInsets; import android.view.WindowMetrics; import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentCreationParams; @@ -307,8 +305,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } final int taskId = primaryContainer.getTaskId(); - final TaskFragmentContainer secondaryContainer = mController.newContainer( - null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId, + final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent, + launchingActivity, taskId, // Pass in the primary container to make sure it is added right above the primary. primaryContainer); final TaskContainer taskContainer = mController.getTaskContainer(taskId); @@ -618,7 +616,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes, @Nullable Pair<Size, Size> minDimensionsPair) { final Configuration taskConfiguration = taskProperties.getConfiguration(); - final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration); + final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics(); final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator = mController.getSplitAttributesCalculator(); final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics); @@ -713,11 +711,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return new Size(windowLayout.minWidth, windowLayout.minHeight); } - private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds, + static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds, @Nullable Size minDimensions) { if (minDimensions == null) { return false; } + // Empty bounds mean the bounds follow the parent host task's bounds. Skip the check. + if (bounds.isEmpty()) { + return false; + } return bounds.width() < minDimensions.getWidth() || bounds.height() < minDimensions.getHeight(); } @@ -1066,14 +1068,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) { - return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration()); - } - - @NonNull - static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) { - final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds(); - // TODO(b/190433398): Supply correct insets. - final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; - return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density); + return getTaskProperties(activity).getTaskMetrics(); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 9a0769a82d990d34cb252714bea0392e065f8d0e..f4427aaba182891a7c7d95d4715ef930ff6b7243 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -30,7 +30,10 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; +import android.util.DisplayMetrics; import android.util.Log; +import android.view.WindowInsets; +import android.view.WindowMetrics; import android.window.TaskFragmentInfo; import android.window.TaskFragmentParentInfo; import android.window.WindowContainerTransaction; @@ -61,6 +64,10 @@ class TaskContainer { @Nullable private SplitPinContainer mSplitPinContainer; + /** The overlay container in this Task. */ + @Nullable + private TaskFragmentContainer mOverlayContainer; + @NonNull private final Configuration mConfiguration; @@ -221,6 +228,12 @@ class TaskContainer { return null; } + /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */ + @Nullable + TaskFragmentContainer getOverlayContainer() { + return mOverlayContainer; + } + int indexOf(@NonNull TaskFragmentContainer child) { return mContainers.indexOf(child); } @@ -311,8 +324,8 @@ class TaskContainer { onTaskFragmentContainerUpdated(); } - void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) { - mContainers.removeAll(taskFragmentContainer); + void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) { + mContainers.removeAll(taskFragmentContainers); onTaskFragmentContainerUpdated(); } @@ -332,6 +345,15 @@ class TaskContainer { } private void onTaskFragmentContainerUpdated() { + // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce + // another special container that should also be on top in the future. + updateSplitPinContainerIfNecessary(); + // Update overlay container after split pin container since the overlay should be on top of + // pin container. + updateOverlayContainerIfNecessary(); + } + + private void updateSplitPinContainerIfNecessary() { if (mSplitPinContainer == null) { return; } @@ -344,10 +366,7 @@ class TaskContainer { } // Ensure the pinned container is top-most. - if (pinnedContainerIndex != mContainers.size() - 1) { - mContainers.remove(pinnedContainer); - mContainers.add(pinnedContainer); - } + moveContainerToLastIfNecessary(pinnedContainer); // Update the primary container adjacent to the pinned container if needed. final TaskFragmentContainer adjacentContainer = @@ -359,6 +378,31 @@ class TaskContainer { } } + private void updateOverlayContainerIfNecessary() { + final List<TaskFragmentContainer> overlayContainers = mContainers.stream() + .filter(TaskFragmentContainer::isOverlay).toList(); + if (overlayContainers.size() > 1) { + throw new IllegalStateException("There must be at most one overlay container per Task"); + } + mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0); + if (mOverlayContainer != null) { + moveContainerToLastIfNecessary(mOverlayContainer); + } + } + + /** Moves the {@code container} to the last to align taskFragments' z-order. */ + private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) { + final int index = mContainers.indexOf(container); + if (index < 0) { + Log.w(TAG, "The container:" + container + " is not in the container list!"); + return; + } + if (index != mContainers.size() - 1) { + mContainers.remove(container); + mContainers.add(container); + } + } + /** * Gets the descriptors of split states in this Task. * @@ -398,6 +442,15 @@ class TaskContainer { return mConfiguration; } + /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */ + @NonNull + WindowMetrics getTaskMetrics() { + final Rect taskBounds = mConfiguration.windowConfiguration.getBounds(); + // TODO(b/190433398): Supply correct insets. + final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density); + } + /** Translates the given absolute bounds to relative bounds in this Task coordinate. */ void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) { if (inOutBounds.isEmpty()) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 0a694b5c3b645b1e184b0755e1ad8ef61c7683b8..2ba5c9b320f1ea9857c02f5460e8c866a8ae980d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -102,6 +102,9 @@ class TaskFragmentContainer { */ private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>(); + @Nullable + private final String mOverlayTag; + /** Indicates whether the container was cleaned up after the last activity was removed. */ private boolean mIsFinished; @@ -157,15 +160,29 @@ class TaskFragmentContainer { */ private boolean mHasCrossProcessActivities; + /** + * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController, + * TaskFragmentContainer, String) + */ + TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, + @Nullable Intent pendingAppearedIntent, + @NonNull TaskContainer taskContainer, + @NonNull SplitController controller, + @Nullable TaskFragmentContainer pairedPrimaryContainer) { + this(pendingAppearedActivity, pendingAppearedIntent, taskContainer, + controller, pairedPrimaryContainer, null /* overlayTag */); + } + /** * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. * @param pairedPrimaryContainer when it is set, the new container will be add right above it + * @param overlayTag Sets to indicate this taskFragment is an overlay container */ TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller, - @Nullable TaskFragmentContainer pairedPrimaryContainer) { + @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) { if ((pendingAppearedActivity == null && pendingAppearedIntent == null) || (pendingAppearedActivity != null && pendingAppearedIntent != null)) { throw new IllegalArgumentException( @@ -174,6 +191,8 @@ class TaskFragmentContainer { mController = controller; mToken = new Binder("TaskFragmentContainer"); mTaskContainer = taskContainer; + mOverlayTag = overlayTag; + if (pairedPrimaryContainer != null) { // The TaskFragment will be positioned right above the paired container. if (pairedPrimaryContainer.getTaskContainer() != taskContainer) { @@ -863,6 +882,20 @@ class TaskFragmentContainer { return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other); } + /** Returns whether this taskFragment container is an overlay container. */ + boolean isOverlay() { + return mOverlayTag != null; + } + + /** + * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this + * taskFragment container is not an overlay container. + */ + @Nullable + String getOverlayTag() { + return mOverlayTag; + } + @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); @@ -881,6 +914,7 @@ class TaskFragmentContainer { + " topNonFinishingActivity=" + getTopNonFinishingActivity() + " runningActivityCount=" + getRunningActivityCount() + " isFinished=" + mIsFinished + + " overlayTag=" + mOverlayTag + " lastRequestedBounds=" + mLastRequestedBounds + " pendingAppearedActivities=" + mPendingAppearedActivities + (includeContainersToFinishOnExit ? " containersToFinishOnExit=" diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp index ed2ff2de245b6db6c6bcf18877dca3217f7c340f..4ddbd13978d57d39999640d3865b69a26fdb865e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp +++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp @@ -36,6 +36,7 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "flag-junit", "mockito-target-extended-minus-junit4", "truth", "testables", diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..af8017ae9544a0b7785ff8ae86d36575bd025aac --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -0,0 +1,393 @@ +/* + * 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 androidx.window.extensions.embedding; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; +import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS; +import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS; +import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG; +import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Rect; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; +import android.window.TaskFragmentInfo; +import android.window.WindowContainerTransaction; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; +import androidx.window.extensions.layout.WindowLayoutComponentImpl; +import androidx.window.extensions.layout.WindowLayoutInfo; + +import com.android.window.flags.Flags; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Test class for overlay presentation feature. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:OverlayPresentationTest + */ +// Suppress GuardedBy warning on unit tests +@SuppressWarnings("GuardedBy") +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class OverlayPresentationTest { + + @Rule + public final SetFlagsRule mSetFlagRule = new SetFlagsRule(); + + private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS = + new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200)); + + private SplitController.ActivityStartMonitor mMonitor; + + private Intent mIntent; + + private TaskFragmentContainer mOverlayContainer1; + + private TaskFragmentContainer mOverlayContainer2; + + private Activity mActivity; + @Mock + private Resources mActivityResources; + + @Mock + private WindowContainerTransaction mTransaction; + @Mock + private Handler mHandler; + @Mock + private WindowLayoutComponentImpl mWindowLayoutComponent; + + private SplitController mSplitController; + private SplitPresenter mSplitPresenter; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) + .getCurrentWindowLayoutInfo(anyInt(), any()); + DeviceStateManagerFoldingFeatureProducer producer = + mock(DeviceStateManagerFoldingFeatureProducer.class); + mSplitController = new SplitController(mWindowLayoutComponent, producer); + mSplitPresenter = mSplitController.mPresenter; + mMonitor = mSplitController.getActivityStartMonitor(); + mIntent = new Intent(); + + spyOn(mSplitController); + spyOn(mSplitPresenter); + spyOn(mMonitor); + + doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean()); + final Configuration activityConfig = new Configuration(); + activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); + activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); + doReturn(activityConfig).when(mActivityResources).getConfiguration(); + doReturn(mHandler).when(mSplitController).getHandler(); + mActivity = createMockActivity(); + + mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG); + } + + /** Creates a mock activity in the organizer process. */ + @NonNull + private Activity createMockActivity() { + final Activity activity = mock(Activity.class); + doReturn(mActivityResources).when(activity).getResources(); + final IBinder activityToken = new Binder(); + doReturn(activityToken).when(activity).getActivityToken(); + doReturn(activity).when(mSplitController).getActivity(activityToken); + doReturn(TASK_ID).when(activity).getTaskId(); + doReturn(new ActivityInfo()).when(activity).getActivityInfo(); + doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); + return activity; + } + + @Test + public void testOverlayCreateParamsFromBundle() { + assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull(); + + assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle())) + .isEqualTo(TEST_OVERLAY_CREATE_PARAMS); + } + + @Test + public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() { + mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG); + + mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle()); + + verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(), + anyInt(), any(), any()); + } + + @NonNull + private static Bundle createOverlayCreateParamsTestBundle() { + final Bundle bundle = new Bundle(); + + final Bundle paramsBundle = new Bundle(); + paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID, + TEST_OVERLAY_CREATE_PARAMS.getTaskId()); + paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag()); + paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS, + TEST_OVERLAY_CREATE_PARAMS.getBounds()); + + bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle); + + return bundle; + } + + @Test + public void testGetOverlayContainers() { + assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty(); + + final TaskFragmentContainer overlayContainer1 = + createTestOverlayContainer(TASK_ID, "test1"); + + assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(overlayContainer1); + + assertThrows( + "The exception must throw if there are two overlay containers in the same task.", + IllegalStateException.class, + () -> createTestOverlayContainer(TASK_ID, "test2")); + + final TaskFragmentContainer overlayContainer3 = + createTestOverlayContainer(TASK_ID + 1, "test3"); + + assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(overlayContainer1, overlayContainer3); + } + + @Test + public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() { + assertThrows("The method must return null due to task mismatch between" + + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class, + () -> createOrUpdateOverlayTaskFragmentIfNeeded( + TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1)); + } + + @Test + public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() { + createExistingOverlayContainers(); + + final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( + new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID); + + assertWithMessage("overlayContainer1 must be dismissed since the new overlay container" + + " is launched to the same task") + .that(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(mOverlayContainer2, overlayContainer); + } + + @Test + public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() { + createExistingOverlayContainers(); + + final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( + new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)), + TASK_ID + 2); + + assertWithMessage("overlayContainer1 must be dismissed since the new overlay container" + + " is launched with the same tag as an existing overlay container in a different " + + "task") + .that(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(mOverlayContainer2, overlayContainer); + } + + @Test + public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() { + createExistingOverlayContainers(); + + final Rect bounds = new Rect(0, 0, 100, 100); + final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( + new OverlayCreateParams(TASK_ID, "test1", bounds), + TASK_ID); + + assertWithMessage("overlayContainer1 must be updated since the new overlay container" + + " is launched with the same tag and task") + .that(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(mOverlayContainer1, mOverlayContainer2); + + assertThat(overlayContainer).isEqualTo(mOverlayContainer1); + verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction), + eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds)); + } + + @Test + public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() { + createExistingOverlayContainers(); + + final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( + new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)), + TASK_ID); + + // OverlayContainer1 is dismissed since new container is launched in the same task with + // different tag. OverlayContainer2 is dismissed since new container is launched with the + // same tag in different task. + assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed") + .that(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(overlayContainer); + } + + private void createExistingOverlayContainers() { + mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1"); + mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2"); + List<TaskFragmentContainer> overlayContainers = mSplitController + .getAllOverlayTaskFragmentContainers(); + assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2); + } + + @Test + public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() { + mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(), + MinimumDimensionActivity.class)); + + final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( + TEST_OVERLAY_CREATE_PARAMS, TASK_ID); + final IBinder overlayToken = overlayContainer.getTaskFragmentToken(); + + assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(overlayContainer); + assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue(); + verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken, + false); + + // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case. + clearInvocations(mSplitPresenter); + createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID); + + verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect()); + verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken, + false); + assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(overlayContainer); + } + + @Test + public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() { + final Rect bounds = new Rect(TASK_BOUNDS); + bounds.offset(10, 10); + final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID, + "test", bounds); + + final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( + paramsOutsideTaskBounds, TASK_ID); + final IBinder overlayToken = overlayContainer.getTaskFragmentToken(); + + assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(overlayContainer); + assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue(); + verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken, + false); + + // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case. + clearInvocations(mSplitPresenter); + createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID); + + verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect()); + verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken, + false); + assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(overlayContainer); + } + + @Test + public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() { + final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded( + TEST_OVERLAY_CREATE_PARAMS, TASK_ID); + + assertThat(mSplitController.getAllOverlayTaskFragmentContainers()) + .containsExactly(overlayContainer); + assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID); + assertThat(overlayContainer + .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue(); + assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag()); + } + + /** + * A simplified version of {@link SplitController.ActivityStartMonitor + * #createOrUpdateOverlayTaskFragmentIfNeeded} + */ + @Nullable + private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded( + @NonNull OverlayCreateParams params, int taskId) { + return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params, + taskId, mIntent, mActivity); + } + + @NonNull + private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) { + TaskFragmentContainer overlayContainer = mSplitController.newContainer( + null /* pendingAppearedActivity */, mIntent, mActivity, taskId, + null /* pairedPrimaryContainer */, tag); + setupTaskFragmentInfo(overlayContainer, mActivity); + return overlayContainer; + } + + private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container, + @NonNull Activity activity) { + final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity); + container.setInfo(mTransaction, info); + mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info); + } +} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index d440a3eb95de1243819d040ada89f8d5975562c6..6c0b3cf7971d5362d9c2e6ae2a151e8338dddbb3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -60,6 +60,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; @@ -634,7 +635,8 @@ public class SplitControllerTest { false /* isOnReparent */); assertFalse(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any()); + verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), + anyString()); } @Test @@ -796,7 +798,8 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any()); + verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), + anyString()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @@ -838,7 +841,8 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any()); + verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), + anyString()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index 738c94e82a950411aeb544ee6bf1f43d23c1a1c6..79f306ece283c6df92e7d825753cf1ae371f9c1d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.animation; import static android.view.View.LAYOUT_DIRECTION_RTL; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; +import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth; import android.content.res.Resources; import android.graphics.Path; @@ -375,6 +376,9 @@ public class ExpandedAnimationController mMagnetizedBubbleDraggingOut.setMagnetListener(listener); mMagnetizedBubbleDraggingOut.setHapticsEnabled(true); mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); + int screenWidthPx = mLayout.getContext().getResources().getDisplayMetrics().widthPixels; + mMagnetizedBubbleDraggingOut.setFlingToTargetWidthPercent( + getFlingToDismissTargetWidth(screenWidthPx)); } private void springBubbleTo(View bubble, float x, float y) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..2a44f04f1358de19552ee6f71730bd2ff519956e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt @@ -0,0 +1,42 @@ +/* + * 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.wm.shell.bubbles.animation + +/** Utils related to the fling to dismiss animation. */ +object FlingToDismissUtils { + + /** The target width surrounding the dismiss target on a small width screen, e.g. phone. */ + private const val FLING_TO_DISMISS_TARGET_WIDTH_SMALL = 3f + /** + * The target width surrounding the dismiss target on a medium width screen, e.g. tablet in + * portrait. + */ + private const val FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM = 4.5f + /** + * The target width surrounding the dismiss target on a large width screen, e.g. tablet in + * landscape. + */ + private const val FLING_TO_DISMISS_TARGET_WIDTH_LARGE = 6f + + /** Returns the dismiss target width for the specified [screenWidthPx]. */ + @JvmStatic + fun getFlingToDismissTargetWidth(screenWidthPx: Int) = when { + screenWidthPx >= 2000 -> FLING_TO_DISMISS_TARGET_WIDTH_LARGE + screenWidthPx >= 1500 -> FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM + else -> FLING_TO_DISMISS_TARGET_WIDTH_SMALL + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index aad2683943050ea11b2aa6f83df2b4bd567b864f..e48732801094320b5dd8b229c5b03220fdc0ae97 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles.animation; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; +import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth; import android.content.ContentResolver; import android.content.res.Resources; @@ -851,6 +852,15 @@ public class StackAnimationController extends if (mLayout != null) { Resources res = mLayout.getContext().getResources(); mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); + updateFlingToDismissTargetWidth(); + } + } + + private void updateFlingToDismissTargetWidth() { + if (mLayout != null && mMagnetizedStack != null) { + int screenWidthPx = mLayout.getResources().getDisplayMetrics().widthPixels; + mMagnetizedStack.setFlingToTargetWidthPercent( + getFlingToDismissTargetWidth(screenWidthPx)); } } @@ -1022,23 +1032,8 @@ public class StackAnimationController extends }; mMagnetizedStack.setHapticsEnabled(true); mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); + updateFlingToDismissTargetWidth(); } - - final ContentResolver contentResolver = mLayout.getContext().getContentResolver(); - final float minVelocity = Settings.Secure.getFloat(contentResolver, - "bubble_dismiss_fling_min_velocity", - mMagnetizedStack.getFlingToTargetMinVelocity() /* default */); - final float maxVelocity = Settings.Secure.getFloat(contentResolver, - "bubble_dismiss_stick_max_velocity", - mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */); - final float targetWidth = Settings.Secure.getFloat(contentResolver, - "bubble_dismiss_target_width_percent", - mMagnetizedStack.getFlingToTargetWidthPercent() /* default */); - - mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity); - mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity); - mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth); - return mMagnetizedStack; } @@ -1053,7 +1048,7 @@ public class StackAnimationController extends * property directly to move the first bubble and cause the stack to 'follow' to the new * location. * - * This could also be achieved by simply animating the first bubble view and adding an update + * <p>This could also be achieved by simply animating the first bubble view and adding an update * listener to dispatch movement to the rest of the stack. However, this would require * duplication of logic in that update handler - it's simpler to keep all logic contained in the * {@link #moveFirstBubbleWithStackFollowing} method. diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 7f020725d61f173f96cc8708011997f65aada280..acfb259d73593cfe8598f76ff93288da9808d67b 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -39,7 +39,25 @@ filegroup { } filegroup { - name: "WMShellFlickerTestsPip-src", + name: "WMShellFlickerTestsPip1-src", + srcs: [ + "src/com/android/wm/shell/flicker/pip/A*.kt", + "src/com/android/wm/shell/flicker/pip/B*.kt", + "src/com/android/wm/shell/flicker/pip/C*.kt", + "src/com/android/wm/shell/flicker/pip/D*.kt", + "src/com/android/wm/shell/flicker/pip/S*.kt", + ], +} + +filegroup { + name: "WMShellFlickerTestsPip2-src", + srcs: [ + "src/com/android/wm/shell/flicker/pip/E*.kt", + ], +} + +filegroup { + name: "WMShellFlickerTestsPip3-src", srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"], } @@ -176,7 +194,9 @@ android_test { ], exclude_srcs: [ ":WMShellFlickerTestsBubbles-src", - ":WMShellFlickerTestsPip-src", + ":WMShellFlickerTestsPip1-src", + ":WMShellFlickerTestsPip2-src", + ":WMShellFlickerTestsPip3-src", ":WMShellFlickerTestsPipCommon-src", ":WMShellFlickerTestsPipApps-src", ":WMShellFlickerTestsSplitScreenGroup1-src", @@ -200,18 +220,48 @@ android_test { } android_test { - name: "WMShellFlickerTestsPip", + name: "WMShellFlickerTestsPip1", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestPip.xml"], + package_name: "com.android.wm.shell.flicker.pip", + instrumentation_target_package: "com.android.wm.shell.flicker.pip", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsPip1-src", + ":WMShellFlickerTestsPipCommon-src", + ], +} + +android_test { + name: "WMShellFlickerTestsPip2", defaults: ["WMShellFlickerTestsDefault"], additional_manifests: ["manifests/AndroidManifestPip.xml"], package_name: "com.android.wm.shell.flicker.pip", instrumentation_target_package: "com.android.wm.shell.flicker.pip", srcs: [ ":WMShellFlickerTestsBase-src", - ":WMShellFlickerTestsPip-src", + ":WMShellFlickerTestsPip2-src", ":WMShellFlickerTestsPipCommon-src", ], } +android_test { + name: "WMShellFlickerTestsPip3", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestPip.xml"], + package_name: "com.android.wm.shell.flicker.pip", + instrumentation_target_package: "com.android.wm.shell.flicker.pip", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsPip3-src", + ":WMShellFlickerTestsPipCommon-src", + ], + exclude_srcs: [ + ":WMShellFlickerTestsPip1-src", + ":WMShellFlickerTestsPip2-src", + ], +} + android_test { name: "WMShellFlickerTestsPipApps", defaults: ["WMShellFlickerTestsDefault"], diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index 19c8435facaf8fdcc667c48b30e52bf27befa287..94e3959782ed1c1a0b50ae1ad6d939c1a0ef8a7e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -22,6 +22,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice +import com.android.wm.shell.flicker.pip.common.EnterPipTransition import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -55,7 +56,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : - EnterPipViaAppUiButtonTest(flicker) { + EnterPipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } override val defaultEnterPip: FlickerBuilder.() -> Unit = { diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 7f226939ffaaaa21e7c4dfd9f47b7ba293866ac9..d056248273b2ac3bf58f2aacd4bfce50e5e3f2fa 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -785,7 +785,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( has_locale = true; } - // if we don't have a result yet + // if we don't have a result yet if (!final_result || // or this config is better before the locale than the existing result result->config.isBetterThanBeforeLocale(final_result->config, desired_config) || @@ -863,9 +863,12 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( // We can skip calling ResTable_config::match() if the caller does not care for the // configuration to match or if we're using the list of types that have already had their - // configuration matched. + // configuration matched. The exception to this is when the user has multiple locales set + // because the filtered list will then have values from multiple locales and we will need to + // call match() to make sure the current entry matches the config we are currently checking. const ResTable_config& this_config = type_entry->config; - if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) { + if (!((use_filtered && (configurations_.size() == 1)) + || ignore_configuration || this_config.match(desired_config))) { continue; } diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index b00fc699e515d2e212c941e3e8afe53760e8ce7e..14602ef926d3495468f66817e0df0630a45e2ae3 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -139,6 +139,7 @@ CanvasContext::~CanvasContext() { mRenderNodes.clear(); mRenderThread.cacheManager().unregisterCanvasContext(this); mRenderThread.renderState().removeContextCallback(this); + mHintSessionWrapper->destroy(); } void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) { diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp index 1c3399a6650d743f9a962c4a55cb8a18808c7bf7..2362331aca26337898a2c99c45ecace90a575aa1 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.cpp +++ b/libs/hwui/renderthread/HintSessionWrapper.cpp @@ -158,7 +158,6 @@ void HintSessionWrapper::sendLoadResetHint() { void HintSessionWrapper::sendLoadIncreaseHint() { if (!init()) return; mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)); - mLastFrameNotification = systemTime(); } bool HintSessionWrapper::alive() { diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp index a14ae1cc46ecde3e2ed771148b1ccc053356cbd8..10a740a1f803916628ed30aefe9e07c1e29ce6bb 100644 --- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp +++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp @@ -259,6 +259,31 @@ TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishe TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) { EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0); + EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1); + + mWrapper->init(); + waitForWrapperReady(); + // Init a second time just to grab the wrapper from the promise + mWrapper->init(); + EXPECT_EQ(mWrapper->alive(), true); + + // First schedule the deletion + scheduleDelayedDestroyManaged(); + + // Then, report an actual duration + mWrapper->reportActualWorkDuration(5_ms); + + // Then, run the delayed deletion after sending the update + allowDelayedDestructionToStart(); + waitForDelayedDestructionToFinish(); + + // Ensure it didn't close within the timeframe of the test + Mock::VerifyAndClearExpectations(sMockBinding.get()); + EXPECT_EQ(mWrapper->alive(), true); +} + +TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) { + EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); EXPECT_CALL(*sMockBinding, fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP))) .Times(1); @@ -272,16 +297,46 @@ TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) { // First schedule the deletion scheduleDelayedDestroyManaged(); - // Then, send a hint to update the timestamp + // Then, send a load_up hint mWrapper->sendLoadIncreaseHint(); // Then, run the delayed deletion after sending the update allowDelayedDestructionToStart(); waitForDelayedDestructionToFinish(); - // Ensure it didn't close within the timeframe of the test + // Ensure it closed within the timeframe of the test Mock::VerifyAndClearExpectations(sMockBinding.get()); + EXPECT_EQ(mWrapper->alive(), false); +} + +TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) { + EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1); + + mWrapper->init(); + waitForWrapperReady(); + // Init a second time just to grab the wrapper from the promise + mWrapper->init(); EXPECT_EQ(mWrapper->alive(), true); + + // First schedule the deletion + scheduleDelayedDestroyManaged(); + + // Then, kill the session + mWrapper->destroy(); + + // Verify it died + Mock::VerifyAndClearExpectations(sMockBinding.get()); + EXPECT_EQ(mWrapper->alive(), false); + + EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0); + + // Then, run the delayed deletion after manually killing the session + allowDelayedDestructionToStart(); + waitForDelayedDestructionToFinish(); + + // Ensure it didn't close again and is still dead + Mock::VerifyAndClearExpectations(sMockBinding.get()); + EXPECT_EQ(mWrapper->alive(), false); } } // namespace android::uirenderer::renderthread \ No newline at end of file diff --git a/media/Android.bp b/media/Android.bp index f69dd3cc3206ee9bbc2f91abcd45ea0d7ad80a6b..349340804f1e64d72b2669c3677bb7ca783ed90b 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -23,6 +23,10 @@ aidl_interface { name: "soundtrigger_middleware-aidl", unstable: true, local_include_dir: "aidl", + defaults: [ + "latest_android_media_audio_common_types_import_interface", + "latest_android_media_soundtrigger_types_import_interface", + ], backend: { java: { sdk_version: "module_current", @@ -32,8 +36,6 @@ aidl_interface { "aidl/android/media/soundtrigger_middleware/*.aidl", ], imports: [ - "android.media.audio.common.types-V2", - "android.media.soundtrigger.types-V1", "media_permission-aidl", ], } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 91fa873078fcaebe57b19b5ddcb66000af115315..cccf6f1caca7ff9568bf7f8b058b37e03ebf6926 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -18,6 +18,9 @@ package android.media; import static android.media.MediaRouter2Utils.toUniqueId; +import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -141,6 +144,8 @@ public final class MediaRoute2Info implements Parcelable { TYPE_WIRED_HEADPHONES, TYPE_BLUETOOTH_A2DP, TYPE_HDMI, + TYPE_HDMI_ARC, + TYPE_HDMI_EARC, TYPE_USB_DEVICE, TYPE_USB_ACCESSORY, TYPE_DOCK, @@ -205,6 +210,22 @@ public final class MediaRoute2Info implements Parcelable { */ public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI; + /** + * Indicates the route is an Audio Return Channel of an HDMI connection. + * + * @see #getType + */ + @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) + public static final int TYPE_HDMI_ARC = AudioDeviceInfo.TYPE_HDMI_ARC; + + /** + * Indicates the route is an Enhanced Audio Return Channel of an HDMI connection. + * + * @see #getType + */ + @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) + public static final int TYPE_HDMI_EARC = AudioDeviceInfo.TYPE_HDMI_EARC; + /** * Indicates the route is a USB audio device. * @@ -907,6 +928,10 @@ public final class MediaRoute2Info implements Parcelable { return "BLUETOOTH_A2DP"; case TYPE_HDMI: return "HDMI"; + case TYPE_HDMI_ARC: + return "HDMI_ARC"; + case TYPE_HDMI_EARC: + return "HDMI_EARC"; case TYPE_DOCK: return "DOCK"; case TYPE_USB_DEVICE: diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 159427bc27966edac5ca87b03e613ca918afb81f..76a00acfa1c4c3cca1297098f4214345d7c95686 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -310,8 +310,11 @@ public final class MediaRouter2 { IMediaRouterService.Stub.asInterface( ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); + mSystemController = + new SystemRoutingController( + ProxyMediaRouter2Impl.getSystemSessionInfoImpl( + mMediaRouterService, clientPackageName)); mImpl = new ProxyMediaRouter2Impl(context, clientPackageName); - mSystemController = new SystemRoutingController(mImpl.getSystemSessionInfo()); } /** @@ -2057,13 +2060,7 @@ public final class MediaRouter2 { @Override public RoutingSessionInfo getSystemSessionInfo() { - RoutingSessionInfo result; - try { - result = mMediaRouterService.getSystemSessionInfoForPackage(mClientPackageName); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - return ensureClientPackageNameForSystemSession(result); + return getSystemSessionInfoImpl(mMediaRouterService, mClientPackageName); } /** @@ -2427,6 +2424,23 @@ public final class MediaRouter2 { releaseSession(controller.getRoutingSessionInfo()); } + /** + * Retrieves the system session info for the given package. + * + * <p>The returned routing session is guaranteed to have a non-null {@link + * RoutingSessionInfo#getClientPackageName() client package name}. + * + * <p>Extracted into a static method to allow calling this from the constructor. + */ + /* package */ static RoutingSessionInfo getSystemSessionInfoImpl( + @NonNull IMediaRouterService service, @NonNull String clientPackageName) { + try { + return service.getSystemSessionInfoForPackage(clientPackageName); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + /** * Sets the routing session's {@linkplain RoutingSessionInfo#getClientPackageName() client * package name} to {@link #mClientPackageName} if empty and returns the session. diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index 31e65eb13926a74f09d23e865e41f6fe54eb3b25..d0e860c9109f181f2b6186506f55e54089d05a41 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -176,4 +176,21 @@ interface IMediaProjectionManager { @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MANAGE_MEDIA_PROJECTION)") oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); + + /** + * Notifies system server that the permission request was initiated. + * + * <p>Only used for emitting atoms. + * + * @param hostUid The uid of the process requesting consent to capture, may be an app or + * SystemUI. + * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. + * Indicates the entry point for requesting the permission. Must be + * a valid state defined + * in the SessionCreationSource enum. + */ + @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource); } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java index e38e041a87dc1077eae456cc1a32e04fab1e9c79..2a2841745fa06d4cd01cd66b2dae1ebc0c028bc0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java @@ -39,39 +39,50 @@ public class DeviceIconUtil { @DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone; public DeviceIconUtil() { - List<Device> deviceList = Arrays.asList( - new Device( - AudioDeviceInfo.TYPE_USB_DEVICE, - MediaRoute2Info.TYPE_USB_DEVICE, - R.drawable.ic_headphone), - new Device( - AudioDeviceInfo.TYPE_USB_HEADSET, - MediaRoute2Info.TYPE_USB_HEADSET, - R.drawable.ic_headphone), - new Device( - AudioDeviceInfo.TYPE_USB_ACCESSORY, - MediaRoute2Info.TYPE_USB_ACCESSORY, - R.drawable.ic_headphone), - new Device( - AudioDeviceInfo.TYPE_DOCK, - MediaRoute2Info.TYPE_DOCK, - R.drawable.ic_dock_device), - new Device( - AudioDeviceInfo.TYPE_HDMI, - MediaRoute2Info.TYPE_HDMI, - R.drawable.ic_headphone), - new Device( - AudioDeviceInfo.TYPE_WIRED_HEADSET, - MediaRoute2Info.TYPE_WIRED_HEADSET, - R.drawable.ic_headphone), - new Device( - AudioDeviceInfo.TYPE_WIRED_HEADPHONES, - MediaRoute2Info.TYPE_WIRED_HEADPHONES, - R.drawable.ic_headphone), - new Device( - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, - MediaRoute2Info.TYPE_BUILTIN_SPEAKER, - R.drawable.ic_smartphone)); + List<Device> deviceList = + Arrays.asList( + new Device( + AudioDeviceInfo.TYPE_USB_DEVICE, + MediaRoute2Info.TYPE_USB_DEVICE, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_USB_HEADSET, + MediaRoute2Info.TYPE_USB_HEADSET, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_USB_ACCESSORY, + MediaRoute2Info.TYPE_USB_ACCESSORY, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_DOCK, + MediaRoute2Info.TYPE_DOCK, + R.drawable.ic_dock_device), + new Device( + AudioDeviceInfo.TYPE_HDMI, + MediaRoute2Info.TYPE_HDMI, + R.drawable.ic_headphone), + // TODO: b/306359110 - Put proper iconography for HDMI_ARC type. + new Device( + AudioDeviceInfo.TYPE_HDMI_ARC, + MediaRoute2Info.TYPE_HDMI_ARC, + R.drawable.ic_headphone), + // TODO: b/306359110 - Put proper iconography for HDMI_EARC type. + new Device( + AudioDeviceInfo.TYPE_HDMI_EARC, + MediaRoute2Info.TYPE_HDMI_EARC, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_WIRED_HEADSET, + MediaRoute2Info.TYPE_WIRED_HEADSET, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, + MediaRoute2Info.TYPE_WIRED_HEADPHONES, + R.drawable.ic_headphone), + new Device( + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + MediaRoute2Info.TYPE_BUILTIN_SPEAKER, + R.drawable.ic_smartphone)); for (int i = 0; i < deviceList.size(); i++) { Device device = deviceList.get(i); mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index bf63f5d80450a84b72a777700924da7d65fd15cd..5dacba5357cde263b398d3248486104a5c2b2f4f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -21,6 +21,8 @@ import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_DOCK; import static android.media.MediaRoute2Info.TYPE_GROUP; import static android.media.MediaRoute2Info.TYPE_HDMI; +import static android.media.MediaRoute2Info.TYPE_HDMI_ARC; +import static android.media.MediaRoute2Info.TYPE_HDMI_EARC; import static android.media.MediaRoute2Info.TYPE_HEARING_AID; import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER; import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR; @@ -635,6 +637,8 @@ public abstract class InfoMediaManager extends MediaManager { case TYPE_USB_ACCESSORY: case TYPE_DOCK: case TYPE_HDMI: + case TYPE_HDMI_ARC: + case TYPE_HDMI_EARC: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: mediaDevice = @@ -668,11 +672,12 @@ public abstract class InfoMediaManager extends MediaManager { route, mPackageName, mPreferenceItemMap.get(route.getId())); + break; default: Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType); break; - } + if (mediaDevice != null && !TextUtils.isEmpty(mPackageName) && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) { mediaDevice.setState(STATE_SELECTED); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 147412d08d2ce2620c5c844c3c82250490fd11f5..8085c998abea71ab719ed1551be60e1a3940e7fd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -21,6 +21,8 @@ import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_DOCK; import static android.media.MediaRoute2Info.TYPE_GROUP; import static android.media.MediaRoute2Info.TYPE_HDMI; +import static android.media.MediaRoute2Info.TYPE_HDMI_ARC; +import static android.media.MediaRoute2Info.TYPE_HDMI_EARC; import static android.media.MediaRoute2Info.TYPE_HEARING_AID; import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; @@ -140,7 +142,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE; return; } - switch (info.getType()) { case TYPE_GROUP: mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE; @@ -157,6 +158,8 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { case TYPE_USB_ACCESSORY: case TYPE_DOCK: case TYPE_HDMI: + case TYPE_HDMI_ARC: + case TYPE_HDMI_EARC: mType = MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE; break; case TYPE_HEARING_AID: diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index a63bbdf36fa86550ca7dcb08726999a77bd0ef96..c44f66e99d00148660755b65d421cabeb8c05e08 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -18,6 +18,8 @@ package com.android.settingslib.media; import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_DOCK; import static android.media.MediaRoute2Info.TYPE_HDMI; +import static android.media.MediaRoute2Info.TYPE_HDMI_ARC; +import static android.media.MediaRoute2Info.TYPE_HDMI_EARC; import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY; import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; import static android.media.MediaRoute2Info.TYPE_USB_HEADSET; @@ -71,6 +73,8 @@ public class PhoneMediaDevice extends MediaDevice { name = context.getString(R.string.media_transfer_this_device_name); break; case TYPE_HDMI: + case TYPE_HDMI_ARC: + case TYPE_HDMI_EARC: name = context.getString(R.string.media_transfer_external_device_name); break; default: @@ -144,6 +148,8 @@ public class PhoneMediaDevice extends MediaDevice { case TYPE_USB_ACCESSORY: case TYPE_DOCK: case TYPE_HDMI: + case TYPE_HDMI_ARC: + case TYPE_HDMI_EARC: id = USB_HEADSET_ID; break; case TYPE_BUILTIN_SPEAKER: diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 03f7c9968a1df98230759c3843f1426eace4b902..de73b77b21a30eb16489247ace9203bab9e1c43a 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -31,22 +31,6 @@ } ] }, - { - "name": "SystemUIGoogleScreenshotTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.Postsubmit" - } - ], - // The test doesn't run on AOSP Cuttlefish - "keywords": ["internal"] - }, { // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+) "name": "SystemUIGoogleBiometricsScreenshotTests", diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig index 0f55f35adc4e23fb61ba94ad4251745129d27f7d..eadcd7c27a18c5e57972b89afafca0c87fa0ed87 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig +++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig @@ -1,15 +1,17 @@ package: "com.android.systemui.accessibility.accessibilitymenu" -flag { - name: "a11y_menu_settings_back_button_fix_and_large_button_sizing" - namespace: "accessibility" - description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons." - bug: "298467628" -} +# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. flag { name: "a11y_menu_hide_before_taking_action" namespace: "accessibility" description: "Hides the AccessibilityMenuService UI before taking action instead of after." bug: "292020123" -} \ No newline at end of file +} + +flag { + name: "a11y_menu_settings_back_button_fix_and_large_button_sizing" + namespace: "accessibility" + description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons." + bug: "298467628" +} diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 8841967b153510132d85452d9f074936a6bd1f89..bcf1535b94fa389f0fef6c4745c3c08c49539734 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -1,5 +1,7 @@ package: "com.android.systemui" +# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. + flag { name: "floating_menu_overlaps_nav_bars_flag" namespace: "accessibility" diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 211af908a877b391c9ec869ec5b8184f77b50b76..05675289db642b372cf30fe76bae02315457a622 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -30,6 +30,13 @@ flag { bug: "299448097" } +flag { + name: "notification_async_hybrid_view_inflation" + namespace: "systemui" + description: "Inflates the hybrid (single-line) notification views form the background thread." + bug: "217799515" +} + flag { name: "scene_container" namespace: "systemui" 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 6c4b695ed7094b0e8d1948570f2ce5a2ae447884..af35ea44322fda3e754744d8fc0e20efce6a8db6 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -68,6 +68,7 @@ constructor( override var launchContainer = ghostedView.rootView as ViewGroup private val launchContainerOverlay: ViewGroupOverlay get() = launchContainer.overlay + private val launchContainerLocation = IntArray(2) /** The ghost view that is drawn and animated instead of the ghosted view. */ @@ -206,9 +207,8 @@ constructor( return } - backgroundView = FrameLayout(launchContainer.context).also { - launchContainerOverlay.add(it) - } + backgroundView = + FrameLayout(launchContainer.context).also { launchContainerOverlay.add(it) } // We wrap the ghosted view background and use it to draw the expandable background. Its // alpha will be set to 0 as soon as we start drawing the expanding background. @@ -226,6 +226,17 @@ constructor( // the content before fading out the background. ghostView = GhostView.addGhost(ghostedView, launchContainer) + // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and + // adds it first to a [FrameLayout] container. It then adds _that_ container to an + // [OverlayViewGroup]. We need to turn off clipping for that container view. Currently, + // however, the only way to get a reference to that overlay is by going through our + // [ghostView]. The [OverlayViewGroup] will always be its grandparent view. + // TODO(b/306652954) reference the overlay view group directly if we can + (ghostView?.parent?.parent as? ViewGroup)?.let { + it.clipChildren = false + it.clipToPadding = false + } + val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX matrix.getValues(initialGhostViewMatrixValues) diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt index 4ed78b3b95ed930e13ca91ae1c68e60d9b305211..33024f76471056f984b3b607af1c5279ca431d0d 100644 --- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.layout.ui.compose +import android.util.SizeF import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -54,7 +55,14 @@ fun CommunalGridLayout( Row( modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)), ) { - cardInfo.card.Content(Modifier.fillMaxSize()) + cardInfo.card.Content( + modifier = Modifier.fillMaxSize(), + size = + SizeF( + layoutConfig.cardWidth.value, + layoutConfig.cardHeight(cardInfo.size).value, + ), + ) } } } diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt index ac8aa67fa4bf7da8bc03733e9e69df16ecd125f7..4b2a156c1dbd692e5be90d8c109a56b62e4ea55f 100644 --- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.layout.ui.compose.config +import android.util.SizeF import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -26,8 +27,11 @@ abstract class CommunalGridLayoutCard { * * To host non-Compose views, see * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose. + * + * @param size The size given to the card. Content of the card should fill all this space, given + * that margins and paddings have been taken care of by the layout. */ - @Composable abstract fun Content(modifier: Modifier) + @Composable abstract fun Content(modifier: Modifier, size: SizeF) /** * Sizes supported by the card. diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt index fdf65f5d5cc7a71956cbfb7c54633702a8e5fef7..c1974caa5628d4faf1aa8bf36f010763ab3d1290 100644 --- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.communal.layout +import android.util.SizeF import androidx.compose.material3.Card import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -91,7 +92,7 @@ class CommunalLayoutEngineTest { override val supportedSizes = listOf(size) @Composable - override fun Content(modifier: Modifier) { + override fun Content(modifier: Modifier, size: SizeF) { Card(modifier = modifier, content = {}) } } diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index c6e429a79e8642321dd9e125a2cd450d4e91a3d2..ddd1c67bd5faaa4c255abe31de47ca905999b4ec 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -72,6 +72,10 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View { + throwComposeUnavailableError() + } + private fun throwComposeUnavailableError(): Nothing { error( "Compose is not available. Make sure to check isComposeAvailable() before calling any" + diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index 1722685f42877d217de8d48dd1a6cadceda70c3f..eeda6c63b68fd3ba04638675fb22db37951f9304 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -30,6 +30,7 @@ import com.android.compose.theme.PlatformTheme import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider +import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.people.ui.compose.PeopleScreen @@ -104,6 +105,12 @@ object ComposeFacade : BaseComposeFacade { } } + override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View { + return ComposeView(context).apply { + setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } } + } + } + // TODO(b/298525212): remove once Compose exposes window inset bounds. private fun displayCutoutFromWindowInsets( scope: CoroutineScope, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt new file mode 100644 index 0000000000000000000000000000000000000000..46d418a03c00a9defcae0ffb612bf690abe6d042 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -0,0 +1,123 @@ +package com.android.systemui.communal.ui.compose + +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.transitions +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel + +object Scenes { + val Blank = SceneKey(name = "blank") + val Communal = SceneKey(name = "communal") +} + +object Communal { + object Elements { + val Content = ElementKey("CommunalContent") + } +} + +val sceneTransitions = transitions { + from(Scenes.Blank, to = Scenes.Communal) { + spec = tween(durationMillis = 500) + + translate(Communal.Elements.Content, Edge.Right) + fade(Communal.Elements.Content) + } +} + +/** + * View containing a [SceneTransitionLayout] that shows the communal UI and handles transitions. + * + * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture + * handling and transitions before the full Flexiglass layout is ready. + */ +@Composable +fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) { + val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) } + + // Failsafe to hide the whole SceneTransitionLayout in case of bugginess. + var showSceneTransitionLayout by remember { mutableStateOf(true) } + if (!showSceneTransitionLayout) { + return + } + + SceneTransitionLayout( + modifier = modifier.fillMaxSize(), + currentScene = currentScene, + onChangeScene = setCurrentScene, + transitions = sceneTransitions, + ) { + scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) { + BlankScene { showSceneTransitionLayout = false } + } + + scene( + Scenes.Communal, + userActions = mapOf(Swipe.Right to Scenes.Blank), + ) { + CommunalScene(viewModel, modifier = modifier) + } + } +} + +/** + * Blank scene that shows over keyguard/dream. This scene will eventually show nothing at all and is + * only used to allow for transitions to the communal scene. + */ +@Composable +private fun BlankScene( + modifier: Modifier = Modifier, + hideSceneTransitionLayout: () -> Unit, +) { + Box(modifier.fillMaxSize()) { + Column( + Modifier.fillMaxHeight() + .width(100.dp) + .align(Alignment.CenterEnd) + .background(Color(0x55e9f2eb)), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Default scene") + + IconButton(onClick = hideSceneTransitionLayout) { + Icon(Icons.Filled.Close, contentDescription = "Close button") + } + } + } +} + +/** Scene containing the glanceable hub UI. */ +@Composable +private fun SceneScope.CommunalScene( + viewModel: CommunalViewModel, + modifier: Modifier = Modifier, +) { + Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 3d827fb5c9a66c4ebe8fa89deb07d5ba3b22ca29..b8fb264068012822a03a02ada40f4e5f73fe9014 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -1,5 +1,8 @@ package com.android.systemui.communal.ui.compose +import android.appwidget.AppWidgetHostView +import android.os.Bundle +import android.util.SizeF import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -12,9 +15,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.integerResource +import androidx.compose.ui.viewinterop.AndroidView import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig +import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.ui.model.CommunalContentUiModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.res.R @@ -24,6 +30,7 @@ fun CommunalHub( viewModel: CommunalViewModel, ) { val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false) + val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList()) Box( modifier = modifier.fillMaxSize().background(Color.White), ) { @@ -36,7 +43,7 @@ fun CommunalHub( gridHeight = dimensionResource(R.dimen.communal_grid_height), gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card), ), - communalCards = if (showTutorial) tutorialContent else emptyList(), + communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard), ) } } @@ -58,8 +65,37 @@ private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutC override val supportedSizes = listOf(size) @Composable - override fun Content(modifier: Modifier) { + override fun Content(modifier: Modifier, size: SizeF) { Card(modifier = modifier, content = {}) } } } + +private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard { + return object : CommunalGridLayoutCard() { + override val supportedSizes = listOf(convertToCardSize(model.size)) + override val priority = model.priority + + @Composable + override fun Content(modifier: Modifier, size: SizeF) { + AndroidView( + modifier = modifier, + factory = { + model.view.apply { + if (this is AppWidgetHostView) { + updateAppWidgetSize(Bundle(), listOf(size)) + } + } + }, + ) + } + } +} + +private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size { + return when (size) { + CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL + CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF + CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD + } +} diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 5a70b79cb1765f6e5471755a9839b2784b333eb5..452bc317e2d5acb1f3d0df2da25994462ef737b4 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -66,7 +66,7 @@ <FrameLayout android:id="@+id/status_bar_start_side_content" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_gravity="center_vertical|start" android:clipChildren="false"> @@ -77,7 +77,7 @@ and DISABLE_NOTIFICATION_ICONS, respectively --> <LinearLayout android:id="@+id/status_bar_start_side_except_heads_up" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_width="match_parent" android:layout_gravity="center_vertical|start" android:clipChildren="false"> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index acee4258b64d8405c25aca0d7c5878f0b77d569d..7e03bd9d432597399d827200d31174ed3ddd4624 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -26,24 +26,6 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> - <com.android.systemui.statusbar.BackDropView - android:id="@+id/backdrop" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" - sysui:ignoreRightInset="true" - > - <ImageView android:id="@+id/backdrop_back" - android:layout_width="match_parent" - android:scaleType="centerCrop" - android:layout_height="match_parent" /> - <ImageView android:id="@+id/backdrop_front" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop" - android:visibility="invisible" /> - </com.android.systemui.statusbar.BackDropView> - <com.android.systemui.scrim.ScrimView android:id="@+id/scrim_behind" android:layout_width="match_parent" @@ -63,7 +45,8 @@ <com.android.systemui.statusbar.LightRevealScrim android:id="@+id/light_reveal_scrim" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="match_parent" + sysui:ignoreRightInset="true" /> <include layout="@layout/status_bar_expanded" android:layout_width="match_parent" @@ -83,6 +66,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. --> + <ViewStub + android:id="@+id/communal_ui_stub" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + <include layout="@layout/brightness_mirror_container" /> <com.android.systemui.scrim.ScrimView diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index 587caaf3ecf3e301c0c81c03709a6b642c756dba..db526b187d38a0bf35c336c33b991751d23d921a 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -46,4 +46,7 @@ For now, this value has effect only when flag lockscreen.enable_landscape is enabled. TODO (b/293252410) - change this comment/resource when flag is enabled --> <bool name="force_config_use_split_notification_shade">true</bool> + + <!-- Whether to show bottom sheets edge to edge --> + <bool name="config_edgeToEdgeBottomSheetDialog">false</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 7a6d29ab3c1f58cad67f2347eb8926c928a57f20..9e2ebf60fe8f918b7f671d65926a173cc3c40e09 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3224,6 +3224,9 @@ <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]--> <string name="dismiss_dialog">Dismiss</string> + <!--- Content description of the connected display status bar icon that appears every time a display is connected [CHAR LIMIT=NONE]--> + <string name="connected_display_icon_desc">Display connected</string> + <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] --> <string name="privacy_dialog_title">Microphone & Camera</string> <!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt index db46ccf6a827fdef17ef602289352ef4bf698abf..80f70a0cd2f2eaaf12466a33089be83d7949136d 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt @@ -33,6 +33,10 @@ data class BiometricModalities( val hasFingerprint: Boolean get() = fingerprintProperties != null + /** If SFPS authentication is available. */ + val hasSfps: Boolean + get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType + /** If fingerprint authentication is available (and [faceProperties] is non-null). */ val hasFace: Boolean get() = faceProperties != null diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..f219cece3ff8e8f434c7e6b9d627472e3523e95f --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt @@ -0,0 +1,50 @@ +/* + * 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.unfold.system + +import com.android.systemui.unfold.dagger.UnfoldMain +import com.android.systemui.unfold.updates.FoldProvider +import com.android.systemui.unfold.updates.FoldProvider.FoldCallback +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.callbackFlow + +/** Provides whether the device is folded. */ +interface DeviceStateRepository { + val isFolded: Flow<Boolean> +} + +@Singleton +class DeviceStateRepositoryImpl +@Inject +constructor( + private val foldProvider: FoldProvider, + @UnfoldMain private val executor: Executor, +) : DeviceStateRepository { + + override val isFolded: Flow<Boolean> + get() = + callbackFlow { + val callback = FoldCallback { isFolded -> trySend(isFolded) } + foldProvider.registerCallback(callback, executor) + awaitClose { foldProvider.unregisterCallback(callback) } + } + .buffer(capacity = Channel.CONFLATED) +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt index fe607e16661cb9396731b9b5630d7e94008bb7bc..7b67e3f3c92099db0e8db3e34d3312313a5f47aa 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt @@ -47,6 +47,9 @@ abstract class SystemUnfoldSharedModule { @Binds abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider + @Binds + abstract fun deviceStateRepository(provider: DeviceStateRepositoryImpl): DeviceStateRepository + @Binds @UnfoldMain abstract fun mainExecutor(@Main executor: Executor): Executor diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt new file mode 100644 index 0000000000000000000000000000000000000000..63ea1165ee04de7818cd6a5e45c958776da6fd50 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt @@ -0,0 +1,54 @@ +/* + * 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.util + +import android.os.Trace + +/** + * Utility class used to log state changes easily in a track with a custom name. + * + * Example of usage: + * ```kotlin + * class MyClass { + * val screenStateLogger = TraceStateLogger("Screen state") + * + * fun onTurnedOn() { screenStateLogger.log("on") } + * fun onTurnedOff() { screenStateLogger.log("off") } + * } + * ``` + * + * This creates a new slice in a perfetto trace only if the state is different than the previous + * one. + */ +class TraceStateLogger( + private val trackName: String, + private val logOnlyIfDifferent: Boolean = true, + private val instantEvent: Boolean = true +) { + + private var previousValue: String? = null + + /** If needed, logs the value to a track with name [trackName]. */ + fun log(newValue: String) { + if (instantEvent) { + Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, newValue) + } + if (logOnlyIfDifferent && previousValue == newValue) return + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, 0) + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, newValue, 0) + previousValue = newValue + } +} diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java index fd84543ee50bec87c56b6560e529992bd86c51a4..494efb7d3f87faeda737fe81d2fad7caefe950d9 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java @@ -25,21 +25,24 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.os.UserHandle; import com.android.internal.logging.UiEventLogger; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.UserSwitcherController; -import javax.inject.Inject; - +import dagger.Lazy; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import javax.inject.Inject; + /** * Manages handling of guest session persistent notification * and actions to reset guest or exit guest session @@ -70,14 +73,14 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { public AlertDialog mResetSessionDialog; private final UserTracker mUserTracker; private final BroadcastDispatcher mBroadcastDispatcher; - private final ResetSessionDialog.Factory mResetSessionDialogFactory; - private final ExitSessionDialog.Factory mExitSessionDialogFactory; + private final ResetSessionDialogFactory mResetSessionDialogFactory; + private final ExitSessionDialogFactory mExitSessionDialogFactory; @Inject public GuestResetOrExitSessionReceiver(UserTracker userTracker, BroadcastDispatcher broadcastDispatcher, - ResetSessionDialog.Factory resetSessionDialogFactory, - ExitSessionDialog.Factory exitSessionDialogFactory) { + ResetSessionDialogFactory resetSessionDialogFactory, + ExitSessionDialogFactory exitSessionDialogFactory) { mUserTracker = userTracker; mBroadcastDispatcher = broadcastDispatcher; mResetSessionDialogFactory = resetSessionDialogFactory; @@ -111,8 +114,8 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { mResetSessionDialog = mResetSessionDialogFactory.create(currentUser.id); mResetSessionDialog.show(); } else if (ACTION_GUEST_EXIT.equals(action)) { - mExitSessionDialog = mExitSessionDialogFactory.create(currentUser.id, - currentUser.isEphemeral()); + mExitSessionDialog = mExitSessionDialogFactory.create( + currentUser.isEphemeral(), currentUser.id); mExitSessionDialog.show(); } } @@ -132,43 +135,69 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { } /** + * Factory class to create guest reset dialog instance + * * Dialog shown when asking for confirmation before * reset and restart of guest user. */ - public static final class ResetSessionDialog extends SystemUIDialog implements - DialogInterface.OnClickListener { + public static final class ResetSessionDialogFactory { + private final Lazy<SystemUIDialog> mDialogLazy; + private final Resources mResources; + private final ResetSessionDialogClickListener.Factory mClickListenerFactory; + + @Inject + public ResetSessionDialogFactory( + Lazy<SystemUIDialog> dialogLazy, + @Main Resources resources, + ResetSessionDialogClickListener.Factory clickListenerFactory) { + mDialogLazy = dialogLazy; + mResources = resources; + mClickListenerFactory = clickListenerFactory; + } + + /** Create a guest reset dialog instance */ + public AlertDialog create(int userId) { + SystemUIDialog dialog = mDialogLazy.get(); + ResetSessionDialogClickListener listener = mClickListenerFactory.create( + userId, dialog); + dialog.setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title); + dialog.setMessage(mResources.getString( + com.android.settingslib.R.string.guest_reset_and_restart_dialog_message)); + dialog.setButton( + DialogInterface.BUTTON_NEUTRAL, + mResources.getString(android.R.string.cancel), + listener); + dialog.setButton(DialogInterface.BUTTON_POSITIVE, + mResources.getString( + com.android.settingslib.R.string.guest_reset_guest_confirm_button), + listener); + dialog.setCanceledOnTouchOutside(false); + return dialog; + } + } + public static class ResetSessionDialogClickListener implements DialogInterface.OnClickListener { private final UserSwitcherController mUserSwitcherController; private final UiEventLogger mUiEventLogger; private final int mUserId; + private final DialogInterface mDialog; - /** Factory class to create guest reset dialog instance */ @AssistedFactory public interface Factory { - /** Create a guest reset dialog instance */ - ResetSessionDialog create(int userId); + ResetSessionDialogClickListener create(int userId, DialogInterface dialog); } @AssistedInject - ResetSessionDialog(Context context, + public ResetSessionDialogClickListener( UserSwitcherController userSwitcherController, UiEventLogger uiEventLogger, - @Assisted int userId) { - super(context); - - setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title); - setMessage(context.getString( - com.android.settingslib.R.string.guest_reset_and_restart_dialog_message)); - setButton(DialogInterface.BUTTON_NEUTRAL, - context.getString(android.R.string.cancel), this); - setButton(DialogInterface.BUTTON_POSITIVE, - context.getString( - com.android.settingslib.R.string.guest_reset_guest_confirm_button), this); - setCanceledOnTouchOutside(false); - + @Assisted int userId, + @Assisted DialogInterface dialog + ) { mUserSwitcherController = userSwitcherController; mUiEventLogger = uiEventLogger; mUserId = userId; + mDialog = dialog; } @Override @@ -177,7 +206,7 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE); mUserSwitcherController.removeGuestUser(mUserId, UserHandle.USER_NULL); } else if (which == DialogInterface.BUTTON_NEUTRAL) { - cancel(); + mDialog.cancel(); } } } @@ -186,58 +215,93 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { * Dialog shown when asking for confirmation before * exit of guest user. */ - public static final class ExitSessionDialog extends SystemUIDialog implements - DialogInterface.OnClickListener { + public static final class ExitSessionDialogFactory { + private final Lazy<SystemUIDialog> mDialogLazy; + private final ExitSessionDialogClickListener.Factory mClickListenerFactory; + private final Resources mResources; + + @Inject + public ExitSessionDialogFactory( + Lazy<SystemUIDialog> dialogLazy, + ExitSessionDialogClickListener.Factory clickListenerFactory, + @Main Resources resources) { + mDialogLazy = dialogLazy; + mClickListenerFactory = clickListenerFactory; + mResources = resources; + } + public AlertDialog create(boolean isEphemeral, int userId) { + SystemUIDialog dialog = mDialogLazy.get(); + ExitSessionDialogClickListener clickListener = mClickListenerFactory.create( + isEphemeral, userId, dialog); + if (isEphemeral) { + dialog.setTitle(mResources.getString( + com.android.settingslib.R.string.guest_exit_dialog_title)); + dialog.setMessage(mResources.getString( + com.android.settingslib.R.string.guest_exit_dialog_message)); + dialog.setButton( + DialogInterface.BUTTON_NEUTRAL, + mResources.getString(android.R.string.cancel), + clickListener); + dialog.setButton( + DialogInterface.BUTTON_POSITIVE, + mResources.getString( + com.android.settingslib.R.string.guest_exit_dialog_button), + clickListener); + } else { + dialog.setTitle(mResources.getString( + com.android.settingslib + .R.string.guest_exit_dialog_title_non_ephemeral)); + dialog.setMessage(mResources.getString( + com.android.settingslib + .R.string.guest_exit_dialog_message_non_ephemeral)); + dialog.setButton( + DialogInterface.BUTTON_NEUTRAL, + mResources.getString(android.R.string.cancel), + clickListener); + dialog.setButton( + DialogInterface.BUTTON_NEGATIVE, + mResources.getString( + com.android.settingslib.R.string.guest_exit_clear_data_button), + clickListener); + dialog.setButton( + DialogInterface.BUTTON_POSITIVE, + mResources.getString( + com.android.settingslib.R.string.guest_exit_save_data_button), + clickListener); + } + dialog.setCanceledOnTouchOutside(false); + + return dialog; + } + + } + + public static class ExitSessionDialogClickListener implements DialogInterface.OnClickListener { private final UserSwitcherController mUserSwitcherController; + private final boolean mIsEphemeral; private final int mUserId; - private boolean mIsEphemeral; + private final DialogInterface mDialog; - /** Factory class to create guest exit dialog instance */ @AssistedFactory public interface Factory { - /** Create a guest exit dialog instance */ - ExitSessionDialog create(int userId, boolean isEphemeral); + ExitSessionDialogClickListener create( + boolean isEphemeral, + int userId, + DialogInterface dialog); } @AssistedInject - ExitSessionDialog(Context context, + public ExitSessionDialogClickListener( UserSwitcherController userSwitcherController, + @Assisted boolean isEphemeral, @Assisted int userId, - @Assisted boolean isEphemeral) { - super(context); - - if (isEphemeral) { - setTitle(context.getString( - com.android.settingslib.R.string.guest_exit_dialog_title)); - setMessage(context.getString( - com.android.settingslib.R.string.guest_exit_dialog_message)); - setButton(DialogInterface.BUTTON_NEUTRAL, - context.getString(android.R.string.cancel), this); - setButton(DialogInterface.BUTTON_POSITIVE, - context.getString( - com.android.settingslib.R.string.guest_exit_dialog_button), this); - } else { - setTitle(context.getString( - com.android.settingslib - .R.string.guest_exit_dialog_title_non_ephemeral)); - setMessage(context.getString( - com.android.settingslib - .R.string.guest_exit_dialog_message_non_ephemeral)); - setButton(DialogInterface.BUTTON_NEUTRAL, - context.getString(android.R.string.cancel), this); - setButton(DialogInterface.BUTTON_NEGATIVE, - context.getString( - com.android.settingslib.R.string.guest_exit_clear_data_button), this); - setButton(DialogInterface.BUTTON_POSITIVE, - context.getString( - com.android.settingslib.R.string.guest_exit_save_data_button), this); - } - setCanceledOnTouchOutside(false); - + @Assisted DialogInterface dialog + ) { mUserSwitcherController = userSwitcherController; - mUserId = userId; mIsEphemeral = isEphemeral; + mUserId = userId; + mDialog = dialog; } @Override @@ -249,7 +313,7 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false); } else if (which == DialogInterface.BUTTON_NEUTRAL) { // Cancel clicked, do nothing - cancel(); + mDialog.cancel(); } } else { if (which == DialogInterface.BUTTON_POSITIVE) { @@ -261,7 +325,7 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver { mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, true); } else if (which == DialogInterface.BUTTON_NEUTRAL) { // Cancel clicked, do nothing - cancel(); + mDialog.cancel(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java index b573fadea15e89240385e001cc5e2fa002f401d9..0f5f869cba5d741af0495252c4133eecf971f2e8 100644 --- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java @@ -27,6 +27,7 @@ import androidx.annotation.NonNull; import com.android.systemui.res.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -36,14 +37,14 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.settings.SecureSettings; -import java.util.concurrent.Executor; - -import javax.inject.Inject; - import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + /** * Manages notification when a guest session is resumed. */ @@ -58,7 +59,7 @@ public class GuestResumeSessionReceiver { private final Executor mMainExecutor; private final UserTracker mUserTracker; private final SecureSettings mSecureSettings; - private final ResetSessionDialog.Factory mResetSessionDialogFactory; + private final ResetSessionDialogFactory mResetSessionDialogFactory; private final GuestSessionNotification mGuestSessionNotification; @VisibleForTesting @@ -104,7 +105,7 @@ public class GuestResumeSessionReceiver { UserTracker userTracker, SecureSettings secureSettings, GuestSessionNotification guestSessionNotification, - ResetSessionDialog.Factory resetSessionDialogFactory) { + ResetSessionDialogFactory resetSessionDialogFactory) { mMainExecutor = mainExecutor; mUserTracker = userTracker; mSecureSettings = secureSettings; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt deleted file mode 100644 index 3f2da5e144c57cdab04daf2086cc621534789aa1..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.biometrics - -import android.content.Context -import android.graphics.drawable.Drawable -import android.util.Log -import com.airbnb.lottie.LottieAnimationView -import com.android.systemui.res.R -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState - -private const val TAG = "AuthBiometricFaceIconController" - -/** Face only icon animator for BiometricPrompt. */ -class AuthBiometricFaceIconController( - context: Context, - iconView: LottieAnimationView -) : AuthIconController(context, iconView) { - - // false = dark to light, true = light to dark - private var lastPulseLightToDark = false - - private var state: BiometricState = BiometricState.STATE_IDLE - - init { - val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) - iconView.layoutParams.width = size - iconView.layoutParams.height = size - showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light) - } - - private fun startPulsing() { - lastPulseLightToDark = false - animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true) - } - - private fun pulseInNextDirection() { - val iconRes = if (lastPulseLightToDark) { - R.drawable.face_dialog_pulse_dark_to_light - } else { - R.drawable.face_dialog_pulse_light_to_dark - } - animateIcon(iconRes, true /* repeat */) - lastPulseLightToDark = !lastPulseLightToDark - } - - override fun handleAnimationEnd(drawable: Drawable) { - if (state == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) { - pulseInNextDirection() - } - } - - override fun updateIcon(oldState: BiometricState, newState: BiometricState) { - val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP) - if (newState == BiometricState.STATE_AUTHENTICATING_ANIMATING_IN) { - showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light) - iconView.contentDescription = context.getString( - R.string.biometric_dialog_face_icon_description_authenticating - ) - } else if (newState == BiometricState.STATE_AUTHENTICATING) { - startPulsing() - iconView.contentDescription = context.getString( - R.string.biometric_dialog_face_icon_description_authenticating - ) - } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.STATE_AUTHENTICATED) { - animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) - iconView.contentDescription = context.getString( - R.string.biometric_dialog_face_icon_description_confirmed - ) - } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_IDLE) { - animateIconOnce(R.drawable.face_dialog_error_to_idle) - iconView.contentDescription = context.getString( - R.string.biometric_dialog_face_icon_description_idle - ) - } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_AUTHENTICATED) { - animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) - iconView.contentDescription = context.getString( - R.string.biometric_dialog_face_icon_description_authenticated - ) - } else if (newState == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) { - animateIconOnce(R.drawable.face_dialog_dark_to_error) - iconView.contentDescription = context.getString( - R.string.keyguard_face_failed - ) - } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.STATE_AUTHENTICATED) { - animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) - iconView.contentDescription = context.getString( - R.string.biometric_dialog_face_icon_description_authenticated - ) - } else if (newState == BiometricState.STATE_PENDING_CONFIRMATION) { - animateIconOnce(R.drawable.face_dialog_wink_from_dark) - iconView.contentDescription = context.getString( - R.string.biometric_dialog_face_icon_description_authenticated - ) - } else if (newState == BiometricState.STATE_IDLE) { - showStaticDrawable(R.drawable.face_dialog_idle_static) - iconView.contentDescription = context.getString( - R.string.biometric_dialog_face_icon_description_idle - ) - } else { - Log.w(TAG, "Unhandled state: $newState") - } - state = newState - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt deleted file mode 100644 index 09eabf2aa430e0fcc61ef3f86191646a3e535480..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.annotation.RawRes -import android.content.Context -import com.airbnb.lottie.LottieAnimationView -import com.android.systemui.res.R -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION - -/** Face/Fingerprint combined icon animator for BiometricPrompt. */ -open class AuthBiometricFingerprintAndFaceIconController( - context: Context, - iconView: LottieAnimationView, - iconViewOverlay: LottieAnimationView, -) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) { - - override val actsAsConfirmButton: Boolean = true - - override fun shouldAnimateIconViewForTransition( - oldState: BiometricState, - newState: BiometricState - ): Boolean = when (newState) { - STATE_PENDING_CONFIRMATION -> true - else -> super.shouldAnimateIconViewForTransition(oldState, newState) - } - - @RawRes - override fun getAnimationForTransition( - oldState: BiometricState, - newState: BiometricState - ): Int? = when (newState) { - STATE_AUTHENTICATED -> { - if (oldState == STATE_PENDING_CONFIRMATION) { - R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie - } else { - super.getAnimationForTransition(oldState, newState) - } - } - STATE_PENDING_CONFIRMATION -> { - if (oldState == STATE_ERROR || oldState == STATE_HELP) { - R.raw.fingerprint_dialogue_error_to_unlock_lottie - } else { - R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie - } - } - else -> super.getAnimationForTransition(oldState, newState) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt deleted file mode 100644 index 0ad3848299f939ac4b031c00001958d3a4dd23a3..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.annotation.RawRes -import android.content.Context -import android.content.Context.FINGERPRINT_SERVICE -import android.hardware.fingerprint.FingerprintManager -import android.view.DisplayInfo -import android.view.Surface -import android.view.View -import androidx.annotation.VisibleForTesting -import com.airbnb.lottie.LottieAnimationView -import com.android.settingslib.widget.LottieColorUtils -import com.android.systemui.res.R -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION - - -/** Fingerprint only icon animator for BiometricPrompt. */ -open class AuthBiometricFingerprintIconController( - context: Context, - iconView: LottieAnimationView, - protected val iconViewOverlay: LottieAnimationView -) : AuthIconController(context, iconView) { - - private val isSideFps: Boolean - private val isReverseDefaultRotation = - context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation) - - var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1) - set(value) { - if (field == value) { - return - } - iconViewOverlay.layoutParams.width = value.first - iconViewOverlay.layoutParams.height = value.second - iconView.layoutParams.width = value.first - iconView.layoutParams.height = value.second - field = value - } - - init { - iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize( - R.dimen.biometric_dialog_fingerprint_icon_width), - context.resources.getDimensionPixelSize( - R.dimen.biometric_dialog_fingerprint_icon_height)) - isSideFps = - (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm -> - fpm.sensorPropertiesInternal.any { it.isAnySidefpsType } - } ?: false - preloadAssets(context) - val displayInfo = DisplayInfo() - context.display?.getDisplayInfo(displayInfo) - if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) { - iconView.rotation = 180f - } - } - - private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) { - val displayInfo = DisplayInfo() - context.display?.getDisplayInfo(displayInfo) - val rotation = getRotationFromDefault(displayInfo.rotation) - val iconViewOverlayAnimation = - getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return - - if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) { - iconViewOverlay.setAnimation(iconViewOverlayAnimation) - } - - val iconContentDescription = getIconContentDescription(newState) - if (iconContentDescription != null) { - iconView.contentDescription = iconContentDescription - } - - iconView.frame = 0 - iconViewOverlay.frame = 0 - if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) { - iconView.playAnimation() - } - - if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) { - iconViewOverlay.playAnimation() - } - - LottieColorUtils.applyDynamicColors(context, iconView) - LottieColorUtils.applyDynamicColors(context, iconViewOverlay) - } - - private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) { - val icon = getAnimationForTransition(lastState, newState) ?: return - - if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) { - iconView.setAnimation(icon) - } - - val iconContentDescription = getIconContentDescription(newState) - if (iconContentDescription != null) { - iconView.contentDescription = iconContentDescription - } - - iconView.frame = 0 - if (shouldAnimateIconViewForTransition(lastState, newState)) { - iconView.playAnimation() - } - LottieColorUtils.applyDynamicColors(context, iconView) - } - - override fun updateIcon(lastState: BiometricState, newState: BiometricState) { - if (isSideFps) { - updateIconSideFps(lastState, newState) - } else { - iconViewOverlay.visibility = View.GONE - updateIconNormal(lastState, newState) - } - } - - @VisibleForTesting - fun getIconContentDescription(newState: BiometricState): CharSequence? { - val id = when (newState) { - STATE_IDLE, - STATE_AUTHENTICATING_ANIMATING_IN, - STATE_AUTHENTICATING, - STATE_AUTHENTICATED -> - if (isSideFps) { - R.string.security_settings_sfps_enroll_find_sensor_message - } else { - R.string.fingerprint_dialog_touch_sensor - } - STATE_PENDING_CONFIRMATION -> - if (isSideFps) { - R.string.security_settings_sfps_enroll_find_sensor_message - } else { - R.string.fingerprint_dialog_authenticated_confirmation - } - STATE_ERROR, - STATE_HELP -> R.string.biometric_dialog_try_again - else -> null - } - return if (id != null) context.getString(id) else null - } - - protected open fun shouldAnimateIconViewForTransition( - oldState: BiometricState, - newState: BiometricState - ) = when (newState) { - STATE_HELP, - STATE_ERROR -> true - STATE_AUTHENTICATING_ANIMATING_IN, - STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP - STATE_AUTHENTICATED -> true - else -> false - } - - private fun shouldAnimateSfpsIconViewForTransition( - oldState: BiometricState, - newState: BiometricState - ) = when (newState) { - STATE_HELP, - STATE_ERROR -> true - STATE_AUTHENTICATING_ANIMATING_IN, - STATE_AUTHENTICATING -> - oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE - STATE_AUTHENTICATED -> true - else -> false - } - - protected open fun shouldAnimateIconViewOverlayForTransition( - oldState: BiometricState, - newState: BiometricState - ) = when (newState) { - STATE_HELP, - STATE_ERROR -> true - STATE_AUTHENTICATING_ANIMATING_IN, - STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP - STATE_AUTHENTICATED -> true - else -> false - } - - @RawRes - protected open fun getAnimationForTransition( - oldState: BiometricState, - newState: BiometricState - ): Int? { - val id = when (newState) { - STATE_HELP, - STATE_ERROR -> { - R.raw.fingerprint_dialogue_fingerprint_to_error_lottie - } - STATE_AUTHENTICATING_ANIMATING_IN, - STATE_AUTHENTICATING -> { - if (oldState == STATE_ERROR || oldState == STATE_HELP) { - R.raw.fingerprint_dialogue_error_to_fingerprint_lottie - } else { - R.raw.fingerprint_dialogue_fingerprint_to_error_lottie - } - } - STATE_AUTHENTICATED -> { - if (oldState == STATE_ERROR || oldState == STATE_HELP) { - R.raw.fingerprint_dialogue_error_to_success_lottie - } else { - R.raw.fingerprint_dialogue_fingerprint_to_success_lottie - } - } - else -> return null - } - return if (id != null) return id else null - } - - private fun getRotationFromDefault(rotation: Int): Int = - if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation - - @RawRes - private fun getSideFpsOverlayAnimationForTransition( - oldState: BiometricState, - newState: BiometricState, - rotation: Int - ): Int? = when (newState) { - STATE_HELP, - STATE_ERROR -> { - when (rotation) { - Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape - Surface.ROTATION_90 -> - R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft - Surface.ROTATION_180 -> - R.raw.biometricprompt_fingerprint_to_error_landscape - Surface.ROTATION_270 -> - R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright - else -> R.raw.biometricprompt_fingerprint_to_error_landscape - } - } - STATE_AUTHENTICATING_ANIMATING_IN, - STATE_AUTHENTICATING -> { - if (oldState == STATE_ERROR || oldState == STATE_HELP) { - when (rotation) { - Surface.ROTATION_0 -> - R.raw.biometricprompt_symbol_error_to_fingerprint_landscape - Surface.ROTATION_90 -> - R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft - Surface.ROTATION_180 -> - R.raw.biometricprompt_symbol_error_to_fingerprint_landscape - Surface.ROTATION_270 -> - R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright - else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape - } - } else { - when (rotation) { - Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape - Surface.ROTATION_90 -> - R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft - Surface.ROTATION_180 -> - R.raw.biometricprompt_fingerprint_to_error_landscape - Surface.ROTATION_270 -> - R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright - else -> R.raw.biometricprompt_fingerprint_to_error_landscape - } - } - } - STATE_AUTHENTICATED -> { - if (oldState == STATE_ERROR || oldState == STATE_HELP) { - when (rotation) { - Surface.ROTATION_0 -> - R.raw.biometricprompt_symbol_error_to_success_landscape - Surface.ROTATION_90 -> - R.raw.biometricprompt_symbol_error_to_success_portrait_topleft - Surface.ROTATION_180 -> - R.raw.biometricprompt_symbol_error_to_success_landscape - Surface.ROTATION_270 -> - R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright - else -> R.raw.biometricprompt_symbol_error_to_success_landscape - } - } else { - when (rotation) { - Surface.ROTATION_0 -> - R.raw.biometricprompt_symbol_fingerprint_to_success_landscape - Surface.ROTATION_90 -> - R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft - Surface.ROTATION_180 -> - R.raw.biometricprompt_symbol_fingerprint_to_success_landscape - Surface.ROTATION_270 -> - R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright - else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape - } - } - } - else -> null - } - - private fun preloadAssets(context: Context) { - if (isSideFps) { - cacheLottieAssetsInContext( - context, - R.raw.biometricprompt_fingerprint_to_error_landscape, - R.raw.biometricprompt_folded_base_bottomright, - R.raw.biometricprompt_folded_base_default, - R.raw.biometricprompt_folded_base_topleft, - R.raw.biometricprompt_landscape_base, - R.raw.biometricprompt_portrait_base_bottomright, - R.raw.biometricprompt_portrait_base_topleft, - R.raw.biometricprompt_symbol_error_to_fingerprint_landscape, - R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright, - R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft, - R.raw.biometricprompt_symbol_error_to_success_landscape, - R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright, - R.raw.biometricprompt_symbol_error_to_success_portrait_topleft, - R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright, - R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft, - R.raw.biometricprompt_symbol_fingerprint_to_success_landscape, - R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright, - R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft - ) - } else { - cacheLottieAssetsInContext( - context, - R.raw.fingerprint_dialogue_error_to_fingerprint_lottie, - R.raw.fingerprint_dialogue_error_to_success_lottie, - R.raw.fingerprint_dialogue_fingerprint_to_error_lottie, - R.raw.fingerprint_dialogue_fingerprint_to_success_lottie - ) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt index 054bd082edb7067ee17bce4aa4227429bab5fe3d..8d1d90588fc3f596757605f5f2917411eafcaf4f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt @@ -1,3 +1,19 @@ +/* + * 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.biometrics import android.annotation.MainThread @@ -25,7 +41,7 @@ constructor( shadeExpansionCollectorJob = scope.launch { // wait for it to emit true once - shadeInteractorLazy.get().isAnyExpanding.first { it } + shadeInteractorLazy.get().isUserInteracting.first { it } onShadeInteraction.run() } shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt deleted file mode 100644 index 958213afacdf209757d37d5f9bdf004f63c71d86..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.annotation.DrawableRes -import android.content.Context -import android.graphics.drawable.Animatable2 -import android.graphics.drawable.AnimatedVectorDrawable -import android.graphics.drawable.Drawable -import android.util.Log -import com.airbnb.lottie.LottieAnimationView -import com.airbnb.lottie.LottieCompositionFactory -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState - -private const val TAG = "AuthIconController" - -/** Controller for animating the BiometricPrompt icon/affordance. */ -abstract class AuthIconController( - protected val context: Context, - protected val iconView: LottieAnimationView -) : Animatable2.AnimationCallback() { - - /** If this controller should ignore events and pause. */ - var deactivated: Boolean = false - - /** If the icon view should be treated as an alternate "confirm" button. */ - open val actsAsConfirmButton: Boolean = false - - final override fun onAnimationStart(drawable: Drawable) { - super.onAnimationStart(drawable) - } - - final override fun onAnimationEnd(drawable: Drawable) { - super.onAnimationEnd(drawable) - - if (!deactivated) { - handleAnimationEnd(drawable) - } - } - - /** Set the icon to a static image. */ - protected fun showStaticDrawable(@DrawableRes iconRes: Int) { - iconView.setImageDrawable(context.getDrawable(iconRes)) - } - - /** Animate a resource. */ - protected fun animateIconOnce(@DrawableRes iconRes: Int) { - animateIcon(iconRes, false) - } - - /** Animate a resource. */ - protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) { - if (!deactivated) { - val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable - iconView.setImageDrawable(icon) - icon.forceAnimationOnUI() - if (repeat) { - icon.registerAnimationCallback(this) - } - icon.start() - } - } - - /** Update the icon to reflect the [newState]. */ - fun updateState(lastState: BiometricState, newState: BiometricState) { - if (deactivated) { - Log.w(TAG, "Ignoring updateState when deactivated: $newState") - } else { - updateIcon(lastState, newState) - } - } - - /** Call during [updateState] if the controller is not [deactivated]. */ - abstract fun updateIcon(lastState: BiometricState, newState: BiometricState) - - /** Called during [onAnimationEnd] if the controller is not [deactivated]. */ - open fun handleAnimationEnd(drawable: Drawable) {} - - // TODO(b/251476085): Migrate this to an extension at the appropriate level? - /** Load the given [rawResources] immediately so they are cached for use in the [context]. */ - protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) { - for (res in rawResources) { - LottieCompositionFactory.fromRawRes(context, res) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt index c4c52e8b358ec93826e4e4004988c2ef25885c5f..050b399fd3e80d5059fc0928040b495e612f9c2f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt @@ -42,8 +42,11 @@ import kotlinx.coroutines.flow.stateIn /** Repository for the current state of the display */ interface DisplayStateRepository { /** - * Whether or not the direction rotation is applied to get to an application's requested - * orientation is reversed. + * If true, the direction rotation is applied to get to an application's requested orientation + * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a + * portrait device an app requesting landscape will cause a clockwise rotation, and on a + * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting + * true here reverses that logic. See go/natural-orientation for context. */ val isReverseDefaultRotation: Boolean diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt index a317a068405514844fa0162e891c627e4b2d484a..427361d4b17a631e0146fde3f2a777dfa26906bc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt @@ -57,6 +57,15 @@ interface DisplayStateInteractor { /** Display change event indicating a change to the given displayId has occurred. */ val displayChanges: Flow<Int> + /** + * If true, the direction rotation is applied to get to an application's requested orientation + * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a + * portrait device an app requesting landscape will cause a clockwise rotation, and on a + * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting + * true here reverses that logic. See go/natural-orientation for context. + */ + val isReverseDefaultRotation: Boolean + /** Called on configuration changes, used to keep the display state in sync */ fun onConfigurationChanged(newConfig: Configuration) } @@ -112,6 +121,8 @@ constructor( override val currentRotation: StateFlow<DisplayRotation> = displayStateRepository.currentRotation + override val isReverseDefaultRotation: Boolean = displayStateRepository.isReverseDefaultRotation + override fun onConfigurationChanged(newConfig: Configuration) { screenSizeFoldProvider.onConfigurationChange(newConfig) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java index cef0be09d3ce1e377273fbdef23c1d21ce2c88b5..0d72b9c07d7a53f3b30894d29d85afb5ffd6f1de 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java @@ -29,11 +29,10 @@ import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; -import com.android.systemui.res.R; -import com.android.systemui.biometrics.AuthBiometricFingerprintIconController; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthDialog; import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter; +import com.android.systemui.res.R; import kotlin.Pair; @@ -85,13 +84,13 @@ public class BiometricPromptLayout extends LinearLayout { } @Deprecated - public void updateFingerprintAffordanceSize( - @NonNull AuthBiometricFingerprintIconController iconController) { + public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() { if (mUdfpsAdapter != null) { final int sensorDiameter = mUdfpsAdapter.getSensorDiameter( mScaleFactorProvider.provide()); - iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter)); + return new Pair(sensorDiameter, sensorDiameter); } + return null; } @NonNull diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index c29efc0fcab96efc82b95bb3f596985afff0e510..ac48b6a2b11e6d9151410c52b7c652b5dd6eacd6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -38,11 +38,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.airbnb.lottie.LottieAnimationView -import com.android.systemui.res.R -import com.android.systemui.biometrics.AuthBiometricFaceIconController -import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController -import com.android.systemui.biometrics.AuthBiometricFingerprintIconController -import com.android.systemui.biometrics.AuthIconController +import com.airbnb.lottie.LottieCompositionFactory import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality @@ -56,6 +52,7 @@ import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -101,10 +98,15 @@ object BiometricViewBinder { !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled descriptionView.movementMethod = ScrollingMovementMethod() - val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay) + val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay) val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon) - PromptFingerprintIconViewBinder.bind(iconView, viewModel.fingerprintIconViewModel) + PromptIconViewBinder.bind( + iconView, + iconOverlayView, + view.getUpdatedFingerprintAffordanceSize(), + viewModel.iconViewModel + ) val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator) @@ -128,9 +130,21 @@ object BiometricViewBinder { // bind to prompt var boundSize = false + view.repeatWhenAttached { // these do not change and need to be set before any size transitions val modalities = viewModel.modalities.first() + if (modalities.hasFingerprint) { + /** + * Load the given [rawResources] immediately so they are cached for use in the + * [context]. + */ + val rawResources = viewModel.iconViewModel.getRawAssets(modalities.hasSfps) + for (res in rawResources) { + LottieCompositionFactory.fromRawRes(view.context, res) + } + } + titleView.text = viewModel.title.first() descriptionView.text = viewModel.description.first() subtitleView.text = viewModel.subtitle.first() @@ -148,27 +162,8 @@ object BiometricViewBinder { legacyCallback.onButtonTryAgain() } - // TODO(b/251476085): migrate legacy icon controllers and remove - var legacyState = viewModel.legacyState.value - val iconController = - modalities.asIconController( - view.context, - iconView, - iconViewOverlay, - ) - adapter.attach(this, iconController, modalities, legacyCallback) - if (iconController is AuthBiometricFingerprintIconController) { - view.updateFingerprintAffordanceSize(iconController) - } - if (iconController is HackyCoexIconController) { - iconController.faceMode = !viewModel.isConfirmationRequired.first() - } + adapter.attach(this, modalities, legacyCallback) - // the icon controller must be created before this happens for the legacy - // sizing code in BiometricPromptLayout to work correctly. Simplify this - // when those are also migrated. (otherwise the icon size may not be set to - // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly - // used as part of the measure spec) if (!boundSize) { boundSize = true BiometricViewSizeBinder.bind( @@ -212,14 +207,6 @@ object BiometricViewBinder { ) { legacyCallback.onStartDelayedFingerprintSensor() } - - if (newMode.isStarted) { - // do wonky switch from implicit to explicit flow - (iconController as? HackyCoexIconController)?.faceMode = false - viewModel.showAuthenticating( - modalities.asDefaultHelpMessage(view.context), - ) - } } } @@ -312,7 +299,7 @@ object BiometricViewBinder { viewModel.isIconConfirmButton .map { isPending -> when { - isPending && iconController.actsAsConfirmButton -> + isPending && modalities.hasFaceAndFingerprint -> View.OnTouchListener { _: View, event: MotionEvent -> viewModel.onOverlayTouch(event) } @@ -320,22 +307,11 @@ object BiometricViewBinder { } } .collect { onTouch -> - iconViewOverlay.setOnTouchListener(onTouch) + iconOverlayView.setOnTouchListener(onTouch) iconView.setOnTouchListener(onTouch) } } - // TODO(b/251476085): remove w/ legacy icon controllers - // set icon affordance using legacy states - // like the old code, this causes animations to repeat on config changes :( - // but keep behavior for now as no one has complained... - launch { - viewModel.legacyState.collect { newState -> - iconController.updateState(legacyState, newState) - legacyState = newState - } - } - // dismiss prompt when authenticated and confirmed launch { viewModel.isAuthenticated.collect { authState -> @@ -350,7 +326,7 @@ object BiometricViewBinder { // Allow icon to be used as confirmation button with a11y enabled if (accessibilityManager.isTouchExplorationEnabled) { - iconViewOverlay.setOnClickListener { + iconOverlayView.setOnClickListener { viewModel.confirmAuthenticated() } iconView.setOnClickListener { viewModel.confirmAuthenticated() } @@ -377,7 +353,6 @@ object BiometricViewBinder { launch { viewModel.message.collect { promptMessage -> val isError = promptMessage is PromptMessage.Error - indicatorMessageView.text = promptMessage.message indicatorMessageView.setTextColor( if (isError) textColorError else textColorHint @@ -472,9 +447,6 @@ class Spaghetti( private var modalities: BiometricModalities = BiometricModalities() private var legacyCallback: Callback? = null - var legacyIconController: AuthIconController? = null - private set - // hacky way to suppress lockout errors private val lockoutErrorStrings = listOf( @@ -485,24 +457,20 @@ class Spaghetti( fun attach( lifecycleOwner: LifecycleOwner, - iconController: AuthIconController, activeModalities: BiometricModalities, callback: Callback, ) { modalities = activeModalities - legacyIconController = iconController legacyCallback = callback lifecycleOwner.lifecycle.addObserver( object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { lifecycleScope = owner.lifecycleScope - iconController.deactivated = false } override fun onDestroy(owner: LifecycleOwner) { lifecycleScope = null - iconController.deactivated = true } } ) @@ -626,61 +594,9 @@ private fun BiometricModalities.asDefaultHelpMessage(context: Context): String = else -> "" } -private fun BiometricModalities.asIconController( - context: Context, - iconView: LottieAnimationView, - iconViewOverlay: LottieAnimationView, -): AuthIconController = - when { - hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay) - hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) - hasFace -> AuthBiometricFaceIconController(context, iconView) - else -> throw IllegalStateException("unexpected view type :$this") - } - private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE // TODO(b/251476085): proper type? typealias BiometricJankListener = Animator.AnimatorListener - -// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced -private class HackyCoexIconController( - context: Context, - iconView: LottieAnimationView, - iconViewOverlay: LottieAnimationView, -) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) { - - private var state: Spaghetti.BiometricState? = null - private val faceController = AuthBiometricFaceIconController(context, iconView) - - var faceMode: Boolean = true - set(value) { - if (field != value) { - field = value - - faceController.deactivated = !value - iconView.setImageIcon(null) - iconViewOverlay.setImageIcon(null) - state?.let { updateIcon(Spaghetti.BiometricState.STATE_IDLE, it) } - } - } - - override fun updateIcon( - lastState: Spaghetti.BiometricState, - newState: Spaghetti.BiometricState, - ) { - if (deactivated) { - return - } - - if (faceMode) { - faceController.updateIcon(lastState, newState) - } else { - super.updateIcon(lastState, newState) - } - - state = newState - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt deleted file mode 100644 index d28f1dc78c1333d6cdb6957c27121f2f19a990e6..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt +++ /dev/null @@ -1,49 +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.biometrics.ui.binder - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.airbnb.lottie.LottieAnimationView -import com.android.systemui.biometrics.ui.viewmodel.PromptFingerprintIconViewModel -import com.android.systemui.lifecycle.repeatWhenAttached -import kotlinx.coroutines.launch - -/** Sub-binder for [BiometricPromptLayout.iconView]. */ -object PromptFingerprintIconViewBinder { - - /** Binds [BiometricPromptLayout.iconView] to [PromptFingerprintIconViewModel]. */ - @JvmStatic - fun bind(view: LottieAnimationView, viewModel: PromptFingerprintIconViewModel) { - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.onConfigurationChanged(view.context.resources.configuration) - launch { - viewModel.iconAsset.collect { iconAsset -> - if (iconAsset != -1) { - view.setAnimation(iconAsset) - // TODO: must replace call below once non-sfps asset logic and - // shouldAnimateIconView logic is migrated to this ViewModel. - view.playAnimation() - } - } - } - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt new file mode 100644 index 0000000000000000000000000000000000000000..475ef18e50993e2b048ae0355b5422b6d5a03b98 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt @@ -0,0 +1,200 @@ +/* + * 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.biometrics.ui.binder + +import android.graphics.drawable.Animatable2 +import android.graphics.drawable.AnimatedVectorDrawable +import android.graphics.drawable.Drawable +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.android.settingslib.widget.LottieColorUtils +import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel +import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.util.kotlin.Utils.Companion.toQuad +import com.android.systemui.util.kotlin.Utils.Companion.toQuint +import com.android.systemui.util.kotlin.Utils.Companion.toTriple +import com.android.systemui.util.kotlin.sample +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +/** Sub-binder for [BiometricPromptLayout.iconView]. */ +object PromptIconViewBinder { + /** + * Binds [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] to + * [PromptIconViewModel]. + */ + @JvmStatic + fun bind( + iconView: LottieAnimationView, + iconOverlayView: LottieAnimationView, + iconViewLayoutParamSizeOverride: Pair<Int, Int>?, + viewModel: PromptIconViewModel + ) { + iconView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.onConfigurationChanged(iconView.context.resources.configuration) + if (iconViewLayoutParamSizeOverride != null) { + iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first + iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second + + iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first + iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second + } + + var faceIcon: AnimatedVectorDrawable? = null + val faceIconCallback = + object : Animatable2.AnimationCallback() { + override fun onAnimationStart(drawable: Drawable) { + viewModel.onAnimationStart() + } + + override fun onAnimationEnd(drawable: Drawable) { + viewModel.onAnimationEnd() + } + } + + launch { + viewModel.activeAuthType.collect { activeAuthType -> + if (iconViewLayoutParamSizeOverride == null) { + val width: Int + val height: Int + when (activeAuthType) { + AuthType.Fingerprint, + AuthType.Coex -> { + width = viewModel.fingerprintIconWidth + height = viewModel.fingerprintIconHeight + } + AuthType.Face -> { + width = viewModel.faceIconWidth + height = viewModel.faceIconHeight + } + } + + iconView.layoutParams.width = width + iconView.layoutParams.height = height + + iconOverlayView.layoutParams.width = width + iconOverlayView.layoutParams.height = height + } + } + } + + launch { + viewModel.iconAsset + .sample( + combine( + viewModel.activeAuthType, + viewModel.shouldAnimateIconView, + viewModel.shouldRepeatAnimation, + viewModel.showingError, + ::toQuad + ), + ::toQuint + ) + .collect { + ( + iconAsset, + activeAuthType, + shouldAnimateIconView, + shouldRepeatAnimation, + showingError) -> + if (iconAsset != -1) { + when (activeAuthType) { + AuthType.Fingerprint, + AuthType.Coex -> { + iconView.setAnimation(iconAsset) + iconView.frame = 0 + + if (shouldAnimateIconView) { + iconView.playAnimation() + } + } + AuthType.Face -> { + faceIcon?.apply { + unregisterAnimationCallback(faceIconCallback) + stop() + } + faceIcon = + iconView.context.getDrawable(iconAsset) + as AnimatedVectorDrawable + faceIcon?.apply { + iconView.setImageDrawable(this) + if (shouldAnimateIconView) { + forceAnimationOnUI() + if (shouldRepeatAnimation) { + registerAnimationCallback(faceIconCallback) + } + start() + } + } + } + } + LottieColorUtils.applyDynamicColors(iconView.context, iconView) + viewModel.setPreviousIconWasError(showingError) + } + } + } + + launch { + viewModel.iconOverlayAsset + .sample( + combine( + viewModel.shouldAnimateIconOverlay, + viewModel.showingError, + ::Pair + ), + ::toTriple + ) + .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) -> + if (iconOverlayAsset != -1) { + iconOverlayView.setAnimation(iconOverlayAsset) + iconOverlayView.frame = 0 + LottieColorUtils.applyDynamicColors( + iconOverlayView.context, + iconOverlayView + ) + + if (shouldAnimateIconOverlay) { + iconOverlayView.playAnimation() + } + viewModel.setPreviousIconOverlayWasError(showingError) + } + } + } + + launch { + viewModel.shouldFlipIconView.collect { shouldFlipIconView -> + if (shouldFlipIconView) { + iconView.rotation = 180f + } + } + } + + launch { + viewModel.contentDescriptionId.collect { id -> + if (id != -1) { + iconView.contentDescription = iconView.context.getString(id) + } + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt deleted file mode 100644 index dfd3a9b8aebe9257a32af14af9630301b9769b78..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt +++ /dev/null @@ -1,95 +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.biometrics.ui.viewmodel - -import android.annotation.RawRes -import android.content.res.Configuration -import com.android.systemui.res.R -import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor -import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor -import com.android.systemui.biometrics.shared.model.DisplayRotation -import com.android.systemui.biometrics.shared.model.FingerprintSensorType -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine - -/** Models UI of [BiometricPromptLayout.iconView] */ -class PromptFingerprintIconViewModel -@Inject -constructor( - private val displayStateInteractor: DisplayStateInteractor, - promptSelectorInteractor: PromptSelectorInteractor, -) { - /** Current BiometricPromptLayout.iconView asset. */ - val iconAsset: Flow<Int> = - combine( - displayStateInteractor.currentRotation, - displayStateInteractor.isFolded, - displayStateInteractor.isInRearDisplayMode, - promptSelectorInteractor.sensorType, - ) { - rotation: DisplayRotation, - isFolded: Boolean, - isInRearDisplayMode: Boolean, - sensorType: FingerprintSensorType -> - when (sensorType) { - FingerprintSensorType.POWER_BUTTON -> - getSideFpsAnimationAsset(rotation, isFolded, isInRearDisplayMode) - // Replace below when non-SFPS iconAsset logic is migrated to this ViewModel - else -> -1 - } - } - - @RawRes - private fun getSideFpsAnimationAsset( - rotation: DisplayRotation, - isDeviceFolded: Boolean, - isInRearDisplayMode: Boolean, - ): Int = - when (rotation) { - DisplayRotation.ROTATION_90 -> - if (isInRearDisplayMode) { - R.raw.biometricprompt_rear_portrait_reverse_base - } else if (isDeviceFolded) { - R.raw.biometricprompt_folded_base_topleft - } else { - R.raw.biometricprompt_portrait_base_topleft - } - DisplayRotation.ROTATION_270 -> - if (isInRearDisplayMode) { - R.raw.biometricprompt_rear_portrait_base - } else if (isDeviceFolded) { - R.raw.biometricprompt_folded_base_bottomright - } else { - R.raw.biometricprompt_portrait_base_bottomright - } - else -> - if (isInRearDisplayMode) { - R.raw.biometricprompt_rear_landscape_base - } else if (isDeviceFolded) { - R.raw.biometricprompt_folded_base_default - } else { - R.raw.biometricprompt_landscape_base - } - } - - /** Called on configuration changes */ - fun onConfigurationChanged(newConfig: Configuration) { - displayStateInteractor.onConfigurationChanged(newConfig) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..11a5d8b578df9b27a9ccbf6f9b24a17d3af81ad6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt @@ -0,0 +1,721 @@ +/* + * 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.biometrics.ui.viewmodel + +import android.annotation.DrawableRes +import android.annotation.RawRes +import android.content.res.Configuration +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.res.R +import com.android.systemui.util.kotlin.combine +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** + * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] + */ +class PromptIconViewModel +constructor( + promptViewModel: PromptViewModel, + private val displayStateInteractor: DisplayStateInteractor, + promptSelectorInteractor: PromptSelectorInteractor +) { + + /** Auth types for the UI to display. */ + enum class AuthType { + Fingerprint, + Face, + Coex + } + + /** + * Indicates what auth type the UI currently displays. + * Fingerprint-only auth -> Fingerprint + * Face-only auth -> Face + * Co-ex auth, implicit flow -> Face + * Co-ex auth, explicit flow -> Coex + */ + val activeAuthType: Flow<AuthType> = + combine( + promptViewModel.modalities.distinctUntilChanged(), + promptViewModel.faceMode.distinctUntilChanged() + ) { modalities, faceMode -> + if (modalities.hasFaceAndFingerprint && !faceMode) { + AuthType.Coex + } else if (modalities.hasFaceOnly || faceMode) { + AuthType.Face + } else if (modalities.hasFingerprintOnly) { + AuthType.Fingerprint + } else { + throw IllegalStateException("unexpected modality: $modalities") + } + } + + /** Whether an error message is currently being shown. */ + val showingError = promptViewModel.showingError + + /** Whether the previous icon shown displayed an error. */ + private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false) + + /** Whether the previous icon overlay shown displayed an error. */ + private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false) + + fun setPreviousIconWasError(previousIconWasError: Boolean) { + _previousIconWasError.value = previousIconWasError + } + + fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) { + _previousIconOverlayWasError.value = previousIconOverlayWasError + } + + /** Called when iconView begins animating. */ + fun onAnimationStart() { + _animationEnded.value = false + } + + /** Called when iconView ends animating. */ + fun onAnimationEnd() { + _animationEnded.value = true + } + + private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false) + + /** + * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation + * ended). + */ + val shouldPulseAnimation: Flow<Boolean> = + combine(_animationEnded, promptViewModel.isAuthenticating) { + animationEnded, + isAuthenticating -> + animationEnded && isAuthenticating + } + .distinctUntilChanged() + + private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false) + + /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */ + val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow() + + /** Layout params for fingerprint iconView */ + val fingerprintIconWidth: Int = promptViewModel.fingerprintIconWidth + val fingerprintIconHeight: Int = promptViewModel.fingerprintIconHeight + + /** Layout params for face iconView */ + val faceIconWidth: Int = promptViewModel.faceIconWidth + val faceIconHeight: Int = promptViewModel.faceIconHeight + + /** Current BiometricPromptLayout.iconView asset. */ + val iconAsset: Flow<Int> = + activeAuthType.flatMapLatest { activeAuthType: AuthType -> + when (activeAuthType) { + AuthType.Fingerprint -> + combine( + displayStateInteractor.currentRotation, + displayStateInteractor.isFolded, + displayStateInteractor.isInRearDisplayMode, + promptSelectorInteractor.sensorType, + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.showingError + ) { + rotation: DisplayRotation, + isFolded: Boolean, + isInRearDisplayMode: Boolean, + sensorType: FingerprintSensorType, + authState: PromptAuthState, + isAuthenticating: Boolean, + showingError: Boolean -> + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode) + else -> + getFingerprintIconViewAsset( + authState.isAuthenticated, + isAuthenticating, + showingError + ) + } + } + AuthType.Face -> + shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean -> + if (shouldPulseAnimation) { + val iconAsset = + if (_lastPulseLightToDark.value) { + R.drawable.face_dialog_pulse_dark_to_light + } else { + R.drawable.face_dialog_pulse_light_to_dark + } + _lastPulseLightToDark.value = !_lastPulseLightToDark.value + flowOf(iconAsset) + } else { + combine( + promptViewModel.isAuthenticated.distinctUntilChanged(), + promptViewModel.isAuthenticating.distinctUntilChanged(), + promptViewModel.isPendingConfirmation.distinctUntilChanged(), + promptViewModel.showingError.distinctUntilChanged() + ) { + authState: PromptAuthState, + isAuthenticating: Boolean, + isPendingConfirmation: Boolean, + showingError: Boolean -> + getFaceIconViewAsset( + authState, + isAuthenticating, + isPendingConfirmation, + showingError + ) + } + } + } + AuthType.Coex -> + combine( + displayStateInteractor.currentRotation, + displayStateInteractor.isFolded, + displayStateInteractor.isInRearDisplayMode, + promptSelectorInteractor.sensorType, + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.isPendingConfirmation, + promptViewModel.showingError, + ) { + rotation: DisplayRotation, + isFolded: Boolean, + isInRearDisplayMode: Boolean, + sensorType: FingerprintSensorType, + authState: PromptAuthState, + isAuthenticating: Boolean, + isPendingConfirmation: Boolean, + showingError: Boolean -> + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode) + else -> + getCoexIconViewAsset( + authState, + isAuthenticating, + isPendingConfirmation, + showingError + ) + } + } + } + } + + private fun getFingerprintIconViewAsset( + isAuthenticated: Boolean, + isAuthenticating: Boolean, + showingError: Boolean + ): Int = + if (isAuthenticated) { + if (_previousIconWasError.value) { + R.raw.fingerprint_dialogue_error_to_success_lottie + } else { + R.raw.fingerprint_dialogue_fingerprint_to_success_lottie + } + } else if (isAuthenticating) { + if (_previousIconWasError.value) { + R.raw.fingerprint_dialogue_error_to_fingerprint_lottie + } else { + R.raw.fingerprint_dialogue_fingerprint_to_error_lottie + } + } else if (showingError) { + R.raw.fingerprint_dialogue_fingerprint_to_error_lottie + } else { + -1 + } + + @RawRes + private fun getSfpsIconViewAsset( + rotation: DisplayRotation, + isDeviceFolded: Boolean, + isInRearDisplayMode: Boolean, + ): Int = + when (rotation) { + DisplayRotation.ROTATION_90 -> + if (isInRearDisplayMode) { + R.raw.biometricprompt_rear_portrait_reverse_base + } else if (isDeviceFolded) { + R.raw.biometricprompt_folded_base_topleft + } else { + R.raw.biometricprompt_portrait_base_topleft + } + DisplayRotation.ROTATION_270 -> + if (isInRearDisplayMode) { + R.raw.biometricprompt_rear_portrait_base + } else if (isDeviceFolded) { + R.raw.biometricprompt_folded_base_bottomright + } else { + R.raw.biometricprompt_portrait_base_bottomright + } + else -> + if (isInRearDisplayMode) { + R.raw.biometricprompt_rear_landscape_base + } else if (isDeviceFolded) { + R.raw.biometricprompt_folded_base_default + } else { + R.raw.biometricprompt_landscape_base + } + } + + @DrawableRes + private fun getFaceIconViewAsset( + authState: PromptAuthState, + isAuthenticating: Boolean, + isPendingConfirmation: Boolean, + showingError: Boolean + ): Int = + if (authState.isAuthenticated && isPendingConfirmation) { + R.drawable.face_dialog_wink_from_dark + } else if (authState.isAuthenticated) { + R.drawable.face_dialog_dark_to_checkmark + } else if (isAuthenticating) { + _lastPulseLightToDark.value = false + R.drawable.face_dialog_pulse_dark_to_light + } else if (showingError) { + R.drawable.face_dialog_dark_to_error + } else if (_previousIconWasError.value) { + R.drawable.face_dialog_error_to_idle + } else { + R.drawable.face_dialog_idle_static + } + + @RawRes + private fun getCoexIconViewAsset( + authState: PromptAuthState, + isAuthenticating: Boolean, + isPendingConfirmation: Boolean, + showingError: Boolean + ): Int = + if (authState.isAuthenticatedAndExplicitlyConfirmed) { + R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie + } else if (isPendingConfirmation) { + if (_previousIconWasError.value) { + R.raw.fingerprint_dialogue_error_to_unlock_lottie + } else { + R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie + } + } else if (authState.isAuthenticated) { + if (_previousIconWasError.value) { + R.raw.fingerprint_dialogue_error_to_success_lottie + } else { + R.raw.fingerprint_dialogue_fingerprint_to_success_lottie + } + } else if (isAuthenticating) { + if (_previousIconWasError.value) { + R.raw.fingerprint_dialogue_error_to_fingerprint_lottie + } else { + R.raw.fingerprint_dialogue_fingerprint_to_error_lottie + } + } else if (showingError) { + R.raw.fingerprint_dialogue_fingerprint_to_error_lottie + } else { + -1 + } + + /** Current BiometricPromptLayout.biometric_icon_overlay asset. */ + var iconOverlayAsset: Flow<Int> = + activeAuthType.flatMapLatest { activeAuthType: AuthType -> + when (activeAuthType) { + AuthType.Fingerprint, + AuthType.Coex -> + combine( + displayStateInteractor.currentRotation, + promptSelectorInteractor.sensorType, + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.showingError + ) { + rotation: DisplayRotation, + sensorType: FingerprintSensorType, + authState: PromptAuthState, + isAuthenticating: Boolean, + showingError: Boolean -> + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + getSfpsIconOverlayAsset( + rotation, + authState.isAuthenticated, + isAuthenticating, + showingError + ) + else -> -1 + } + } + AuthType.Face -> flowOf(-1) + } + } + + @RawRes + private fun getSfpsIconOverlayAsset( + rotation: DisplayRotation, + isAuthenticated: Boolean, + isAuthenticating: Boolean, + showingError: Boolean + ): Int = + if (isAuthenticated) { + if (_previousIconOverlayWasError.value) { + when (rotation) { + DisplayRotation.ROTATION_0 -> + R.raw.biometricprompt_symbol_error_to_success_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_error_to_success_portrait_topleft + DisplayRotation.ROTATION_180 -> + R.raw.biometricprompt_symbol_error_to_success_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright + } + } else { + when (rotation) { + DisplayRotation.ROTATION_0 -> + R.raw.biometricprompt_symbol_fingerprint_to_success_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft + DisplayRotation.ROTATION_180 -> + R.raw.biometricprompt_symbol_fingerprint_to_success_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright + } + } + } else if (isAuthenticating) { + if (_previousIconOverlayWasError.value) { + when (rotation) { + DisplayRotation.ROTATION_0 -> + R.raw.biometricprompt_symbol_error_to_fingerprint_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft + DisplayRotation.ROTATION_180 -> + R.raw.biometricprompt_symbol_error_to_fingerprint_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright + } + } else { + when (rotation) { + DisplayRotation.ROTATION_0 -> + R.raw.biometricprompt_fingerprint_to_error_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft + DisplayRotation.ROTATION_180 -> + R.raw.biometricprompt_fingerprint_to_error_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright + } + } + } else if (showingError) { + when (rotation) { + DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft + DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright + } + } else { + -1 + } + + /** Content description for iconView */ + val contentDescriptionId: Flow<Int> = + activeAuthType.flatMapLatest { activeAuthType: AuthType -> + when (activeAuthType) { + AuthType.Fingerprint, + AuthType.Coex -> + combine( + promptSelectorInteractor.sensorType, + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.isPendingConfirmation, + promptViewModel.showingError + ) { + sensorType: FingerprintSensorType, + authState: PromptAuthState, + isAuthenticating: Boolean, + isPendingConfirmation: Boolean, + showingError: Boolean -> + getFingerprintIconContentDescriptionId( + sensorType, + authState.isAuthenticated, + isAuthenticating, + isPendingConfirmation, + showingError + ) + } + AuthType.Face -> + combine( + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.showingError, + ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean + -> + getFaceIconContentDescriptionId(authState, isAuthenticating, showingError) + } + } + } + + private fun getFingerprintIconContentDescriptionId( + sensorType: FingerprintSensorType, + isAuthenticated: Boolean, + isAuthenticating: Boolean, + isPendingConfirmation: Boolean, + showingError: Boolean + ): Int = + if (isPendingConfirmation) { + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + R.string.security_settings_sfps_enroll_find_sensor_message + else -> R.string.fingerprint_dialog_authenticated_confirmation + } + } else if (isAuthenticating || isAuthenticated) { + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + R.string.security_settings_sfps_enroll_find_sensor_message + else -> R.string.fingerprint_dialog_touch_sensor + } + } else if (showingError) { + R.string.biometric_dialog_try_again + } else { + -1 + } + + private fun getFaceIconContentDescriptionId( + authState: PromptAuthState, + isAuthenticating: Boolean, + showingError: Boolean + ): Int = + if (authState.isAuthenticatedAndExplicitlyConfirmed) { + R.string.biometric_dialog_face_icon_description_confirmed + } else if (authState.isAuthenticated) { + R.string.biometric_dialog_face_icon_description_authenticated + } else if (isAuthenticating) { + R.string.biometric_dialog_face_icon_description_authenticating + } else if (showingError) { + R.string.keyguard_face_failed + } else { + R.string.biometric_dialog_face_icon_description_idle + } + + /** Whether the current BiometricPromptLayout.iconView asset animation should be playing. */ + val shouldAnimateIconView: Flow<Boolean> = + activeAuthType.flatMapLatest { activeAuthType: AuthType -> + when (activeAuthType) { + AuthType.Fingerprint -> + combine( + promptSelectorInteractor.sensorType, + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.showingError + ) { + sensorType: FingerprintSensorType, + authState: PromptAuthState, + isAuthenticating: Boolean, + showingError: Boolean -> + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + shouldAnimateSfpsIconView( + authState.isAuthenticated, + isAuthenticating, + showingError + ) + else -> + shouldAnimateFingerprintIconView( + authState.isAuthenticated, + isAuthenticating, + showingError + ) + } + } + AuthType.Face -> + combine( + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.showingError + ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean + -> + isAuthenticating || + authState.isAuthenticated || + showingError || + _previousIconWasError.value + } + AuthType.Coex -> + combine( + promptSelectorInteractor.sensorType, + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.isPendingConfirmation, + promptViewModel.showingError, + ) { + sensorType: FingerprintSensorType, + authState: PromptAuthState, + isAuthenticating: Boolean, + isPendingConfirmation: Boolean, + showingError: Boolean -> + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + shouldAnimateSfpsIconView( + authState.isAuthenticated, + isAuthenticating, + showingError + ) + else -> + shouldAnimateCoexIconView( + authState.isAuthenticated, + isAuthenticating, + isPendingConfirmation, + showingError + ) + } + } + } + } + + private fun shouldAnimateFingerprintIconView( + isAuthenticated: Boolean, + isAuthenticating: Boolean, + showingError: Boolean + ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError + + private fun shouldAnimateSfpsIconView( + isAuthenticated: Boolean, + isAuthenticating: Boolean, + showingError: Boolean + ) = isAuthenticated || isAuthenticating || showingError + + private fun shouldAnimateCoexIconView( + isAuthenticated: Boolean, + isAuthenticating: Boolean, + isPendingConfirmation: Boolean, + showingError: Boolean + ) = + (isAuthenticating && _previousIconWasError.value) || + isPendingConfirmation || + isAuthenticated || + showingError + + /** Whether the current iconOverlayAsset animation should be playing. */ + val shouldAnimateIconOverlay: Flow<Boolean> = + activeAuthType.flatMapLatest { activeAuthType: AuthType -> + when (activeAuthType) { + AuthType.Fingerprint, + AuthType.Coex -> + combine( + promptSelectorInteractor.sensorType, + promptViewModel.isAuthenticated, + promptViewModel.isAuthenticating, + promptViewModel.showingError + ) { + sensorType: FingerprintSensorType, + authState: PromptAuthState, + isAuthenticating: Boolean, + showingError: Boolean -> + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + shouldAnimateSfpsIconOverlay( + authState.isAuthenticated, + isAuthenticating, + showingError + ) + else -> false + } + } + AuthType.Face -> flowOf(false) + } + } + + private fun shouldAnimateSfpsIconOverlay( + isAuthenticated: Boolean, + isAuthenticating: Boolean, + showingError: Boolean + ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError + + /** Whether the iconView should be flipped due to a device using reverse default rotation . */ + val shouldFlipIconView: Flow<Boolean> = + activeAuthType.flatMapLatest { activeAuthType: AuthType -> + when (activeAuthType) { + AuthType.Fingerprint, + AuthType.Coex -> + combine( + promptSelectorInteractor.sensorType, + displayStateInteractor.currentRotation + ) { sensorType: FingerprintSensorType, rotation: DisplayRotation -> + when (sensorType) { + FingerprintSensorType.POWER_BUTTON -> + (rotation == DisplayRotation.ROTATION_180) + else -> false + } + } + AuthType.Face -> flowOf(false) + } + } + + /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */ + val shouldRepeatAnimation: Flow<Boolean> = + activeAuthType.flatMapLatest { activeAuthType: AuthType -> + when (activeAuthType) { + AuthType.Fingerprint, + AuthType.Coex -> flowOf(false) + AuthType.Face -> promptViewModel.isAuthenticating.map { it } + } + } + + /** Called on configuration changes */ + fun onConfigurationChanged(newConfig: Configuration) { + displayStateInteractor.onConfigurationChanged(newConfig) + } + + /** iconView assets for caching */ + fun getRawAssets(hasSfps: Boolean): List<Int> { + return if (hasSfps) { + listOf( + R.raw.biometricprompt_fingerprint_to_error_landscape, + R.raw.biometricprompt_folded_base_bottomright, + R.raw.biometricprompt_folded_base_default, + R.raw.biometricprompt_folded_base_topleft, + R.raw.biometricprompt_landscape_base, + R.raw.biometricprompt_portrait_base_bottomright, + R.raw.biometricprompt_portrait_base_topleft, + R.raw.biometricprompt_symbol_error_to_fingerprint_landscape, + R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright, + R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft, + R.raw.biometricprompt_symbol_error_to_success_landscape, + R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright, + R.raw.biometricprompt_symbol_error_to_success_portrait_topleft, + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright, + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft, + R.raw.biometricprompt_symbol_fingerprint_to_success_landscape, + R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright, + R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft + ) + } else { + listOf( + R.raw.fingerprint_dialogue_error_to_fingerprint_lottie, + R.raw.fingerprint_dialogue_error_to_success_lottie, + R.raw.fingerprint_dialogue_fingerprint_to_error_lottie, + R.raw.fingerprint_dialogue_fingerprint_to_success_lottie + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 267afae118e6ff499400d27e7bb474f509c2f30b..e49b4a7bbce9de7bfa61269305f5ea27ef7ad084 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -28,10 +28,10 @@ import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.biometrics.shared.model.PromptKind -import com.android.systemui.biometrics.ui.binder.Spaghetti import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject import kotlinx.coroutines.Job @@ -39,7 +39,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -51,25 +50,29 @@ import kotlinx.coroutines.launch class PromptViewModel @Inject constructor( - private val displayStateInteractor: DisplayStateInteractor, - private val promptSelectorInteractor: PromptSelectorInteractor, + displayStateInteractor: DisplayStateInteractor, + promptSelectorInteractor: PromptSelectorInteractor, private val vibrator: VibratorHelper, @Application context: Context, private val featureFlags: FeatureFlags, ) { - /** Models UI of [BiometricPromptLayout.iconView] */ - val fingerprintIconViewModel: PromptFingerprintIconViewModel = - PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor) - /** The set of modalities available for this prompt */ val modalities: Flow<BiometricModalities> = promptSelectorInteractor.prompt .map { it?.modalities ?: BiometricModalities() } .distinctUntilChanged() - // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state - private var _legacyState = MutableStateFlow(Spaghetti.BiometricState.STATE_IDLE) - val legacyState: StateFlow<Spaghetti.BiometricState> = _legacyState.asStateFlow() + /** Layout params for fingerprint iconView */ + val fingerprintIconWidth: Int = + context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_width) + val fingerprintIconHeight: Int = + context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_height) + + /** Layout params for face iconView */ + val faceIconWidth: Int = + context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) + val faceIconHeight: Int = + context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false) @@ -82,6 +85,12 @@ constructor( /** If the user has successfully authenticated and confirmed (when explicitly required). */ val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow() + /** If the auth is pending confirmation. */ + val isPendingConfirmation: Flow<Boolean> = + isAuthenticated.map { authState -> + authState.isAuthenticated && authState.needsUserConfirmation + } + private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false) /** The kind of credential the user has. */ @@ -96,6 +105,9 @@ constructor( /** A message to show the user, if there is an error, hint, or help to show. */ val message: Flow<PromptMessage> = _message.asStateFlow() + /** Whether an error message is currently being shown. */ + val showingError: Flow<Boolean> = message.map { it.isError }.distinctUntilChanged() + private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace } private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending) @@ -141,6 +153,38 @@ constructor( !isOverlayTouched && size.isNotSmall } + /** + * When fingerprint and face modalities are enrolled, indicates whether only face auth has + * started. + * + * True when fingerprint and face modalities are enrolled and implicit flow is active. This + * occurs in co-ex auth when confirmation is not required and only face auth is started, then + * becomes false when device transitions to explicit flow after a first error, when the + * fingerprint sensor is started. + * + * False when the dialog opens in explicit flow (fingerprint and face modalities enrolled but + * confirmation is required), or if user has only fingerprint enrolled, or only face enrolled. + */ + val faceMode: Flow<Boolean> = + combine(modalities, isConfirmationRequired, fingerprintStartMode) { + modalities: BiometricModalities, + isConfirmationRequired: Boolean, + fingerprintStartMode: FingerprintStartMode -> + if (modalities.hasFaceAndFingerprint) { + if (isConfirmationRequired) { + false + } else { + !fingerprintStartMode.isStarted + } + } else { + false + } + } + .distinctUntilChanged() + + val iconViewModel: PromptIconViewModel = + PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor) + /** Padding for prompt UI elements */ val promptPadding: Flow<Rect> = combine(size, displayStateInteractor.currentRotation) { size, rotation -> @@ -184,9 +228,9 @@ constructor( val isConfirmButtonVisible: Flow<Boolean> = combine( size, - isAuthenticated, - ) { size, authState -> - size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation + isPendingConfirmation, + ) { size, isPendingConfirmation -> + size.isNotSmall && isPendingConfirmation } .distinctUntilChanged() @@ -293,7 +337,6 @@ constructor( _isAuthenticated.value = PromptAuthState(false) _forceMediumSize.value = true _message.value = PromptMessage.Error(message) - _legacyState.value = Spaghetti.BiometricState.STATE_ERROR if (hapticFeedback) { vibrator.error(failedModality) @@ -305,7 +348,7 @@ constructor( if (authenticateAfterError) { showAuthenticating(messageAfterError) } else { - showInfo(messageAfterError) + showHelp(messageAfterError) } } } @@ -325,15 +368,12 @@ constructor( private fun supportsRetry(failedModality: BiometricModality) = failedModality == BiometricModality.Face - suspend fun showHelp(message: String) = showHelp(message, clearIconError = false) - suspend fun showInfo(message: String) = showHelp(message, clearIconError = true) - /** * Show a persistent help message. * * Will be show even if the user has already authenticated. */ - private suspend fun showHelp(message: String, clearIconError: Boolean) { + suspend fun showHelp(message: String) { val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated if (!alreadyAuthenticated) { _isAuthenticating.value = false @@ -343,16 +383,6 @@ constructor( _message.value = if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty _forceMediumSize.value = true - _legacyState.value = - if (alreadyAuthenticated && isConfirmationRequired.first()) { - Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION - } else if (alreadyAuthenticated && !isConfirmationRequired.first()) { - Spaghetti.BiometricState.STATE_AUTHENTICATED - } else if (clearIconError) { - Spaghetti.BiometricState.STATE_IDLE - } else { - Spaghetti.BiometricState.STATE_HELP - } messageJob?.cancel() messageJob = null @@ -376,7 +406,6 @@ constructor( _message.value = if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty _forceMediumSize.value = true - _legacyState.value = Spaghetti.BiometricState.STATE_HELP messageJob?.cancel() messageJob = launch { @@ -396,7 +425,6 @@ constructor( _isAuthenticating.value = true _isAuthenticated.value = PromptAuthState(false) _message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message) - _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATING // reset the try again button(s) after the user attempts a retry if (isRetry) { @@ -427,12 +455,6 @@ constructor( _isAuthenticated.value = PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay) _message.value = PromptMessage.Empty - _legacyState.value = - if (needsUserConfirmation) { - Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION - } else { - Spaghetti.BiometricState.STATE_AUTHENTICATED - } if (!needsUserConfirmation) { vibrator.success(modality) @@ -472,7 +494,6 @@ constructor( _isAuthenticated.value = authState.asExplicitlyConfirmed() _message.value = PromptMessage.Empty - _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATED vibrator.success(authState.authenticatedModality) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt index 2bd62587834da6d0ef61c0585090aa51970c5d81..21578f491de7e5c2d9223a21f6917b85a58358b0 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt @@ -146,6 +146,16 @@ constructor( /** Show the bouncer if necessary and set the relevant states. */ @JvmOverloads fun show(isScrimmed: Boolean) { + if (primaryBouncerView.delegate == null) { + Log.d( + TAG, + "PrimaryBouncerInteractor#show is being called before the " + + "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " + + "primaryBouncer state." + ) + return + } + // Reset some states as we show the bouncer. repository.setKeyguardAuthenticatedBiometrics(null) repository.setPrimaryStartingToHide(false) diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java index 0bf50693344aa3a5b079351c9b109f8e0bf0ab85..7d73896bb37069dd1c1e1838ea86cee6b2108865 100644 --- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java +++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java @@ -19,7 +19,6 @@ package com.android.systemui.colorextraction; import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Color; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; @@ -47,9 +46,7 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, ConfigurationController.ConfigurationListener { private static final String TAG = "SysuiColorExtractor"; private final Tonal mTonal; - private boolean mHasMediaArtwork; private final GradientColors mNeutralColorsLock; - private final GradientColors mBackdropColors; private Lazy<SelectedUserInteractor> mUserInteractor; @Inject @@ -82,9 +79,6 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, mNeutralColorsLock = new GradientColors(); configurationController.addCallback(this); dumpManager.registerDumpable(getClass().getSimpleName(), this); - - mBackdropColors = new GradientColors(); - mBackdropColors.setMainColor(Color.BLACK); mUserInteractor = userInteractor; // Listen to all users instead of only the current one. @@ -123,14 +117,6 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, triggerColorsChanged(WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK); } - @Override - public GradientColors getColors(int which, int type) { - if (mHasMediaArtwork && (which & WallpaperManager.FLAG_LOCK) != 0) { - return mBackdropColors; - } - return super.getColors(which, type); - } - /** * Colors that should be using for scrims. * @@ -140,14 +126,7 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, * - Black otherwise */ public GradientColors getNeutralColors() { - return mHasMediaArtwork ? mBackdropColors : mNeutralColorsLock; - } - - public void setHasMediaArtwork(boolean hasBackdrop) { - if (mHasMediaArtwork != hasBackdrop) { - mHasMediaArtwork = hasBackdrop; - triggerColorsChanged(WallpaperManager.FLAG_LOCK); - } + return mNeutralColorsLock; } @Override @@ -164,7 +143,5 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, pw.println(" system: " + Arrays.toString(system)); pw.println(" lock: " + Arrays.toString(lock)); pw.println(" Neutral colors: " + mNeutralColorsLock); - pw.println(" Has media backdrop: " + mHasMediaArtwork); - } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt index f9c4f29afee9e28b3c2723dd3e05519fb6c96d01..1a214ba5a3d97a39b1d00af44e26999dbcabd261 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt @@ -16,7 +16,7 @@ package com.android.systemui.communal.data.model -import com.android.systemui.communal.shared.CommunalContentSize +import com.android.systemui.communal.shared.model.CommunalContentSize /** Metadata for the default widgets */ data class CommunalWidgetMetadata( 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 53b6879db3d7fb3947654492a05299d59775f814..485e5122cfad98c9c302d223ad35fc116954271d 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 @@ -1,5 +1,6 @@ package com.android.systemui.communal.data.repository +import com.android.systemui.FeatureFlags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags @@ -15,10 +16,11 @@ interface CommunalRepository { class CommunalRepositoryImpl @Inject constructor( - private val featureFlags: FeatureFlagsClassic, + private val featureFlags: FeatureFlags, + private val featureFlagsClassic: FeatureFlagsClassic, ) : CommunalRepository { override val isCommunalEnabled: Boolean get() = - featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && - featureFlags.isEnabled(Flags.COMMUNAL_HUB) + featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && + featureFlags.communalHub() } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index f13b62fbfeb90a8dc5a5a5d3cf0b91c4dc563ddf..77025dc8839a12a20009139b0daa8388b937f8af 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -20,6 +20,7 @@ import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo import android.content.BroadcastReceiver +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -28,11 +29,12 @@ import android.os.UserManager import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.communal.data.model.CommunalWidgetMetadata -import com.android.systemui.communal.shared.CommunalAppWidgetInfo -import com.android.systemui.communal.shared.CommunalContentSize +import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger @@ -43,6 +45,7 @@ import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map /** Encapsulates the state of widgets for communal mode. */ @@ -52,6 +55,9 @@ interface CommunalWidgetRepository { /** Widgets that are allowed to render in the glanceable hub */ val communalWidgetAllowlist: List<CommunalWidgetMetadata> + + /** A flow of information about all the communal widgets to show. */ + val communalWidgets: Flow<List<CommunalWidgetContentModel>> } @SysUISingleton @@ -67,7 +73,7 @@ constructor( private val userManager: UserManager, private val userTracker: UserTracker, @CommunalLog logBuffer: LogBuffer, - featureFlags: FeatureFlags, + featureFlags: FeatureFlagsClassic, ) : CommunalWidgetRepository { companion object { const val TAG = "CommunalWidgetRepository" @@ -88,49 +94,59 @@ constructor( // Widgets that should be rendered in communal mode. private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf() - private val isUserUnlocked: Flow<Boolean> = callbackFlow { - if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) { - awaitClose() - } - - fun isUserUnlockingOrUnlocked(): Boolean { - return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) - } - - fun send() { - trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG) - } + private val isUserUnlocked: Flow<Boolean> = + callbackFlow { + if (!communalRepository.isCommunalEnabled) { + awaitClose() + } - if (isUserUnlockingOrUnlocked()) { - send() - awaitClose() - } else { - val receiver = - object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - send() - } + fun isUserUnlockingOrUnlocked(): Boolean { + return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) } - broadcastDispatcher.registerReceiver( - receiver, - IntentFilter(Intent.ACTION_USER_UNLOCKED), - ) + fun send() { + trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG) + } - awaitClose { broadcastDispatcher.unregisterReceiver(receiver) } + if (isUserUnlockingOrUnlocked()) { + send() + awaitClose() + } else { + val receiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + send() + } + } + + broadcastDispatcher.registerReceiver( + receiver, + IntentFilter(Intent.ACTION_USER_UNLOCKED), + ) + + awaitClose { broadcastDispatcher.unregisterReceiver(receiver) } + } + } + .distinctUntilChanged() + + private val isHostActive: Flow<Boolean> = + isUserUnlocked.map { + if (it) { + startListening() + true + } else { + stopListening() + clearWidgets() + false + } } - } override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = - isUserUnlocked.map { isUserUnlocked -> - if (!isUserUnlocked) { - clearWidgets() - stopListening() + isHostActive.map { isHostActive -> + if (!isHostActive || !featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) { return@map null } - startListening() - val providerInfo = appWidgetManager.installedProviders.find { it.loadLabel(packageManager).equals(WIDGET_LABEL) @@ -144,6 +160,42 @@ constructor( return@map addWidget(providerInfo) } + override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = + isHostActive.map { isHostActive -> + if (!isHostActive) { + return@map emptyList() + } + + // The allowlist should be fetched from the local database with all the metadata tied to + // a widget, including an appWidgetId if it has been bound. Before the database is set + // up, we are going to use the app widget host as the source of truth for bound widgets, + // and rebind each time on boot. + + // Remove all previously bound widgets. + appWidgetHost.appWidgetIds.forEach { appWidgetHost.deleteAppWidgetId(it) } + + val inventory = mutableListOf<CommunalWidgetContentModel>() + + // Bind all widgets from the allowlist. + communalWidgetAllowlist.forEach { + val id = appWidgetHost.allocateAppWidgetId() + appWidgetManager.bindAppWidgetId( + id, + ComponentName.unflattenFromString(it.componentName), + ) + + inventory.add( + CommunalWidgetContentModel( + appWidgetId = id, + providerInfo = appWidgetManager.getAppWidgetInfo(id), + priority = it.priority, + ) + ) + } + + return@map inventory.toList() + } + private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> { val componentNames = applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist) @@ -151,7 +203,7 @@ constructor( CommunalWidgetMetadata( componentName = name, priority = componentNames.size - index, - sizes = listOf(CommunalContentSize.HALF) + sizes = listOf(CommunalContentSize.HALF), ) } } 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 04bb6ae75e60a5a40def0880b610ed81e8806ee6..62387079ab353c6da98f92e5141da5e23728ec88 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 @@ -18,7 +18,8 @@ package com.android.systemui.communal.domain.interactor import com.android.systemui.communal.data.repository.CommunalRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository -import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -38,4 +39,12 @@ constructor( /** A flow of info about the widget to be displayed, or null if widget is unavailable. */ val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo + + /** + * A flow of information about widgets to be shown in communal hub. + * + * Currently only showing persistent widgets that have been bound to the app widget service + * (have an allocated id). + */ + val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt deleted file mode 100644 index 0bd7d86c972d28c125108d4f577878cd6a03179d..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.android.systemui.communal.shared - -/** Supported sizes for communal content in the layout grid. */ -enum class CommunalContentSize { - FULL, - HALF, - THIRD, -} diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt similarity index 94% rename from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt rename to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt index 0803a01b93b8a8e363c87561312dfaa2a101370d..109ed2da7e48089659142c924c20296905f7b4bc 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt @@ -15,7 +15,7 @@ * */ -package com.android.systemui.communal.shared +package com.android.systemui.communal.shared.model import android.appwidget.AppWidgetProviderInfo diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt new file mode 100644 index 0000000000000000000000000000000000000000..7f05b9cd4943c8b058ff87ef6215b7330f42ef2d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt @@ -0,0 +1,25 @@ +/* + * 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.communal.shared.model + +enum class CommunalContentCategory { + /** The content persists in the communal hub until removed by the user. */ + PERSISTENT, + + /** The content temporarily shows up in the communal hub when certain conditions are met. */ + TRANSIENT, +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt new file mode 100644 index 0000000000000000000000000000000000000000..39a6476929ed4c7ea19b74a734385f76dc18e84d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt @@ -0,0 +1,29 @@ +/* + * 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.communal.shared.model + +/** Supported sizes for communal content in the layout grid. */ +enum class CommunalContentSize { + /** Content takes the full height of the column. */ + FULL, + + /** Content takes half of the height of the column. */ + HALF, + + /** Content takes a third of the height of the column. */ + THIRD, +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..e141dc40477cb2ea084a3b8679e7a476ab36974a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt @@ -0,0 +1,26 @@ +/* + * 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.communal.shared.model + +import android.appwidget.AppWidgetProviderInfo + +/** Encapsulates data for a communal widget. */ +data class CommunalWidgetContentModel( + val appWidgetId: Int, + val providerInfo: AppWidgetProviderInfo, + val priority: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt index 2a08d7f6bc28325463a7a931c63763de10c3d915..0daf7b5610e8de08c891bdc859200ed90bcc0b0c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt @@ -20,7 +20,7 @@ import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.content.Context import android.util.SizeF -import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo import com.android.systemui.communal.ui.view.CommunalWidgetWrapper import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..98060dc1dcebcfab438fd67fc11834b190bcd005 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt @@ -0,0 +1,15 @@ +package com.android.systemui.communal.ui.model + +import android.view.View +import com.android.systemui.communal.shared.model.CommunalContentSize + +/** + * Encapsulates data for a communal content that holds a view. + * + * This model stays in the UI layer. + */ +data class CommunalContentUiModel( + val view: View, + val size: CommunalContentSize, + val priority: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index ddeb1d67b945414fafc8a64edbbf0e33b9a3975f..25c64eafe2557fe0cf8749ff29cac19f9e91bab7 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -16,17 +16,43 @@ package com.android.systemui.communal.ui.viewmodel +import android.appwidget.AppWidgetHost +import android.content.Context +import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor +import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.ui.model.CommunalContentUiModel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map @SysUISingleton class CommunalViewModel @Inject constructor( + @Application private val context: Context, + private val appWidgetHost: AppWidgetHost, + communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, ) { /** Whether communal hub should show tutorial content. */ val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable + + /** List of widgets to be displayed in the communal hub. */ + val widgetContent: Flow<List<CommunalContentUiModel>> = + communalInteractor.widgetContent.map { + it.map { + // TODO(b/306406256): As adding and removing widgets functionalities are + // supported, cache the host views so they're not recreated each time. + val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo) + return@map CommunalContentUiModel( + view = hostView, + priority = it.priority, + // All widgets have HALF size. + size = CommunalContentSize.HALF, + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt index 8fba342c49bedfa00534ff3e27074a01adcd2d0e..d7bbea64332e8c840445ba0771a19e6a327f7530 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt @@ -17,7 +17,7 @@ package com.android.systemui.communal.ui.viewmodel import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 9221832b4ba1e5e2f3c67af3b659559f6599e382..4bdea75d9d71a8f089741275022b1b9d1925aa18 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -79,4 +79,7 @@ interface BaseComposeFacade { context: Context, viewModel: CommunalViewModel, ): View + + /** Creates a container that hosts the communal UI and handles gesture transitions. */ + fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 5d6949b3e87f8232da39c105a994e251d43d8d01..d8ff535ffd782e1087526d336d93570171895736 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -57,7 +57,6 @@ import com.android.systemui.statusbar.ImmersiveModeConfirmation import com.android.systemui.statusbar.gesture.GesturePointerEventListener import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController -import com.android.systemui.statusbar.phone.LockscreenWallpaper import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener import com.android.systemui.stylus.StylusUsiPowerStartable @@ -342,11 +341,6 @@ abstract class SystemUICoreStartableModule { @ClassKey(KeyguardViewConfigurator::class) abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable - @Binds - @IntoMap - @ClassKey(LockscreenWallpaper::class) - abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable - @Binds @IntoMap @ClassKey(ScrimController::class) diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt index d19efbdd80269d579cdac2a43a0e576fb4bf4350..3f215333382eae12b6245b951336897a51cdef59 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt @@ -21,6 +21,7 @@ import android.view.View import android.widget.TextView import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog +import com.android.systemui.statusbar.policy.ConfigurationController /** * Dialog used to decide what to do with a connected display. @@ -32,8 +33,9 @@ class MirroringConfirmationDialog( context: Context, private val onStartMirroringClickListener: View.OnClickListener, private val onCancelMirroring: View.OnClickListener, + configurationController: ConfigurationController? = null, theme: Int = R.style.Theme_SystemUI_Dialog, -) : SystemUIBottomSheetDialog(context, theme) { +) : SystemUIBottomSheetDialog(context, configurationController, theme) { private lateinit var mirrorButton: TextView private lateinit var dismissButton: TextView 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 86ef439361b06af4d4abe330568d68c78c198458..91f535df586a22a40c8a826394c205d8e97d644d 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 @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Background 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 javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -41,7 +42,8 @@ constructor( private val context: Context, private val connectedDisplayInteractor: ConnectedDisplayInteractor, @Application private val scope: CoroutineScope, - @Background private val bgDispatcher: CoroutineDispatcher + @Background private val bgDispatcher: CoroutineDispatcher, + private val configurationController: ConfigurationController ) { private var dialog: Dialog? = null @@ -71,7 +73,8 @@ constructor( onCancelMirroring = { scope.launch(bgDispatcher) { pendingDisplay.ignore() } hideDialog() - } + }, + configurationController ) .apply { show() } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index ecd8b0e7fd4ecf068a0ac296c221c382f5d49905..10fac4d328e02649cdd20e6fa9110f19f92c691b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -549,12 +549,6 @@ object Flags { val LOCKSCREEN_ENABLE_LANDSCAPE = unreleasedFlag("lockscreen.enable_landscape") - // TODO(b/273443374): Tracking Bug - @Keep - @JvmField - val LOCKSCREEN_LIVE_WALLPAPER = - sysPropBooleanFlag("persist.wm.debug.lockscreen_live_wallpaper", default = true) - // TODO(b/281648899): Tracking bug @Keep @JvmField @@ -776,7 +770,7 @@ object Flags { val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog") // TODO(b/289573946): Tracking Bug - @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true) + @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text") // TODO(b/302087895): Tracking Bug @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data") diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt index 7ccc26c063d3da3e830012aa41d77a4aeae95605..4a5cc641dcf1f329a215010167649f6b1109391d 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt @@ -16,7 +16,6 @@ package com.android.systemui.flags -import android.util.Log import com.android.systemui.Dependency /** @@ -65,8 +64,7 @@ private constructor( * } * ```` */ - fun assertInLegacyMode() = - check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." } + fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName) /** * Called to ensure code is only run when the flag is enabled. This protects users from the @@ -81,13 +79,8 @@ private constructor( * } * ``` */ - fun isUnexpectedlyInLegacyMode(): Boolean { - if (!isEnabled) { - val message = "New code path expects $flagName to be enabled." - Log.wtf(TAG, message, Exception(message)) - } - return !isEnabled - } + fun isUnexpectedlyInLegacyMode(): Boolean = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName) companion object { private const val TAG = "RefactorFlag" diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..2aa397f3e74467431ce96ca26f64d0286f4cc647 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.flags + +import android.util.Log + +/** + * Utilities for writing your own objects to uphold refactor flag conventions. + * + * Example usage: + * ``` + * object SomeRefactor { + * const val FLAG_NAME = Flags.SOME_REFACTOR + * @JvmStatic inline val isEnabled get() = Flags.someRefactor() + * @JvmStatic inline fun isUnexpectedlyInLegacyMode() = + * RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + * @JvmStatic inline fun assertInLegacyMode() = + * RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) + * } + * ``` + */ +@Suppress("NOTHING_TO_INLINE") +object RefactorFlagUtils { + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + * + * Example usage: + * ``` + * public void setNewController(SomeController someController) { + * if (SomeRefactor.isUnexpectedlyInLegacyMode()) return; + * mSomeController = someController; + * } + * ``` + */ + inline fun isUnexpectedlyInLegacyMode(isEnabled: Boolean, flagName: Any): Boolean { + val inLegacyMode = !isEnabled + if (inLegacyMode) { + val message = "New code path expects $flagName to be enabled." + Log.wtf("RefactorFlag", message, IllegalStateException(message)) + } + return inLegacyMode + } + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + * + * Example usage: + * ``` + * public void setSomeLegacyController(SomeController someController) { + * SomeRefactor.assertInLegacyMode(); + * mSomeController = someController; + * } + * ```` + */ + inline fun assertInLegacyMode(isEnabled: Boolean, flagName: Any) = + check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 47798955b43da31084c03cffe92567a65bcffa12..2b1cdc2ff026193430bff295617813916ef1a841 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -202,7 +202,7 @@ public class KeyguardService extends Service { // Wrap Keyguard going away animation. // Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy). public static IRemoteTransition wrap(final KeyguardViewMediator keyguardViewMediator, - final IRemoteAnimationRunner runner, final boolean lockscreenLiveWallpaperEnabled) { + final IRemoteAnimationRunner runner) { return new IRemoteTransition.Stub() { @GuardedBy("mLeashMap") @@ -236,9 +236,8 @@ public class KeyguardService extends Service { } } initAlphaForAnimationTargets(t, apps); - if (lockscreenLiveWallpaperEnabled) { - initAlphaForAnimationTargets(t, wallpapers); - } + initAlphaForAnimationTargets(t, wallpapers); + t.apply(); runner.onAnimationStart( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 0bac40bcbcc13bbe28ddda92a5aef7ecd6bc13ff..c8c06ae65d45af48b7195abd7ca2991dc1ea9e91 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -707,8 +707,7 @@ class KeyguardUnlockAnimationController @Inject constructor( return@postDelayed } - if ((wallpaperTargets?.isNotEmpty() == true) && - wallpaperManager.isLockscreenLiveWallpaperEnabled()) { + if ((wallpaperTargets?.isNotEmpty() == true)) { fadeInWallpaper() hideKeyguardViewAfterRemoteAnimation() } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e893c6305deee74b02568ca993a1f7d82c157354..4e6a872cb3f7035c2fdb5a7e29ca74949b24981b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -184,8 +184,6 @@ import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; - - import kotlinx.coroutines.CoroutineDispatcher; /** @@ -1517,12 +1515,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, setShowingLocked(false /* showing */, true /* forceCallbacks */); } - boolean isLLwpEnabled = getWallpaperManager().isLockscreenLiveWallpaperEnabled(); mKeyguardTransitions.register( - KeyguardService.wrap(this, getExitAnimationRunner(), isLLwpEnabled), - KeyguardService.wrap(this, getOccludeAnimationRunner(), isLLwpEnabled), - KeyguardService.wrap(this, getOccludeByDreamAnimationRunner(), isLLwpEnabled), - KeyguardService.wrap(this, getUnoccludeAnimationRunner(), isLLwpEnabled)); + KeyguardService.wrap(this, getExitAnimationRunner()), + KeyguardService.wrap(this, getOccludeAnimationRunner()), + KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()), + KeyguardService.wrap(this, getUnoccludeAnimationRunner())); final ContentResolver cr = mContext.getContentResolver(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index d06f31fed8dbdb8d479e8082d9b550e521cbab3c..7e360cfad66d2bc102f44c7fb22b195d7415aa9c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -26,13 +26,13 @@ import com.android.systemui.util.kotlin.Utils.Companion.toQuad import com.android.systemui.util.kotlin.Utils.Companion.toQuint import com.android.systemui.util.kotlin.sample import com.android.wm.shell.animation.Interpolators +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds @SysUISingleton class FromAlternateBouncerTransitionInteractor @@ -130,11 +130,16 @@ constructor( override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { interpolator = Interpolators.LINEAR - duration = TRANSITION_DURATION_MS.inWholeMilliseconds + duration = + when (toState) { + KeyguardState.GONE -> TO_GONE_DURATION + else -> TRANSITION_DURATION_MS + }.inWholeMilliseconds } } companion object { val TRANSITION_DURATION_MS = 300.milliseconds + val TO_GONE_DURATION = 500.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index fbe26de4e9ba324920a80101633e77a192563f7b..b0b857729c77baf929aef5fa3f384e4f6e97defa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -143,6 +143,11 @@ constructor( val dozingToLockscreenTransition: Flow<TransitionStep> = repository.transition(DOZING, LOCKSCREEN) + /** Receive all [TransitionStep] matching a filter of [from]->[to] */ + fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> { + return repository.transition(from, to) + } + /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..023d16cab013c3dff9455abd566e68ed4b0afb59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.ScrimAlpha +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down ALTERNATE_BOUNCER->GONE transition into discrete steps for corresponding views to + * consume. + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class AlternateBouncerToGoneTransitionViewModel +@Inject +constructor( + bouncerToGoneFlows: BouncerToGoneFlows, +) { + + /** Scrim alpha values */ + val scrimAlpha: Flow<ScrimAlpha> = + bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt new file mode 100644 index 0000000000000000000000000000000000000000..da74f2fa061e514655ffb02f50d3dde1321cb560 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.ScrimAlpha +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.SysuiStatusBarStateController +import dagger.Lazy +import javax.inject.Inject +import kotlin.time.Duration +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map + +/** ALTERNATE and PRIMARY bouncers common animations */ +@OptIn(ExperimentalCoroutinesApi::class) +class BouncerToGoneFlows +@Inject +constructor( + private val interactor: KeyguardTransitionInteractor, + private val statusBarStateController: SysuiStatusBarStateController, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>, + private val featureFlags: FeatureFlagsClassic, + private val shadeInteractor: ShadeInteractor, +) { + /** Common fade for scrim alpha values during *BOUNCER->GONE */ + fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> { + return if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + keyguardDismissActionInteractor + .get() + .willAnimateDismissActionOnLockscreen + .flatMapLatest { createScrimAlphaFlow(duration, fromState) { it } } + } else { + createScrimAlphaFlow( + duration, + fromState, + primaryBouncerInteractor::willRunDismissFromKeyguard + ) + } + } + + private fun createScrimAlphaFlow( + duration: Duration, + fromState: KeyguardState, + willRunAnimationOnKeyguard: () -> Boolean + ): Flow<ScrimAlpha> { + var isShadeExpanded = false + var leaveShadeOpen: Boolean = false + var willRunDismissFromKeyguard: Boolean = false + val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = duration, + transitionFlow = interactor.transition(fromState, GONE) + ) + + return shadeInteractor.shadeExpansion.flatMapLatest { shadeExpansion -> + transitionAnimation + .createFlow( + duration = duration, + interpolator = EMPHASIZED_ACCELERATE, + onStart = { + leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() + willRunDismissFromKeyguard = willRunAnimationOnKeyguard() + isShadeExpanded = shadeExpansion > 0f + }, + onStep = { 1f - it }, + ) + .map { + if (willRunDismissFromKeyguard) { + if (isShadeExpanded) { + ScrimAlpha( + behindAlpha = it, + notificationsAlpha = it, + ) + } else { + ScrimAlpha() + } + } else if (leaveShadeOpen) { + ScrimAlpha( + behindAlpha = 1f, + notificationsAlpha = 1f, + ) + } else { + ScrimAlpha(behindAlpha = it) + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt index 078318196883ad3e6a852078d4f5b92ae054f73f..0e95be20d059d8cfd39be471ed94665a34958719 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic @@ -24,6 +23,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -33,7 +34,6 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map /** * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to @@ -49,11 +49,12 @@ constructor( private val primaryBouncerInteractor: PrimaryBouncerInteractor, keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>, featureFlags: FeatureFlagsClassic, + bouncerToGoneFlows: BouncerToGoneFlows, ) { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_GONE_DURATION, - transitionFlow = interactor.primaryBouncerToGoneTransition, + transitionFlow = interactor.transition(PRIMARY_BOUNCER, GONE) ) private var leaveShadeOpen: Boolean = false @@ -110,38 +111,6 @@ constructor( ) } - /** Scrim alpha values */ val scrimAlpha: Flow<ScrimAlpha> = - if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { - keyguardDismissActionInteractor - .get() - .willAnimateDismissActionOnLockscreen - .flatMapLatest { createScrimAlphaFlow { it } } - } else { - createScrimAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard) - } - private fun createScrimAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<ScrimAlpha> { - return transitionAnimation - .createFlow( - duration = TO_GONE_DURATION, - interpolator = EMPHASIZED_ACCELERATE, - onStart = { - leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() - willRunDismissFromKeyguard = willRunAnimationOnKeyguard() - }, - onStep = { 1f - it }, - ) - .map { - if (willRunDismissFromKeyguard) { - ScrimAlpha() - } else if (leaveShadeOpen) { - ScrimAlpha( - behindAlpha = 1f, - notificationsAlpha = 1f, - ) - } else { - ScrimAlpha(behindAlpha = it) - } - } - } + bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, PRIMARY_BOUNCER) } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt index ce8b79cd9e38882adc0a494a5e1e66cd66313cf1..d8cab6ab2a6cf3936c7fd03c0c4bc50e2b1d5e82 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -23,7 +23,6 @@ import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGE import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN -import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED as METRICS_STATE_PERMISSION_REQUEST_DISPLAYED import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject @@ -36,16 +35,22 @@ import javax.inject.Inject class MediaProjectionMetricsLogger @Inject constructor(private val service: IMediaProjectionManager) { + /** * Request to log that the permission was requested. * + * @param hostUid The UID of the package that initiates MediaProjection. * @param sessionCreationSource The entry point requesting permission to capture. */ - fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) { - notifyToServer( - MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED, - sessionCreationSource - ) + fun notifyProjectionInitiated(hostUid: Int, sessionCreationSource: SessionCreationSource) { + try { + service.notifyPermissionRequestInitiated( + hostUid, + sessionCreationSource.toMetricsConstant() + ) + } catch (e: RemoteException) { + Log.e(TAG, "Error notifying server of projection initiated", e) + } } fun notifyPermissionRequestDisplayed() { diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index 2d830d3115625152d1cf832c2f17172e7ba3bd92..922f7e0aabaf7c38180db39b09da4693775ddc0f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -140,7 +140,7 @@ public class MediaProjectionPermissionActivity extends Activity if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) { if (savedInstanceState == null) { mMediaProjectionMetricsLogger.notifyProjectionInitiated( - SessionCreationSource.APP); + mUid, SessionCreationSource.APP); } final IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName, @@ -242,6 +242,7 @@ public class MediaProjectionPermissionActivity extends Activity if (savedInstanceState == null) { mMediaProjectionMetricsLogger.notifyProjectionInitiated( + mUid, appName == null ? SessionCreationSource.CAST : SessionCreationSource.APP); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index 1b0d5f9a9fdf39bd81a5f1a963b4c11b46029b5b..2f8fe42672c21899ae1f12391370573a3245dafe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -32,7 +32,6 @@ import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -43,6 +42,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 com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -55,7 +55,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS private final KeyguardStateController mKeyguard; protected IndividualSensorPrivacyController mSensorPrivacyController; - private final SafetyCenterManager mSafetyCenterManager; + private final Boolean mIsSafetyCenterEnabled; /** * @return Id of the sensor that will be toggled @@ -89,7 +89,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS statusBarStateController, activityStarter, qsLogger); mSensorPrivacyController = sensorPrivacyController; mKeyguard = keyguardStateController; - mSafetyCenterManager = safetyCenterManager; + mIsSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled(); mSensorPrivacyController.observe(getLifecycle(), this); } @@ -138,7 +138,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS @Override public Intent getLongClickIntent() { - if (mSafetyCenterManager.isSafetyCenterEnabled()) { + if (mIsSafetyCenterEnabled) { return new Intent(Settings.ACTION_PRIVACY_CONTROLS); } else { return new Intent(Settings.ACTION_PRIVACY_SETTINGS); diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index f469c6b78e871460132f26c45a9a820772347748..f602da47e585bb8055e383854753c9a74b5f3179 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -155,6 +155,7 @@ public class RecordingController } mMediaProjectionMetricsLogger.notifyProjectionInitiated( + mUserContextProvider.getUserContext().getUserId(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 1ecb127f0c9268036aeef40036918bf3b1f31ba1..ead10d6da860f042840ae7f58b13171283a7a29d 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -16,6 +16,7 @@ package com.android.systemui.settings.brightness; +import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY; @@ -83,7 +84,7 @@ public class BrightnessDialog extends Activity { private void setWindowAttributes() { final Window window = getWindow(); - window.setGravity(Gravity.TOP | Gravity.LEFT); + window.setGravity(Gravity.TOP | Gravity.START); window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); window.requestFeature(Window.FEATURE_NO_TITLE); @@ -130,13 +131,14 @@ public class BrightnessDialog extends Activity { Configuration configuration = getResources().getConfiguration(); int orientation = configuration.orientation; + int screenWidth = getWindowManager().getDefaultDisplay().getWidth(); if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2 - - lp.leftMargin * 2; + boolean shouldBeFullWidth = getIntent() + .getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false); + lp.width = (shouldBeFullWidth ? screenWidth : screenWidth / 2) - horizontalMargin * 2; } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { - lp.width = getWindowManager().getDefaultDisplay().getWidth() - - lp.leftMargin * 2; + lp.width = screenWidth - horizontalMargin * 2; } frame.setLayoutParams(lp); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 09514404d4dba63c5d3a5263ca27f63ea93db871..3d3447b921fc37a5c2504ad9f368b71336adf9b5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -42,10 +42,13 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.communal.data.repository.CommunalRepository; +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel; +import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.flags.Flags; import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; @@ -103,8 +106,10 @@ public class NotificationShadeWindowViewController implements Dumpable { private final PulsingGestureListener mPulsingGestureListener; private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener; private final NotificationInsetsController mNotificationInsetsController; + private final CommunalViewModel mCommunalViewModel; + private final CommunalRepository mCommunalRepository; private final boolean mIsTrackpadCommonEnabled; - private final FeatureFlags mFeatureFlags; + private final FeatureFlagsClassic mFeatureFlagsClassic; private final SysUIKeyEventHandler mSysUIKeyEventHandler; private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; private final AlternateBouncerInteractor mAlternateBouncerInteractor; @@ -175,8 +180,10 @@ public class NotificationShadeWindowViewController implements Dumpable { KeyguardMessageAreaController.Factory messageAreaControllerFactory, KeyguardTransitionInteractor keyguardTransitionInteractor, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, + CommunalViewModel communalViewModel, + CommunalRepository communalRepository, NotificationExpansionRepository notificationExpansionRepository, - FeatureFlags featureFlags, + FeatureFlagsClassic featureFlagsClassic, SystemClock clock, BouncerMessageInteractor bouncerMessageInteractor, BouncerLogger bouncerLogger, @@ -206,8 +213,10 @@ public class NotificationShadeWindowViewController implements Dumpable { mPulsingGestureListener = pulsingGestureListener; mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener; mNotificationInsetsController = notificationInsetsController; - mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON); - mFeatureFlags = featureFlags; + mCommunalViewModel = communalViewModel; + mCommunalRepository = communalRepository; + mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON); + mFeatureFlagsClassic = featureFlagsClassic; mSysUIKeyEventHandler = sysUIKeyEventHandler; mPrimaryBouncerInteractor = primaryBouncerInteractor; mAlternateBouncerInteractor = alternateBouncerInteractor; @@ -223,7 +232,7 @@ public class NotificationShadeWindowViewController implements Dumpable { messageAreaControllerFactory, bouncerMessageInteractor, bouncerLogger, - featureFlags, + featureFlagsClassic, selectedUserInteractor); collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), @@ -234,7 +243,7 @@ public class NotificationShadeWindowViewController implements Dumpable { this::setExpandAnimationRunning); mClock = clock; - if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) { + if (featureFlagsClassic.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) { unfoldTransitionProgressProvider.ifPresent( progressProvider -> progressProvider.addCallback( mDisableSubpixelTextTransitionListener)); @@ -263,7 +272,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(), mPulsingGestureListener); - if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) { + if (mFeatureFlagsClassic.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) { mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(), mLockscreenHostedDreamGestureListener); } @@ -435,7 +444,7 @@ public class NotificationShadeWindowViewController implements Dumpable { } boolean bouncerShowing; - if (mFeatureFlags.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) { + if (mFeatureFlagsClassic.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) { bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing() || mAlternateBouncerInteractor.isVisibleState(); } else { @@ -447,7 +456,7 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mDragDownHelper.isDragDownEnabled()) { // This handles drag down over lockscreen boolean result = mDragDownHelper.onInterceptTouchEvent(ev); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { if (result) { mLastInterceptWasDragDownHelper = true; if (ev.getAction() == MotionEvent.ACTION_DOWN) { @@ -479,7 +488,7 @@ public class NotificationShadeWindowViewController implements Dumpable { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); mStackScrollLayout.onInterceptTouchEvent(cancellation); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { mNotificationPanelViewController.handleExternalInterceptTouch(cancellation); } cancellation.recycle(); @@ -494,7 +503,7 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarKeyguardViewManager.onTouch(ev)) { return true; } - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { // we still want to finish our drag down gesture when locking the screen handled |= mDragDownHelper.onTouchEvent(ev) || handled; @@ -559,8 +568,28 @@ public class NotificationShadeWindowViewController implements Dumpable { mDepthController.onPanelExpansionChanged(currentState); } + /** + * Sets up the communal hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB} flag + * is enabled. + * + * The layout lives in {@link R.id.communal_ui_container}. + */ + public void setupCommunalHubLayout() { + if (!mCommunalRepository.isCommunalEnabled() + || !ComposeFacade.INSTANCE.isComposeAvailable()) { + return; + } + + // Replace the placeholder view with the communal UI. + View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub); + int index = mView.indexOfChild(communalPlaceholder); + mView.removeView(communalPlaceholder); + mView.addView(ComposeFacade.INSTANCE.createCommunalContainer(mView.getContext(), + mCommunalViewModel), index); + } + private boolean didNotificationPanelInterceptEvent(MotionEvent ev) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need // to also ask NotificationPanelViewController directly, in order to process swipe up // events originating from notifications diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index a8a20cc8559b9e19183cef11ab871c8260f06230..2f684762a13a8144af2b3f88af4d0ee4f1d98670 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -35,7 +35,6 @@ import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository import com.android.systemui.user.domain.interactor.UserSwitcherInteractor -import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope @@ -140,13 +139,6 @@ constructor( /** Whether either the shade or QS is fully expanded. */ val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged() - /** Whether either the shade or QS is expanding from a fully collapsed state. */ - val isAnyExpanding: Flow<Boolean> = - anyExpansion - .pairwise(1f) - .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f } - .distinctUntilChanged() - /** * Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At * this time, this is not simply a matter of checking if either value in shadeExpansion and diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java b/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java deleted file mode 100644 index f1eb9febfb33459c7f8ed6604d4d2261d90c1252..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2014 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; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; - -/** - * A view who contains media artwork. - */ -public class BackDropView extends FrameLayout -{ - private Runnable mOnVisibilityChangedRunnable; - - public BackDropView(Context context) { - super(context); - } - - public BackDropView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BackDropView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public BackDropView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - @Override - protected void onVisibilityChanged(View changedView, int visibility) { - super.onVisibilityChanged(changedView, visibility); - if (changedView == this && mOnVisibilityChangedRunnable != null) { - mOnVisibilityChangedRunnable.run(); - } - } - - public void setOnVisibilityChangedRunnable(Runnable runnable) { - mOnVisibilityChangedRunnable = runnable; - } - -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index f32f1c2dcd2510baec9cd6396520ab68c550957c..710e59d91c6b92018a34ef66ca90f9431fb8b555 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_ import static android.os.UserHandle.USER_NULL; import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; +import static android.os.Flags.allowPrivateProfile; import static com.android.systemui.DejankUtils.whitelistIpcs; @@ -79,6 +80,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; +import java.util.Objects; import javax.inject.Inject; @@ -177,57 +179,50 @@ public class NotificationLockscreenUserManagerImpl implements @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - switch (action) { - case Intent.ACTION_USER_REMOVED: - int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (removedUserId != -1) { - for (UserChangedListener listener : mListeners) { - listener.onUserRemoved(removedUserId); - } - } - updateCurrentProfilesCache(); - break; - case Intent.ACTION_USER_ADDED: - updateCurrentProfilesCache(); - if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); - mBackgroundHandler.post(() -> { - initValuesForUser(userId); - }); - } - break; - case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: - case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: - updateCurrentProfilesCache(); - break; - case Intent.ACTION_USER_UNLOCKED: - // Start the overview connection to the launcher service - // Connect if user hasn't connected yet - if (mOverviewProxyServiceLazy.get().getProxy() == null) { - mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); - } - break; - case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION: - final IntentSender intentSender = intent.getParcelableExtra( - Intent.EXTRA_INTENT); - final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); - if (intentSender != null) { - try { - ActivityOptions options = ActivityOptions.makeBasic(); - options.setPendingIntentBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - mContext.startIntentSender(intentSender, null, 0, 0, 0, - options.toBundle()); - } catch (IntentSender.SendIntentException e) { - /* ignore */ - } + if (Objects.equals(action, Intent.ACTION_USER_REMOVED)) { + int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (removedUserId != -1) { + for (UserChangedListener listener : mListeners) { + listener.onUserRemoved(removedUserId); } - if (notificationKey != null) { - final NotificationVisibility nv = mVisibilityProviderLazy.get() - .obtain(notificationKey, true); - mClickNotifier.onNotificationClick(notificationKey, nv); + } + updateCurrentProfilesCache(); + } else if (Objects.equals(action, Intent.ACTION_USER_ADDED)){ + updateCurrentProfilesCache(); + if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); + mBackgroundHandler.post(() -> { + initValuesForUser(userId); + }); + } + } else if (profileAvailabilityActions(action)) { + updateCurrentProfilesCache(); + } else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) { + // Start the overview connection to the launcher service + // Connect if user hasn't connected yet + if (mOverviewProxyServiceLazy.get().getProxy() == null) { + mOverviewProxyServiceLazy.get().startConnectionToCurrentUser(); + } + } else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) { + final IntentSender intentSender = intent.getParcelableExtra( + Intent.EXTRA_INTENT); + final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); + if (intentSender != null) { + try { + ActivityOptions options = ActivityOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + mContext.startIntentSender(intentSender, null, 0, 0, 0, + options.toBundle()); + } catch (IntentSender.SendIntentException e) { + /* ignore */ } - break; + } + if (notificationKey != null) { + final NotificationVisibility nv = mVisibilityProviderLazy.get() + .obtain(notificationKey, true); + mClickNotifier.onNotificationClick(notificationKey, nv); + } } } }; @@ -403,6 +398,10 @@ public class NotificationLockscreenUserManagerImpl implements filter.addAction(Intent.ACTION_USER_UNLOCKED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + if (allowPrivateProfile()){ + filter.addAction(Intent.ACTION_PROFILE_AVAILABLE); + filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE); + } mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter, null /* executor */, UserHandle.ALL); @@ -814,6 +813,14 @@ public class NotificationLockscreenUserManagerImpl implements } } + private boolean profileAvailabilityActions(String action){ + return allowPrivateProfile()? + Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)|| + Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE): + Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)|| + Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + } + @Override public void dump(PrintWriter pw, String[] args) { pw.println("NotificationLockscreenUserManager state:"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 389486f0ada37c07ad3fb276b1ef92404a775685..9c4625e91110d2bdb9120d5d98ccd4cf7a71e642 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -18,31 +18,21 @@ package com.android.systemui.statusbar; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; -import android.app.WallpaperManager; import android.content.Context; -import android.graphics.Point; import android.graphics.drawable.Icon; -import android.hardware.display.DisplayManager; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; -import android.os.Trace; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.util.Log; -import android.view.Display; -import android.view.View; -import android.widget.ImageView; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.models.player.MediaData; import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData; import com.android.systemui.media.controls.pipeline.MediaDataManager; -import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.dagger.CentralSurfacesModule; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -50,21 +40,13 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.HashSet; -import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; /** * Handles tasks and state related to media notifications. For example, there is a 'current' media @@ -74,9 +56,6 @@ public class NotificationMediaManager implements Dumpable { private static final String TAG = "NotificationMediaManager"; public static final boolean DEBUG_MEDIA = false; - private final StatusBarStateController mStatusBarStateController; - private final SysuiColorExtractor mColorExtractor; - private final KeyguardStateController mKeyguardStateController; private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>(); private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>(); static { @@ -93,15 +72,6 @@ public class NotificationMediaManager implements Dumpable { private final NotifPipeline mNotifPipeline; private final NotifCollection mNotifCollection; - @Nullable - private BiometricUnlockController mBiometricUnlockController; - @Nullable - private ScrimController mScrimController; - @Nullable - private LockscreenWallpaper mLockscreenWallpaper; - @VisibleForTesting - boolean mIsLockscreenLiveWallpaperEnabled; - private final Context mContext; private final ArrayList<MediaListener> mMediaListeners; @@ -110,16 +80,6 @@ public class NotificationMediaManager implements Dumpable { private String mMediaNotificationKey; private MediaMetadata mMediaMetadata; - private BackDropView mBackdrop; - private ImageView mBackdropFront; - private ImageView mBackdropBack; - private final Point mTmpDisplaySize = new Point(); - - private final DisplayManager mDisplayManager; - @Nullable - private List<String> mSmallerInternalDisplayUids; - private Display mCurrentDisplay; - private final MediaController.Callback mMediaListener = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { @@ -142,7 +102,7 @@ public class NotificationMediaManager implements Dumpable { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } mMediaMetadata = metadata; - dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); + dispatchUpdateMediaMetaData(); } }; @@ -155,23 +115,13 @@ public class NotificationMediaManager implements Dumpable { NotifPipeline notifPipeline, NotifCollection notifCollection, MediaDataManager mediaDataManager, - StatusBarStateController statusBarStateController, - SysuiColorExtractor colorExtractor, - KeyguardStateController keyguardStateController, - DumpManager dumpManager, - WallpaperManager wallpaperManager, - DisplayManager displayManager) { + DumpManager dumpManager) { mContext = context; mMediaListeners = new ArrayList<>(); mVisibilityProvider = visibilityProvider; mMediaDataManager = mediaDataManager; mNotifPipeline = notifPipeline; mNotifCollection = notifCollection; - mStatusBarStateController = statusBarStateController; - mColorExtractor = colorExtractor; - mKeyguardStateController = keyguardStateController; - mDisplayManager = displayManager; - mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled(); setupNotifPipeline(); @@ -275,7 +225,7 @@ public class NotificationMediaManager implements Dumpable { public void onNotificationRemoved(String key) { if (key.equals(mMediaNotificationKey)) { clearCurrentMediaNotification(); - dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */); + dispatchUpdateMediaMetaData(); } } @@ -309,21 +259,18 @@ public class NotificationMediaManager implements Dumpable { } public void findAndUpdateMediaNotifications() { - boolean metaDataChanged; // TODO(b/169655907): get the semi-filtered notifications for current user Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs(); - metaDataChanged = findPlayingMediaNotification(allNotifications); - dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */); + findPlayingMediaNotification(allNotifications); + dispatchUpdateMediaMetaData(); } /** * Find a notification and media controller associated with the playing media session, and * update this manager's internal state. - * @return whether the current MediaMetadata changed (and needs to be announced to listeners). + * TODO(b/273443374) check this method */ - boolean findPlayingMediaNotification( - @NonNull Collection<NotificationEntry> allNotifications) { - boolean metaDataChanged = false; + void findPlayingMediaNotification(@NonNull Collection<NotificationEntry> allNotifications) { // Promote the media notification with a controller in 'playing' state, if any. NotificationEntry mediaNotification = null; MediaController controller = null; @@ -359,8 +306,6 @@ public class NotificationMediaManager implements Dumpable { Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: " + mMediaController + ", receive metadata: " + mMediaMetadata); } - - metaDataChanged = true; } if (mediaNotification != null @@ -371,8 +316,6 @@ public class NotificationMediaManager implements Dumpable { + mMediaNotificationKey); } } - - return metaDataChanged; } public void clearCurrentMediaNotification() { @@ -380,10 +323,7 @@ public class NotificationMediaManager implements Dumpable { clearCurrentMediaNotificationSession(); } - private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) { - if (mPresenter != null) { - mPresenter.updateMediaMetaData(changed, allowEnterAnimation); - } + private void dispatchUpdateMediaMetaData() { @PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController); ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners); for (int i = 0; i < callbacks.size(); i++) { @@ -446,125 +386,6 @@ public class NotificationMediaManager implements Dumpable { mMediaController = null; } - /** - * Notify lockscreen wallpaper drawable the current internal display. - */ - public void onDisplayUpdated(Display display) { - Trace.beginSection("NotificationMediaManager#onDisplayUpdated"); - mCurrentDisplay = display; - Trace.endSection(); - } - - private boolean isOnSmallerInternalDisplays() { - if (mSmallerInternalDisplayUids == null) { - mSmallerInternalDisplayUids = findSmallerInternalDisplayUids(); - } - return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId()); - } - - private List<String> findSmallerInternalDisplayUids() { - if (mSmallerInternalDisplayUids != null) { - return mSmallerInternalDisplayUids; - } - List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays( - DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) - .filter(display -> display.getType() == Display.TYPE_INTERNAL) - .collect(Collectors.toList()); - if (internalDisplays.isEmpty()) { - return List.of(); - } - Display largestDisplay = internalDisplays.stream() - .max(Comparator.comparingInt(this::getRealDisplayArea)) - .orElse(internalDisplays.get(0)); - internalDisplays.remove(largestDisplay); - return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList()); - } - - private int getRealDisplayArea(Display display) { - display.getRealSize(mTmpDisplaySize); - return mTmpDisplaySize.x * mTmpDisplaySize.y; - } - - /** - * Update media state of lockscreen media views and controllers . - */ - public void updateMediaMetaData(boolean metaDataChanged) { - - if (mIsLockscreenLiveWallpaperEnabled) return; - - Trace.beginSection("CentralSurfaces#updateMediaMetaData"); - if (getBackDropView() == null) { - Trace.endSection(); - return; // called too early - } - - boolean wakeAndUnlock = mBiometricUnlockController != null - && mBiometricUnlockController.isWakeAndUnlock(); - if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) { - mBackdrop.setVisibility(View.INVISIBLE); - Trace.endSection(); - return; - } - - MediaMetadata mediaMetadata = getMediaMetadata(); - - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " - + getMediaNotificationKey() - + " metadata=" + mediaMetadata - + " metaDataChanged=" + metaDataChanged - + " state=" + mStatusBarStateController.getState()); - } - - mColorExtractor.setHasMediaArtwork(false); - if (mScrimController != null) { - mScrimController.setHasBackdrop(false); - } - - Trace.endSection(); - } - - public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, - ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) { - mBackdrop = backdrop; - mBackdropFront = backdropFront; - mBackdropBack = backdropBack; - mScrimController = scrimController; - mLockscreenWallpaper = lockscreenWallpaper; - } - - public void setBiometricUnlockController(BiometricUnlockController biometricUnlockController) { - mBiometricUnlockController = biometricUnlockController; - } - - /** - * Hide the album artwork that is fading out and release its bitmap. - */ - protected final Runnable mHideBackdropFront = new Runnable() { - @Override - public void run() { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: removing fade layer"); - } - mBackdropFront.setVisibility(View.INVISIBLE); - mBackdropFront.animate().cancel(); - mBackdropFront.setImageDrawable(null); - } - }; - - // TODO(b/273443374): remove - public boolean isLockscreenWallpaperOnNotificationShade() { - return mBackdrop != null && mLockscreenWallpaper != null - && !mLockscreenWallpaper.isLockscreenLiveWallpaperEnabled() - && (mBackdropFront.isVisibleToUser() || mBackdropBack.isVisibleToUser()); - } - - // TODO(b/273443374) temporary test helper; remove - @VisibleForTesting - BackDropView getBackDropView() { - return mBackdrop; - } - public interface MediaListener { /** * Called whenever there's new metadata or playback state. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index 5dcf6d1d8f283d3ca16dbe2541752f58cd9f3388..f3b5ab6968a0cfab2e6475d460b7215fbc504917 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -31,11 +31,6 @@ public interface NotificationPresenter extends ExpandableNotificationRow.OnExpan */ boolean isPresenterFullyCollapsed(); - /** - * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. - */ - void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation); - /** * Called when the current user changes. * @param newUserId new user id diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index 17da015789ea0ea64ffbb2c90884f039bcf5ecac..ffde8c03682fce777ea445029bfaeeb279956ee4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -118,16 +118,27 @@ public class RemoteInputController { // If the view is being removed, this may be called even though we're not active boolean remoteInputActiveForEntry = isRemoteInputActive(entry); + boolean remoteInputActive = isRemoteInputActive(); mLogger.logRemoveRemoteInput( entry.getKey() /* entryKey */, entry.mRemoteEditImeVisible /* remoteEditImeVisible */, entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */, remoteInputActiveForEntry /* isRemoteInputActiveForEntry */, - isRemoteInputActive()/* isRemoteInputActive */, + remoteInputActive/* isRemoteInputActive */, reason/* reason */, entry.getNotificationStyle()/* notificationStyle */); - if (!remoteInputActiveForEntry) return; + if (!remoteInputActiveForEntry) { + if (mLastAppliedRemoteInputActive != null + && mLastAppliedRemoteInputActive + && !remoteInputActive) { + mLogger.logRemoteInputApplySkipped( + entry.getKey() /* entryKey */, + reason/* reason */, + entry.getNotificationStyle()/* notificationStyle */); + } + return; + } pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token); 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 125c8efe18849668181920677811ebcb88ec7133..1fe6b83b47b1ead64ce327cefa958d0eaa1b4d16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -16,9 +16,7 @@ package com.android.systemui.statusbar.dagger; -import android.app.WallpaperManager; import android.content.Context; -import android.hardware.display.DisplayManager; import android.os.RemoteException; import android.service.dreams.IDreamManager; import android.util.Log; @@ -29,7 +27,6 @@ import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.AnimationFeatureFlags; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; @@ -121,24 +118,14 @@ public interface CentralSurfacesDependenciesModule { NotifPipeline notifPipeline, NotifCollection notifCollection, MediaDataManager mediaDataManager, - StatusBarStateController statusBarStateController, - SysuiColorExtractor colorExtractor, - KeyguardStateController keyguardStateController, - DumpManager dumpManager, - WallpaperManager wallpaperManager, - DisplayManager displayManager) { + DumpManager dumpManager) { return new NotificationMediaManager( context, visibilityProvider, notifPipeline, notifCollection, mediaDataManager, - statusBarStateController, - colorExtractor, - keyguardStateController, - dumpManager, - wallpaperManager, - displayManager); + dumpManager); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt index bde298d7a33d4ebe909e104d8e28fd1e350274fa..ea1d7820c79c8391f54cfbe79513a3dd714a533a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt @@ -36,6 +36,11 @@ interface StatusEvent { val showAnimation: Boolean val viewCreator: ViewCreator var contentDescription: String? + /** + * When true, an accessibility event with [contentDescription] is announced when the view + * becomes visible. + */ + val shouldAnnounceAccessibilityEvent: Boolean // Update this event with values from another event. fun updateFromEvent(other: StatusEvent?) { @@ -76,6 +81,7 @@ class BatteryEvent(@IntRange(from = 0, to = 100) val batteryLevel: Int) : Status override var forceVisible = false override val showAnimation = true override var contentDescription: String? = "" + override val shouldAnnounceAccessibilityEvent: Boolean = false override val viewCreator: ViewCreator = { context -> BatteryStatusChip(context).apply { @@ -95,6 +101,7 @@ class ConnectedDisplayEvent : StatusEvent { override var forceVisible = false override val showAnimation = true override var contentDescription: String? = "" + override val shouldAnnounceAccessibilityEvent: Boolean = true override val viewCreator: ViewCreator = { context -> ConnectedDisplayChip(context) @@ -110,6 +117,7 @@ open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEven override var contentDescription: String? = null override val priority = 100 override var forceVisible = true + override val shouldAnnounceAccessibilityEvent: Boolean = false var privacyItems: List<PrivacyItem> = listOf() private var privacyChip: OngoingPrivacyChip? = null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt index dccc23f61092cdbab47cf67bb312b6b849712b34..73c0bfec69a1d3135a02ec224d8e076f893e1927 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -273,6 +273,11 @@ class SystemEventChipAnimationController @Inject constructor( }) } + /** Announces [contentDescriptions] for accessibility. */ + fun announceForAccessibility(contentDescriptions: String) { + currentAnimatedView?.view?.announceForAccessibility(contentDescriptions) + } + private fun updateDimens(contentArea: Rect) { val lp = animationWindowView.layoutParams as FrameLayout.LayoutParams lp.height = contentArea.height() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt index 7d866df73da97842580a9ba9acc7e21d720d1833..8ee1ade7a185812b6d6fd4cc4d8114c73a899d45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt @@ -100,9 +100,12 @@ constructor( } private fun startConnectedDisplayCollection() { + val connectedDisplayEvent = ConnectedDisplayEvent().apply { + contentDescription = context.getString(R.string.connected_display_icon_desc) + } connectedDisplayCollectionJob = onDisplayConnectedFlow - .onEach { scheduler.onStatusEvent(ConnectedDisplayEvent()) } + .onEach { scheduler.onStatusEvent(connectedDisplayEvent) } .launchIn(appScope) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt index a3bc00248362d59c92d777abd1281e195173f2f4..f0e60dd2ce54fbf10373c058dcef13cb0cdfda10 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt @@ -254,11 +254,18 @@ constructor( currentlyRunningAnimationJob = coroutineScope.launch { runChipAppearAnimation() + announceForAccessibilityIfNeeded(event) delay(APPEAR_ANIMATION_DURATION + DISPLAY_LENGTH) runChipDisappearAnimation() } } + private fun announceForAccessibilityIfNeeded(event: StatusEvent) { + val description = event.contentDescription ?: return + if (!event.shouldAnnounceAccessibilityEvent) return + chipAnimationController.announceForAccessibility(description) + } + /** * 1. Define a total budget for the chip animation (1500ms) * 2. Send out callbacks to listeners so that they can generate animations locally diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java index 57d20246ea14e8a82ca21d2d3e2c9a45f2c558ab..f98f39e3532cd71e50023cc0b7bd59a998d084e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; +import android.util.Log; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; @@ -33,6 +34,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; * An animator to animate properties */ public class PropertyAnimator { + private static final String TAG = "PropertyAnimator"; /** * Set a property on a view, updating its value, even if it's already animating. @@ -114,18 +116,23 @@ public class PropertyAnimator { || previousAnimator.getAnimatedFraction() == 0)) { animator.setStartDelay(properties.delay); } - if (listener != null) { - animator.addListener(listener); - } // remove the tag when the animation is finished animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - view.setTag(animatorTag, null); - view.setTag(animationStartTag, null); - view.setTag(animationEndTag, null); + Animator existing = (Animator) view.getTag(animatorTag); + if (existing == animation) { + view.setTag(animatorTag, null); + view.setTag(animationStartTag, null); + view.setTag(animationEndTag, null); + } else { + Log.e(TAG, "Unexpected Animator set during onAnimationEnd. Not cleaning up."); + } } }); + if (listener != null) { + animator.addListener(listener); + } ViewState.startAnimator(animator, listener); view.setTag(animatorTag, animator); view.setTag(animationStartTag, currentValue); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt index ff89c62ab49605420942c7e91c945e2bf3eb721d..23f87ba9dcc9468305731ed1c1b6d9d7d1426af3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt @@ -83,6 +83,21 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) { } ) + fun logRemoteInputApplySkipped(entryKey: String, reason: String, notificationStyle: String) = + logBuffer.log( + TAG, + DEBUG, + { + str1 = entryKey + str2 = reason + str3 = notificationStyle + }, + { + "removeRemoteInput[apply is skipped] reason: $str2" + + "for entry: $str1, style: $str3 " + } + ) + private companion object { private const val TAG = "RemoteInputControllerLog" } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 4573d5989faae09d80172ab9e8b5aba8d1ea9311..cfe9fbe3af296594a414a79e526f1cc4d9779301 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -748,7 +748,11 @@ public final class NotificationEntry extends ListEntry { return row != null && row.getGuts() != null && row.getGuts().isExposed(); } - public boolean isChildInGroup() { + /** + * @return Whether the notification row is a child of a group notification view; false if the + * row is null + */ + public boolean rowIsChildInGroup() { return row != null && row.isChildInGroup(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt new file mode 100644 index 0000000000000000000000000000000000000000..94e70e5521c32019a61bb90d93b9f329cd76810e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt @@ -0,0 +1,47 @@ +/* + * 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.footer.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the FooterView refactor flag state. */ +@Suppress("NOTHING_TO_INLINE") +object FooterViewRefactor { + const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationsFooterViewRefactor() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 59f10aed414530e211a769b16af292809c3832c3..daa4f1807625572a1079ff1aebd034a2bdfacbc5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -511,7 +511,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp case MODE_WAKE_AND_UNLOCK: if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) { Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING"); - mMediaManager.updateMediaMetaData(false /* metaDataChanged */); } else if (mMode == MODE_WAKE_AND_UNLOCK){ Trace.beginSection("MODE_WAKE_AND_UNLOCK"); } else { 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 9fb6c1bb2fd0606c95c86b86ef7c273d7f29bedb..7cd32f9774220d186284def79e838794cb4caf6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -76,7 +76,6 @@ import android.util.DisplayMetrics; import android.util.EventLog; import android.util.IndentingPrintWriter; import android.util.Log; -import android.util.MathUtils; import android.view.Display; import android.view.IRemoteAnimationRunner; import android.view.IWindowManager; @@ -176,7 +175,6 @@ import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.statusbar.AutoHideUiElement; -import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CircleReveal; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.GestureRecorder; @@ -237,10 +235,10 @@ import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.startingsurface.SplashscreenContentDrawer; import com.android.wm.shell.startingsurface.StartingSurface; -import dagger.Lazy; - import dalvik.annotation.optimization.NeverCompile; +import dagger.Lazy; + import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; @@ -376,9 +374,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private boolean mBrightnessMirrorVisible; private BiometricUnlockController mBiometricUnlockController; private final LightBarController mLightBarController; - private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy; - @Nullable - protected LockscreenWallpaper mLockscreenWallpaper; private final AutoHideController mAutoHideController; private final Point mCurrentDisplaySize = new Point(); @@ -658,7 +653,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { NotificationExpansionRepository notificationExpansionRepository, DozeParameters dozeParameters, ScrimController scrimController, - Lazy<LockscreenWallpaper> lockscreenWallpaperLazy, Lazy<BiometricUnlockController> biometricUnlockControllerLazy, AuthRippleController authRippleController, DozeServiceHost dozeServiceHost, @@ -770,7 +764,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mPowerManager = powerManager; mDozeParameters = dozeParameters; mScrimController = scrimController; - mLockscreenWallpaperLazy = lockscreenWallpaperLazy; mDozeScrimController = dozeScrimController; mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; mAuthRippleController = authRippleController; @@ -1198,10 +1191,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { createNavigationBar(result); - if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) { - mLockscreenWallpaper = mLockscreenWallpaperLazy.get(); - } - mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById( R.id.ambient_indication_container); @@ -1268,24 +1257,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShelfController, mHeadsUpManager); - BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop); - if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - mMediaManager.setup(null, null, null, mScrimController, null); - } else { - mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front), - backdrop.findViewById(R.id.backdrop_back), mScrimController, - mLockscreenWallpaper); - } - float maxWallpaperZoom = mContext.getResources().getFloat( - com.android.internal.R.dimen.config_wallpaperMaxScale); - mNotificationShadeDepthControllerLazy.get().addListener(depth -> { - float scale = MathUtils.lerp(maxWallpaperZoom, 1f, depth); - backdrop.setPivotX(backdrop.getWidth() / 2f); - backdrop.setPivotY(backdrop.getHeight() / 2f); - backdrop.setScaleX(scale); - backdrop.setScaleY(scale); - }); - // Set up the quick settings tile panel final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame); if (container != null) { @@ -1357,14 +1328,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // receive broadcasts registerBroadcastReceiver(); - IntentFilter demoFilter = new IntentFilter(); - if (DEBUG_MEDIA_FAKE_ARTWORK) { - demoFilter.addAction(ACTION_FAKE_ARTWORK); - } - mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter, - android.Manifest.permission.DUMP, null, - Context.RECEIVER_EXPORTED_UNAUDITED); - // listen for USER_SETUP_COMPLETE setting (per-user) mDeviceProvisionedController.addCallback(mUserSetupObserver); mUserSetupObserver.onUserSetupChanged(); @@ -1525,6 +1488,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // regressions, we'll continue standing up the root view in CentralSurfaces. mNotificationShadeWindowController.fetchWindowRootView(); getNotificationShadeWindowViewController().setupExpandedStatusBar(); + getNotificationShadeWindowViewController().setupCommunalHubLayout(); mShadeController.setNotificationShadeWindowViewController( getNotificationShadeWindowViewController()); mBackActionInteractor.setup(mQsController, mShadeSurface); @@ -1582,7 +1546,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager); mLightBarController.setBiometricUnlockController(mBiometricUnlockController); - mMediaManager.setBiometricUnlockController(mBiometricUnlockController); Trace.endSection(); } @@ -1869,7 +1832,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { void updateDisplaySize() { mDisplay.getMetrics(mDisplayMetrics); mDisplay.getSize(mCurrentDisplaySize); - mMediaManager.onDisplayUpdated(mDisplay); if (DEBUG_GESTURES) { mGestureRec.tag("display", String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels)); @@ -1943,19 +1905,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } }; - private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (DEBUG) Log.v(TAG, "onReceive: " + intent); - String action = intent.getAction(); - if (ACTION_FAKE_ARTWORK.equals(action)) { - if (DEBUG_MEDIA_FAKE_ARTWORK) { - mPresenterLazy.get().updateMediaMetaData(true, true); - } - } - } - }; - /** * Reload some of our resources when the configuration changes. * @@ -2138,7 +2087,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { releaseGestureWakeLock(); runLaunchTransitionEndRunnable(); mKeyguardStateController.setLaunchTransitionFadingAway(false); - mPresenterLazy.get().updateMediaMetaData(true /* metaDataChanged */, true); } /** @@ -2162,7 +2110,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { beforeFading.run(); } updateScrimController(); - mPresenterLazy.get().updateMediaMetaData(false, true); mShadeSurface.resetAlpha(); mShadeSurface.fadeOut( FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION, @@ -3221,8 +3168,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { updateDozingState(); checkBarModes(); updateScrimController(); - mPresenterLazy.get() - .updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 6b4382f731eab9cbd18f22a622ff2f51cd114004..f4862c73606f1ba9f6b2f67487ee76c9ac0fb1d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -175,7 +175,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp if (!hasPinnedHeadsUp() || topEntry == null) { return null; } else { - if (topEntry.isChildInGroup()) { + if (topEntry.rowIsChildInGroup()) { final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(topEntry); if (groupSummary != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 2960520f00b412caa5f5f6605a8d989cd60ff288..2206be5e614bb85bb3a7381e9bac10ab99a3bcc6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -546,8 +546,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat * (1.0f - mKeyguardHeadsUpShowingAmount); } - if (mSystemEventAnimator.isAnimationRunning() - && !mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) { + if (mSystemEventAnimator.isAnimationRunning()) { newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha); } else { mView.setTranslationX(0); @@ -704,21 +703,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private StatusBarSystemEventDefaultAnimator getSystemEventAnimator(boolean isAnimationRunning) { return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> { - // TODO(b/273443374): remove if-else condition - if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) { - mSystemEventAnimatorAlpha = alpha; - } else { - mSystemEventAnimatorAlpha = 1f; - } + mSystemEventAnimatorAlpha = alpha; updateViewState(); return Unit.INSTANCE; }, (translationX) -> { - // TODO(b/273443374): remove if-else condition - if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) { - mView.setTranslationX(translationX); - } else { - mView.setTranslationX(0); - } + mView.setTranslationX(translationX); return Unit.INSTANCE; }, isAnimationRunning); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java deleted file mode 100644 index 00fd9fbfffe3135fd3d1a4e36379120ca7885d80..0000000000000000000000000000000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.phone; - -import android.annotation.Nullable; -import android.app.IWallpaperManager; -import android.app.IWallpaperManagerCallback; -import android.app.WallpaperColors; -import android.app.WallpaperManager; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Rect; -import android.graphics.Xfermode; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.DrawableWrapper; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.internal.util.IndentingPrintWriter; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.CoreStartable; -import com.android.systemui.Dumpable; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.user.data.model.SelectedUserModel; -import com.android.systemui.user.data.model.SelectionStatus; -import com.android.systemui.user.data.repository.UserRepository; -import com.android.systemui.util.kotlin.JavaAdapter; - -import libcore.io.IoUtils; - -import java.io.PrintWriter; -import java.util.Objects; - -import javax.inject.Inject; - -/** - * Manages the lockscreen wallpaper. - */ -@SysUISingleton -public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable, - Dumpable, CoreStartable { - - private static final String TAG = "LockscreenWallpaper"; - - // TODO(b/253507223): temporary; remove this - private static final String DISABLED_ERROR_MESSAGE = "Methods from LockscreenWallpaper.java " - + "should not be called in this version. The lock screen wallpaper should be " - + "managed by the WallpaperManagerService and not by this class."; - - private final NotificationMediaManager mMediaManager; - private final WallpaperManager mWallpaperManager; - private final KeyguardUpdateMonitor mUpdateMonitor; - private final Handler mH; - private final JavaAdapter mJavaAdapter; - private final UserRepository mUserRepository; - - private boolean mCached; - private Bitmap mCache; - private int mCurrentUserId; - // The user selected in the UI, or null if no user is selected or UI doesn't support selecting - // users. - private UserHandle mSelectedUser; - private AsyncTask<Void, Void, LoaderResult> mLoader; - - @Inject - public LockscreenWallpaper(WallpaperManager wallpaperManager, - @Nullable IWallpaperManager iWallpaperManager, - KeyguardUpdateMonitor keyguardUpdateMonitor, - DumpManager dumpManager, - NotificationMediaManager mediaManager, - @Main Handler mainHandler, - JavaAdapter javaAdapter, - UserRepository userRepository, - UserTracker userTracker) { - dumpManager.registerDumpable(getClass().getSimpleName(), this); - mWallpaperManager = wallpaperManager; - mCurrentUserId = userTracker.getUserId(); - mUpdateMonitor = keyguardUpdateMonitor; - mMediaManager = mediaManager; - mH = mainHandler; - mJavaAdapter = javaAdapter; - mUserRepository = userRepository; - - if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - // Service is disabled on some devices like Automotive - try { - iWallpaperManager.setLockWallpaperCallback(this); - } catch (RemoteException e) { - Log.e(TAG, "System dead?" + e); - } - } - } - - @Override - public void start() { - if (!isLockscreenLiveWallpaperEnabled()) { - mJavaAdapter.alwaysCollectFlow( - mUserRepository.getSelectedUser(), this::setSelectedUser); - } - } - - public Bitmap getBitmap() { - assertLockscreenLiveWallpaperNotEnabled(); - - if (mCached) { - return mCache; - } - if (!mWallpaperManager.isWallpaperSupported()) { - mCached = true; - mCache = null; - return null; - } - - LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser); - if (result.success) { - mCached = true; - mCache = result.bitmap; - } - return mCache; - } - - public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) { - // May be called on any thread - only use thread safe operations. - - assertLockscreenLiveWallpaperNotEnabled(); - - - if (!mWallpaperManager.isWallpaperSupported()) { - // When wallpaper is not supported, show the system wallpaper - return LoaderResult.success(null); - } - - // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK - // wallpaper. - final int lockWallpaperUserId = - selectedUser != null ? selectedUser.getIdentifier() : currentUserId; - ParcelFileDescriptor fd = mWallpaperManager.getWallpaperFile( - WallpaperManager.FLAG_LOCK, lockWallpaperUserId); - - if (fd != null) { - try { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Bitmap.Config.HARDWARE; - return LoaderResult.success(BitmapFactory.decodeFileDescriptor( - fd.getFileDescriptor(), null, options)); - } catch (OutOfMemoryError e) { - Log.w(TAG, "Can't decode file", e); - return LoaderResult.fail(); - } finally { - IoUtils.closeQuietly(fd); - } - } else { - if (selectedUser != null) { - // Show the selected user's static wallpaper. - return LoaderResult.success(mWallpaperManager.getBitmapAsUser( - selectedUser.getIdentifier(), true /* hardware */)); - - } else { - // When there is no selected user, show the system wallpaper - return LoaderResult.success(null); - } - } - } - - private void setSelectedUser(SelectedUserModel selectedUserModel) { - assertLockscreenLiveWallpaperNotEnabled(); - - if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) { - // Wait until the selection has finished before updating. - return; - } - - int user = selectedUserModel.getUserInfo().id; - if (user != mCurrentUserId) { - if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) { - mCached = false; - } - mCurrentUserId = user; - } - } - - public void setSelectedUser(UserHandle selectedUser) { - assertLockscreenLiveWallpaperNotEnabled(); - - if (Objects.equals(selectedUser, mSelectedUser)) { - return; - } - mSelectedUser = selectedUser; - postUpdateWallpaper(); - } - - @Override - public void onWallpaperChanged() { - assertLockscreenLiveWallpaperNotEnabled(); - // Called on Binder thread. - postUpdateWallpaper(); - } - - @Override - public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) { - assertLockscreenLiveWallpaperNotEnabled(); - } - - private void postUpdateWallpaper() { - assertLockscreenLiveWallpaperNotEnabled(); - if (mH == null) { - Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization."); - return; - } - mH.removeCallbacks(this); - mH.post(this); - } - @Override - public void run() { - // Called in response to onWallpaperChanged on the main thread. - - assertLockscreenLiveWallpaperNotEnabled(); - - if (mLoader != null) { - mLoader.cancel(false /* interrupt */); - } - - final int currentUser = mCurrentUserId; - final UserHandle selectedUser = mSelectedUser; - mLoader = new AsyncTask<Void, Void, LoaderResult>() { - @Override - protected LoaderResult doInBackground(Void... params) { - return loadBitmap(currentUser, selectedUser); - } - - @Override - protected void onPostExecute(LoaderResult result) { - super.onPostExecute(result); - if (isCancelled()) { - return; - } - if (result.success) { - mCached = true; - mCache = result.bitmap; - mMediaManager.updateMediaMetaData(true /* metaDataChanged */); - } - mLoader = null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - // TODO(b/273443374): remove - public boolean isLockscreenLiveWallpaperEnabled() { - return mWallpaperManager.isLockscreenLiveWallpaperEnabled(); - } - - @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - pw.println(getClass().getSimpleName() + ":"); - IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " ").increaseIndent(); - iPw.println("mCached=" + mCached); - iPw.println("mCache=" + mCache); - iPw.println("mCurrentUserId=" + mCurrentUserId); - iPw.println("mSelectedUser=" + mSelectedUser); - } - - private static class LoaderResult { - public final boolean success; - public final Bitmap bitmap; - - LoaderResult(boolean success, Bitmap bitmap) { - this.success = success; - this.bitmap = bitmap; - } - - static LoaderResult success(Bitmap b) { - return new LoaderResult(true, b); - } - - static LoaderResult fail() { - return new LoaderResult(false, null); - } - } - - /** - * Drawable that aligns left horizontally and center vertically (like ImageWallpaper). - * - * <p>Aligns to the center when showing on the smaller internal display of a multi display - * device. - */ - public static class WallpaperDrawable extends DrawableWrapper { - - private final ConstantState mState; - private final Rect mTmpRect = new Rect(); - private boolean mIsOnSmallerInternalDisplays; - - public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) { - this(r, new ConstantState(b), isOnSmallerInternalDisplays); - } - - private WallpaperDrawable(Resources r, ConstantState state, - boolean isOnSmallerInternalDisplays) { - super(new BitmapDrawable(r, state.mBackground)); - mState = state; - mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays; - } - - @Override - public void setXfermode(@Nullable Xfermode mode) { - // DrawableWrapper does not call this for us. - getDrawable().setXfermode(mode); - } - - @Override - public int getIntrinsicWidth() { - return -1; - } - - @Override - public int getIntrinsicHeight() { - return -1; - } - - @Override - protected void onBoundsChange(Rect bounds) { - int vwidth = getBounds().width(); - int vheight = getBounds().height(); - int dwidth = mState.mBackground.getWidth(); - int dheight = mState.mBackground.getHeight(); - float scale; - float dx = 0, dy = 0; - - if (dwidth * vheight > vwidth * dheight) { - scale = (float) vheight / (float) dheight; - } else { - scale = (float) vwidth / (float) dwidth; - } - - if (scale <= 1f) { - scale = 1f; - } - dy = (vheight - dheight * scale) * 0.5f; - - int offsetX = 0; - // Offset to show the center area of the wallpaper on a smaller display for multi - // display device - if (mIsOnSmallerInternalDisplays) { - offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2); - } - - mTmpRect.set( - bounds.left + offsetX, - bounds.top + Math.round(dy), - bounds.left + Math.round(dwidth * scale) + offsetX, - bounds.top + Math.round(dheight * scale + dy)); - - super.onBoundsChange(mTmpRect); - } - - @Override - public ConstantState getConstantState() { - return mState; - } - - /** - * Update bounds when the hosting display or the display size has changed. - * - * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal - * displays with the smaller area. - */ - public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) { - mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays; - onBoundsChange(getBounds()); - } - - static class ConstantState extends Drawable.ConstantState { - - private final Bitmap mBackground; - - ConstantState(Bitmap background) { - mBackground = background; - } - - @Override - public Drawable newDrawable() { - return newDrawable(null); - } - - @Override - public Drawable newDrawable(@Nullable Resources res) { - return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false); - } - - @Override - public int getChangingConfigurations() { - // DrawableWrapper already handles this for us. - return 0; - } - } - } - - /** - * Feature b/253507223 will adapt the logic to always use the - * WallpaperManagerService to render the lock screen wallpaper. - * Methods of this class should not be called at all if the project flag is enabled. - * TODO(b/253507223) temporary assertion; remove this - */ - private void assertLockscreenLiveWallpaperNotEnabled() { - if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - throw new IllegalStateException(DISABLED_ERROR_MESSAGE); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 54d81b83197eeca7ec65c9fd202855bdd61de81b..5a8b636e54fcfcefdc02620c3ca2401e845aca44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -300,7 +300,8 @@ public class PhoneStatusBarPolicy mIconController.setIconVisibility(mSlotCast, false); // connected display - mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null); + mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, + mResources.getString(R.string.connected_display_icon_desc)); mIconController.setIconVisibility(mSlotConnectedDisplay, false); // hotspot diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 5b552640f3974ae871cc127c722aa14398961523..744d70e36972c151ab660e6f75ac6ff4534705c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER; +import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE; +import static com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import static java.lang.Float.isNaN; @@ -51,7 +54,6 @@ import com.android.settingslib.Utils; import com.android.systemui.CoreStartable; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; import com.android.systemui.dagger.SysUISingleton; @@ -62,7 +64,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.ScrimAlpha; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; +import com.android.systemui.res.R; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; @@ -273,6 +277,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private CoroutineDispatcher mMainDispatcher; private boolean mIsBouncerToGoneTransitionRunning = false; private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; + private AlternateBouncerToGoneTransitionViewModel mAlternateBouncerToGoneTransitionViewModel; private final Consumer<ScrimAlpha> mScrimAlphaConsumer = (ScrimAlpha alphas) -> { mInFrontAlpha = alphas.getFrontAlpha(); @@ -285,7 +290,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScrimBehind.setViewAlpha(mBehindAlpha); }; - Consumer<TransitionStep> mPrimaryBouncerToGoneTransition; + Consumer<TransitionStep> mBouncerToGoneTransition; @Inject public ScrimController( @@ -304,6 +309,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump KeyguardUnlockAnimationController keyguardUnlockAnimationController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, + AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel, KeyguardTransitionInteractor keyguardTransitionInteractor, WallpaperRepository wallpaperRepository, @Main CoroutineDispatcher mainDispatcher, @@ -349,6 +355,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump }); mColors = new GradientColors(); mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; + mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mWallpaperRepository = wallpaperRepository; mMainDispatcher = mainDispatcher; @@ -405,7 +412,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump // Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure // to report back that keyguard has faded away. This fixes cases where the scrim state was // rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl - mPrimaryBouncerToGoneTransition = + mBouncerToGoneTransition = (TransitionStep step) -> { TransitionState state = step.getTransitionState(); @@ -425,10 +432,17 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } }; - collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(), - mPrimaryBouncerToGoneTransition, mMainDispatcher); + // PRIMARY_BOUNCER->GONE + collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(PRIMARY_BOUNCER, GONE), + mBouncerToGoneTransition, mMainDispatcher); collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(), mScrimAlphaConsumer, mMainDispatcher); + + // ALTERNATE_BOUNCER->GONE + collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, GONE), + mBouncerToGoneTransition, mMainDispatcher); + collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(), + mScrimAlphaConsumer, mMainDispatcher); } // TODO(b/270984686) recompute scrim height accurately, based on shade contents. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 90fddd9ae22c0a2652d4bb6ae37b0d715f9a4638..267b56378d82b0de7fc04ba99d31242ef1b9fed5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -964,9 +964,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); } - if (isShowing) { - mMediaManager.updateMediaMetaData(false); - } mNotificationShadeWindowController.setKeyguardOccluded(isOccluded); // setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 57a8e6fe0d91b795817a26ba8f52466248d4672d..a5cd062f6f37b00e310f25c412293cb0d8ec1c3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -205,7 +205,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu // End old BaseStatusBar.userSwitched mCommandQueue.animateCollapsePanels(); mMediaManager.clearCurrentMediaNotification(); - updateMediaMetaData(true, false); } @Override @@ -219,11 +218,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu return mNotificationPanel.isFullyCollapsed(); } - @Override - public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { - mMediaManager.updateMediaMetaData(metaDataChanged); - } - @Override public void onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt index 85fd2afed9ec46bdc89fcc73ead6aeb2a849d38e..d7f3b07675903327f477a631f71c9ef1ca320f71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt @@ -17,42 +17,71 @@ package com.android.systemui.statusbar.phone import android.app.Dialog import android.content.Context +import android.content.res.Configuration import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.Gravity -import android.view.WindowManager +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.WindowManager.LayoutParams.MATCH_PARENT +import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION +import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS +import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener /** A dialog shown as a bottom sheet. */ open class SystemUIBottomSheetDialog( context: Context, - theme: Int = R.style.Theme_SystemUI_Dialog, + private val configurationController: ConfigurationController? = null, + theme: Int = R.style.Theme_SystemUI_Dialog ) : Dialog(context, theme) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setupWindow() + setupEdgeToEdge() + setCanceledOnTouchOutside(true) + } + private fun setupWindow() { window?.apply { - setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) - addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) - + setType(TYPE_STATUS_BAR_SUB_PANEL) + addPrivateFlags(SYSTEM_FLAG_SHOW_FOR_ALL_USERS or PRIVATE_FLAG_NO_MOVE_ANIMATION) setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) setGravity(Gravity.BOTTOM) - val edgeToEdgeHorizontally = - context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog) - if (edgeToEdgeHorizontally) { - decorView.setPadding(0, 0, 0, 0) - setLayout( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.WRAP_CONTENT - ) + decorView.setPadding(0, 0, 0, 0) + attributes = + attributes.apply { + fitInsetsSides = 0 + horizontalMargin = 0f + } + } + } + + private fun setupEdgeToEdge() { + val edgeToEdgeHorizontally = + context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog) + val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT + val height = WRAP_CONTENT + window?.setLayout(width, height) + } - val lp = attributes - lp.fitInsetsSides = 0 - lp.horizontalMargin = 0f - attributes = lp + override fun onStart() { + super.onStart() + configurationController?.addCallback(onConfigChanged) + } + + override fun onStop() { + super.onStop() + configurationController?.removeCallback(onConfigChanged) + } + + private val onConfigChanged = + object : ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + super.onConfigChanged(newConfig) + setupEdgeToEdge() } } - setCanceledOnTouchOutside(true) - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 9d627af357cb407729efce0a49ab6c2468920bd4..2558645e3f649a9922c60d463926ebaf2597631a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -45,6 +45,7 @@ import com.android.systemui.Dependency; import com.android.systemui.res.R; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; @@ -54,8 +55,14 @@ import com.android.systemui.util.DialogKt; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + /** - * Base class for dialogs that should appear over panels and keyguard. + * Class for dialogs that should appear over panels and keyguard. + * + * DO NOT SUBCLASS THIS. See {@link SystemUIDialog.Delegate} for an interface that enables + * customizing behavior via composition instead of inheritance. Clients should implement the + * Delegate class and then pass their implementation into the SystemUIDialog constructor. * * Optionally provide a {@link SystemUIDialogManager} to its constructor to send signals to * listeners on whether this dialog is showing. @@ -72,6 +79,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private final Context mContext; private final FeatureFlags mFeatureFlags; + private final Delegate mDelegate; @Nullable private final DismissReceiver mDismissReceiver; private final Handler mHandler = new Handler(); private final SystemUIDialogManager mDialogManager; @@ -101,18 +109,102 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh Dependency.get(SystemUIDialogManager.class), Dependency.get(SysUiState.class), Dependency.get(BroadcastDispatcher.class), - Dependency.get(DialogLaunchAnimator.class)); + Dependency.get(DialogLaunchAnimator.class), + new Delegate() {}); } - public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock, + @Inject + public SystemUIDialog( + @Application Context context, + FeatureFlags featureFlags, + SystemUIDialogManager systemUIDialogManager, + SysUiState sysUiState, + BroadcastDispatcher broadcastDispatcher, + DialogLaunchAnimator dialogLaunchAnimator) { + this(context, + DEFAULT_THEME, + DEFAULT_DISMISS_ON_DEVICE_LOCK, + featureFlags, + systemUIDialogManager, + sysUiState, + broadcastDispatcher, + dialogLaunchAnimator, + new Delegate(){}); + } + + public static class Factory { + private final Context mContext; + private final FeatureFlags mFeatureFlags; + private final SystemUIDialogManager mSystemUIDialogManager; + private final SysUiState mSysUiState; + private final BroadcastDispatcher mBroadcastDispatcher; + private final DialogLaunchAnimator mDialogLaunchAnimator; + + @Inject + public Factory( + @Application Context context, + FeatureFlags featureFlags, + SystemUIDialogManager systemUIDialogManager, + SysUiState sysUiState, + BroadcastDispatcher broadcastDispatcher, + DialogLaunchAnimator dialogLaunchAnimator) { + mContext = context; + mFeatureFlags = featureFlags; + mSystemUIDialogManager = systemUIDialogManager; + mSysUiState = sysUiState; + mBroadcastDispatcher = broadcastDispatcher; + mDialogLaunchAnimator = dialogLaunchAnimator; + } + + public SystemUIDialog create(Delegate delegate) { + return new SystemUIDialog( + mContext, + DEFAULT_THEME, + DEFAULT_DISMISS_ON_DEVICE_LOCK, + mFeatureFlags, + mSystemUIDialogManager, + mSysUiState, + mBroadcastDispatcher, + mDialogLaunchAnimator, + delegate); + } + } + + public SystemUIDialog( + Context context, + int theme, + boolean dismissOnDeviceLock, FeatureFlags featureFlags, SystemUIDialogManager dialogManager, SysUiState sysUiState, BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator) { + this( + context, + theme, + dismissOnDeviceLock, + featureFlags, + dialogManager, + sysUiState, + broadcastDispatcher, + dialogLaunchAnimator, + new Delegate(){}); + } + + public SystemUIDialog( + Context context, + int theme, + boolean dismissOnDeviceLock, + FeatureFlags featureFlags, + SystemUIDialogManager dialogManager, + SysUiState sysUiState, + BroadcastDispatcher broadcastDispatcher, + DialogLaunchAnimator dialogLaunchAnimator, + Delegate delegate) { super(context, theme); mContext = context; mFeatureFlags = featureFlags; + mDelegate = delegate; applyFlags(this); WindowManager.LayoutParams attrs = getWindow().getAttributes(); @@ -127,7 +219,9 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh @Override protected void onCreate(Bundle savedInstanceState) { + mDelegate.beforeCreate(this, savedInstanceState); super.onCreate(savedInstanceState); + mDelegate.onCreate(this, savedInstanceState); Configuration config = getContext().getResources().getConfiguration(); mLastConfigurationWidthDp = config.screenWidthDp; @@ -172,6 +266,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh updateWindowSize(); } + + mDelegate.onConfigurationChanged(this, configuration); } /** @@ -212,7 +308,9 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh * Called when {@link #onStart} is called. Subclasses wishing to override {@link #onStart()} * should override this method instead. */ - protected void start() {} + protected void start() { + mDelegate.start(this); + } @Override protected final void onStop() { @@ -234,7 +332,15 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh * Called when {@link #onStop} is called. Subclasses wishing to override {@link #onStop()} * should override this method instead. */ - protected void stop() {} + protected void stop() { + mDelegate.stop(this); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + mDelegate.onWindowFocusChanged(this, hasFocus); + } public void setShowForAllUsers(boolean show) { setShowForAllUsers(this, show); @@ -353,7 +459,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh registerDismissListener(dialog, null); } - /** * Registers a listener that dismisses the given dialog when it receives * the screen off / close system dialogs broadcast. @@ -480,4 +585,42 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh } } + /** + * A delegate class that should be implemented in place of sublcassing {@link SystemUIDialog}. + * + * Implement this interface and then pass an instance of your implementation to + * {@link SystemUIDialog.Factory#create(Delegate)}. + */ + public interface Delegate { + /** + * Called before {@link AlertDialog#onCreate} is called. + */ + default void beforeCreate(SystemUIDialog dialog, Bundle savedInstanceState) {} + + /** + * Called after {@link AlertDialog#onCreate} is called. + */ + default void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {} + + /** + * Called after {@link AlertDialog#onStart} is called. + */ + default void start(SystemUIDialog dialog) {} + + /** + * Called after {@link AlertDialog#onStop} is called. + */ + default void stop(SystemUIDialog dialog) {} + + /** + * Called after {@link AlertDialog#onWindowFocusChanged(boolean)} is called. + */ + default void onWindowFocusChanged(SystemUIDialog dialog, boolean hasFocus) {} + + /** + * Called as part of + * {@link ViewRootImpl.ConfigChangedCallback#onConfigurationChanged(Configuration)}. + */ + default void onConfigurationChanged(SystemUIDialog dialog, Configuration configuration) {} + } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt index 9269df31e37ed408f2516b0f318d421bc19ad78c..8c66c2f0fab34c656f63a2fdba024c372820d3f1 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt @@ -19,6 +19,7 @@ package com.android.systemui.unfold import android.content.ContentResolver import android.content.Context import android.hardware.devicestate.DeviceStateManager +import android.os.Trace import android.util.Log import com.android.internal.util.LatencyTracker import com.android.systemui.dagger.SysUISingleton @@ -57,6 +58,7 @@ constructor( private var folded: Boolean? = null private var isTransitionEnabled: Boolean? = null private val foldStateListener = FoldStateListener(context) + private var unfoldInProgress = false private val isFoldable: Boolean get() = context.resources @@ -95,7 +97,7 @@ constructor( // the unfold animation (e.g. it could be disabled because of battery saver). // When animation is enabled finishing of the tracking will be done in onTransitionStarted. if (folded == false && isTransitionEnabled == false) { - latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD) + onUnfoldEnded() if (DEBUG) { Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD") @@ -116,7 +118,7 @@ constructor( } if (folded == false && isTransitionEnabled == true) { - latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD) + onUnfoldEnded() if (DEBUG) { Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD") @@ -124,6 +126,22 @@ constructor( } } + private fun onUnfoldStarted() { + if (unfoldInProgress) return + unfoldInProgress = true + // As LatencyTracker might be disabled, let's also log a parallel slice to the trace to be + // able to debug all cases. + latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD) + Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, UNFOLD_IN_PROGRESS_TRACE_NAME, /* cookie= */ 0) + } + + private fun onUnfoldEnded() { + if (!unfoldInProgress) return + unfoldInProgress = false + latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD) + Trace.endAsyncSection(UNFOLD_IN_PROGRESS_TRACE_NAME, 0) + } + private fun onFoldEvent(folded: Boolean) { val oldFolded = this.folded @@ -139,7 +157,7 @@ constructor( // unfolding the device. if (oldFolded != null && !folded) { // Unfolding started - latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD) + onUnfoldStarted() isTransitionEnabled = transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled() @@ -159,4 +177,5 @@ constructor( } private const val TAG = "UnfoldLatencyTracker" +private const val UNFOLD_IN_PROGRESS_TRACE_NAME = "Switch displays during unfold" private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE) diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt new file mode 100644 index 0000000000000000000000000000000000000000..ed960f31228a35d864afe5f923d9e50784808e75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt @@ -0,0 +1,75 @@ +/* + * 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.unfold + +import android.content.Context +import android.os.Trace +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.unfold.system.DeviceStateRepository +import com.android.systemui.unfold.updates.FoldStateRepository +import com.android.systemui.util.TraceStateLogger +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * Logs several unfold related details in a trace. Mainly used for debugging and investigate + * droidfooders traces. + */ +@SysUISingleton +class UnfoldTraceLogger +@Inject +constructor( + private val context: Context, + private val foldStateRepository: FoldStateRepository, + @Application private val applicationScope: CoroutineScope, + private val deviceStateRepository: DeviceStateRepository +) : CoreStartable { + private val isFoldable: Boolean + get() = + context.resources + .getIntArray(com.android.internal.R.array.config_foldedDeviceStates) + .isNotEmpty() + + override fun start() { + if (!isFoldable) return + + applicationScope.launch { + val foldUpdateLogger = TraceStateLogger("FoldUpdate") + foldStateRepository.foldUpdate.collect { foldUpdateLogger.log(it.name) } + } + + applicationScope.launch { + foldStateRepository.hingeAngle.collect { + Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt()) + } + } + applicationScope.launch { + val foldedStateLogger = TraceStateLogger("FoldedState") + deviceStateRepository.isFolded.collect { isFolded -> + foldedStateLogger.log( + if (isFolded) { + "folded" + } else { + "unfolded" + } + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index ed3eacd27c0ac0e68e7efe9ca70a5d7e2cd11e39..71314f1f17759049b4c47b37620c436ec88df593 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.unfold import android.content.Context import android.hardware.devicestate.DeviceStateManager import android.os.SystemProperties +import com.android.systemui.CoreStartable import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.LifecycleScreenStatusProvider @@ -34,16 +35,26 @@ import com.android.systemui.unfold.util.UnfoldOnlyProgressProvider import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix import com.android.systemui.util.time.SystemClockImpl import com.android.wm.shell.unfold.ShellUnfoldProgressProvider +import dagger.Binds import dagger.Lazy import dagger.Module import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import java.util.Optional import java.util.concurrent.Executor import javax.inject.Named import javax.inject.Provider import javax.inject.Singleton -@Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class]) +@Module( + includes = + [ + UnfoldSharedModule::class, + SystemUnfoldSharedModule::class, + UnfoldTransitionModule.Bindings::class + ] +) class UnfoldTransitionModule { @Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui" @@ -136,13 +147,22 @@ class UnfoldTransitionModule { null } - return resultingProvider?.get()?.orElse(null)?.let { - unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider) - } ?: ShellUnfoldProgressProvider.NO_PROVIDER + return resultingProvider?.get()?.orElse(null)?.let { unfoldProgressProvider -> + UnfoldProgressProvider(unfoldProgressProvider, foldProvider) + } + ?: ShellUnfoldProgressProvider.NO_PROVIDER } @Provides fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl + + @Module + interface Bindings { + @Binds + @IntoMap + @ClassKey(UnfoldTraceLogger::class) + fun bindUnfoldTraceLogger(impl: UnfoldTraceLogger): CoreStartable + } } const val UNFOLD_STATUS_BAR = "unfold_status_bar" diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt index 83ff789808801d1a4f19e7b0891277a70b6afbfb..b3834f58be2f865fff83d72a576d3b9998f4d5c1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt @@ -269,3 +269,108 @@ inline fun <T> CoroutineScope.stateFlow( crossinline getValue: () -> T, ): StateFlow<T> = changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue()) + +inline fun <T1, T2, T3, T4, T5, T6, R> combine( + flow: Flow<T1>, + flow2: Flow<T2>, + flow3: Flow<T3>, + flow4: Flow<T4>, + flow5: Flow<T5>, + flow6: Flow<T6>, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R +): Flow<R> { + return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { + args: Array<*> -> + @Suppress("UNCHECKED_CAST") + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6 + ) + } +} + +inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine( + flow: Flow<T1>, + flow2: Flow<T2>, + flow3: Flow<T3>, + flow4: Flow<T4>, + flow5: Flow<T5>, + flow6: Flow<T6>, + flow7: Flow<T7>, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R +): Flow<R> { + return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { + args: Array<*> -> + @Suppress("UNCHECKED_CAST") + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + args[6] as T7 + ) + } +} + +inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine( + flow: Flow<T1>, + flow2: Flow<T2>, + flow3: Flow<T3>, + flow4: Flow<T4>, + flow5: Flow<T5>, + flow6: Flow<T6>, + flow7: Flow<T7>, + flow8: Flow<T8>, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R +): Flow<R> { + return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { + args: Array<*> -> + @Suppress("UNCHECKED_CAST") + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + args[6] as T7, + args[7] as T8 + ) + } +} + +inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine( + flow: Flow<T1>, + flow2: Flow<T2>, + flow3: Flow<T3>, + flow4: Flow<T4>, + flow5: Flow<T5>, + flow6: Flow<T6>, + flow7: Flow<T7>, + flow8: Flow<T8>, + flow9: Flow<T9>, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R +): Flow<R> { + return kotlinx.coroutines.flow.combine( + flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9 + ) { args: Array<*> -> + @Suppress("UNCHECKED_CAST") + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + args[6] as T7, + args[6] as T8, + args[6] as T9, + ) + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index aea3030967d281ef23fa9c5fd680a46e548836e0..fdf5966419b44eea7ce0a6c891caea2b214b0cc2 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java @@ -126,8 +126,6 @@ public class ImageWallpaper extends WallpaperService { private int mBitmapUsages = 0; private final Object mLock = new Object(); - private boolean mIsLockscreenLiveWallpaperEnabled; - CanvasEngine() { super(); setFixedSizeAllowed(true); @@ -171,12 +169,8 @@ public class ImageWallpaper extends WallpaperService { Log.d(TAG, "onCreate"); } mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class); - mIsLockscreenLiveWallpaperEnabled = mWallpaperManager - .isLockscreenLiveWallpaperEnabled(); mSurfaceHolder = surfaceHolder; - Rect dimensions = mIsLockscreenLiveWallpaperEnabled - ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true) - : mWallpaperManager.peekBitmapDimensions(); + Rect dimensions = mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true); int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width()); int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height()); mSurfaceHolder.setFixedSize(width, height); @@ -327,10 +321,8 @@ public class ImageWallpaper extends WallpaperService { boolean loadSuccess = false; Bitmap bitmap; try { - bitmap = mIsLockscreenLiveWallpaperEnabled - ? mWallpaperManager.getBitmapAsUser( - mUserTracker.getUserId(), false, getSourceFlag(), true) - : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false); + bitmap = mWallpaperManager.getBitmapAsUser( + mUserTracker.getUserId(), false, getSourceFlag(), true); if (bitmap != null && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) { throw new RuntimeException("Wallpaper is too large to draw!"); @@ -341,18 +333,11 @@ public class ImageWallpaper extends WallpaperService { // be loaded, we will go into a cycle. Don't do a build where the // default wallpaper can't be loaded. Log.w(TAG, "Unable to load wallpaper!", exception); - if (mIsLockscreenLiveWallpaperEnabled) { - mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId()); - } else { - mWallpaperManager.clearWallpaper( - WallpaperManager.FLAG_SYSTEM, mUserTracker.getUserId()); - } + mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId()); try { - bitmap = mIsLockscreenLiveWallpaperEnabled - ? mWallpaperManager.getBitmapAsUser( - mUserTracker.getUserId(), false, getSourceFlag(), true) - : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false); + bitmap = mWallpaperManager.getBitmapAsUser( + mUserTracker.getUserId(), false, getSourceFlag(), true); } catch (RuntimeException | OutOfMemoryError e) { Log.w(TAG, "Unable to load default wallpaper!", e); bitmap = null; @@ -373,9 +358,7 @@ public class ImageWallpaper extends WallpaperService { mBitmap.recycle(); } mBitmap = bitmap; - mWideColorGamut = mIsLockscreenLiveWallpaperEnabled - ? mWallpaperManager.wallpaperSupportsWcg(getSourceFlag()) - : mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM); + mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(getSourceFlag()); // +2 usages for the color extraction and the delayed unload. mBitmapUsages += 2; diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt index ea74510a907050a4aa87591532fdfd1f40edd72c..0b3c3b483d1c50fc1e62fbd1302b66c83a5242fa 100644 --- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt +++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt @@ -16,10 +16,12 @@ package com.android import android.content.Context +import android.content.res.Resources import com.android.systemui.FakeSystemUiModule import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main import dagger.Module import dagger.Provides @@ -36,6 +38,9 @@ class SysUITestModule { @Provides @Application fun provideAppContext(test: SysuiTestCase): Context = test.context + @Provides @Main + fun provideResources(@Application context: Context): Resources = context.resources + @Provides fun provideBroadcastDispatcher(test: SysuiTestCase): BroadcastDispatcher = test.fakeBroadcastDispatcher diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt index 167e3417c1624b5c234308a847b3ea624b0bc621..ff1d5b2ea3bfc3f2004c7b8bd8bd78ed1b564a3c 100644 --- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt +++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt @@ -22,7 +22,9 @@ import android.util.DisplayMetrics import com.android.internal.logging.MetricsLogger import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardViewController import com.android.systemui.GuestResumeSessionReceiver +import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.ScreenLifecycle @@ -31,6 +33,7 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.dagger.BroadcastDispatcherLog import com.android.systemui.log.dagger.SceneFrameworkLog import com.android.systemui.media.controls.ui.MediaHierarchyManager +import com.android.systemui.model.SysUiState import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -86,6 +89,9 @@ data class TestMocksModule( @get:Provides val statusBarStateController: SysuiStatusBarStateController = mock(), @get:Provides val statusBarWindowController: StatusBarWindowController = mock(), @get:Provides val wakefulnessLifecycle: WakefulnessLifecycle = mock(), + @get:Provides val keyguardViewController: KeyguardViewController = mock(), + @get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(), + @get:Provides val sysuiState: SysUiState = mock(), // log buffers @get:[Provides BroadcastDispatcherLog] diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index df7d1b7a4aadb2db06b397b9c4fd0a97e5d94cb0..aed795a440b48acac03eadd4797d3bee6e14a3b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -222,14 +222,14 @@ public class MenuViewLayerTest extends SysuiTestCase { @Test public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() { - final float menuTop = IME_TOP + 100; - mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); + mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); + final PointF beforePosition = mMenuView.getMenuPosition(); dispatchShowingImeInsets(); final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight(); - assertThat(mMenuView.getTranslationX()).isEqualTo(0); - assertThat(menuBottom).isLessThan(IME_TOP); + assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); + assertThat(menuBottom).isLessThan(beforePosition.y); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt deleted file mode 100644 index 215d63508306674c772647766d7a7058bb9b7460..0000000000000000000000000000000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt +++ /dev/null @@ -1,101 +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.biometrics - -import android.content.Context -import android.hardware.biometrics.SensorProperties -import android.hardware.fingerprint.FingerprintManager -import android.hardware.fingerprint.FingerprintSensorProperties -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal -import android.view.ViewGroup.LayoutParams -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.airbnb.lottie.LottieAnimationView -import com.android.systemui.res.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.`when` as whenEver -import org.mockito.junit.MockitoJUnit - -private const val SENSOR_ID = 1 - -@SmallTest -@RunWith(AndroidJUnit4::class) -class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() { - - @JvmField @Rule var mockitoRule = MockitoJUnit.rule() - - @Mock private lateinit var iconView: LottieAnimationView - @Mock private lateinit var iconViewOverlay: LottieAnimationView - @Mock private lateinit var layoutParam: LayoutParams - @Mock private lateinit var fingerprintManager: FingerprintManager - - private lateinit var controller: AuthBiometricFingerprintIconController - - @Before - fun setUp() { - context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager) - whenEver(iconView.layoutParams).thenReturn(layoutParam) - whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam) - } - - @Test - fun testIconContentDescription_SfpsDevice() { - setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON) - controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) - - assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING)) - .isEqualTo( - context.resources.getString( - R.string.security_settings_sfps_enroll_find_sensor_message - ) - ) - } - - @Test - fun testIconContentDescription_NonSfpsDevice() { - setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) - controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) - - assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING)) - .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor)) - } - - private fun setupFingerprintSensorProperties(sensorType: Int) { - whenEver(fingerprintManager.sensorPropertiesInternal) - .thenReturn( - listOf( - FingerprintSensorPropertiesInternal( - SENSOR_ID, - SensorProperties.STRENGTH_STRONG, - 5 /* maxEnrollmentsPerUser */, - listOf() /* componentInfo */, - sensorType, - true /* halControlsIllumination */, - true /* resetLockoutRequiresHardwareAuthToken */, - listOf() /* sensorLocations */ - ) - ) - ) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt index d68a3131da4c286e36be1017bb2bbe40b9728d10..8c26776a1ef8a67c29b8708272805adbc016790c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -72,6 +72,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { runCurrent() // WHEN shade expands + shadeRepository.setLegacyShadeTracking(true) shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() @@ -108,6 +109,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { // WHEN detector is disabled and shade opens detector.disable() + shadeRepository.setLegacyShadeTracking(true) shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt index 9f24a9f553a16cd66486ea3267b3ffaca9ab7338..15633d1baed1ce18894487d7ca6bd9ff72328247 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt @@ -30,6 +30,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal internal fun fingerprintSensorPropertiesInternal( ids: List<Int> = listOf(0), strong: Boolean = true, + sensorType: Int = FingerprintSensorProperties.TYPE_REAR ): List<FingerprintSensorPropertiesInternal> { val componentInfo = listOf( @@ -54,7 +55,7 @@ internal fun fingerprintSensorPropertiesInternal( if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */, componentInfo, - FingerprintSensorProperties.TYPE_REAR, + sensorType, false /* resetLockoutRequiresHardwareAuthToken */ ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt deleted file mode 100644 index fd86486aeff87bb71c1eebd16ffeae6dce9c6e5a..0000000000000000000000000000000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.android.systemui.biometrics.ui.viewmodel - -import android.content.res.Configuration -import androidx.test.filters.SmallTest -import com.android.internal.widget.LockPatternUtils -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository -import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository -import com.android.systemui.biometrics.data.repository.FakePromptRepository -import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor -import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl -import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor -import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl -import com.android.systemui.biometrics.shared.model.FingerprintSensorType -import com.android.systemui.biometrics.shared.model.SensorStrength -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.display.data.repository.FakeDisplayRepository -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.Mock -import org.mockito.junit.MockitoJUnit - -@OptIn(ExperimentalCoroutinesApi::class) -@SmallTest -@RunWith(JUnit4::class) -class PromptFingerprintIconViewModelTest : SysuiTestCase() { - - @JvmField @Rule var mockitoRule = MockitoJUnit.rule() - - @Mock private lateinit var lockPatternUtils: LockPatternUtils - - private lateinit var displayRepository: FakeDisplayRepository - private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository - private lateinit var promptRepository: FakePromptRepository - private lateinit var displayStateRepository: FakeDisplayStateRepository - - private val testScope = TestScope(StandardTestDispatcher()) - private val fakeExecutor = FakeExecutor(FakeSystemClock()) - - private lateinit var promptSelectorInteractor: PromptSelectorInteractor - private lateinit var displayStateInteractor: DisplayStateInteractor - private lateinit var viewModel: PromptFingerprintIconViewModel - - @Before - fun setup() { - displayRepository = FakeDisplayRepository() - fingerprintRepository = FakeFingerprintPropertyRepository() - promptRepository = FakePromptRepository() - displayStateRepository = FakeDisplayStateRepository() - - promptSelectorInteractor = - PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) - displayStateInteractor = - DisplayStateInteractorImpl( - testScope.backgroundScope, - mContext, - fakeExecutor, - displayStateRepository, - displayRepository, - ) - viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor) - } - - @Test - fun sfpsIconUpdates_onConfigurationChanged() { - testScope.runTest { - runCurrent() - configureFingerprintPropertyRepository(FingerprintSensorType.POWER_BUTTON) - val testConfig = Configuration() - val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1 - val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1 - val currentIcon = collectLastValue(viewModel.iconAsset) - - testConfig.smallestScreenWidthDp = folded - viewModel.onConfigurationChanged(testConfig) - val foldedIcon = currentIcon() - - testConfig.smallestScreenWidthDp = unfolded - viewModel.onConfigurationChanged(testConfig) - val unfoldedIcon = currentIcon() - - assertThat(foldedIcon).isNotEqualTo(unfoldedIcon) - } - } - - private fun configureFingerprintPropertyRepository(sensorType: FingerprintSensorType) { - fingerprintRepository.setProperties(0, SensorStrength.STRONG, sensorType, mapOf()) - } -} - -internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600 diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index ca6df4027ea9a15346eb99ef45d4a8bea490a2c2..b695a0ee1fa36dd8e0eaf4b752c8639b8ea6d6f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -16,8 +16,10 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.content.res.Configuration import android.hardware.biometrics.PromptInfo import android.hardware.face.FaceSensorPropertiesInternal +import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.view.HapticFeedbackConstants import android.view.MotionEvent @@ -36,12 +38,15 @@ import com.android.systemui.biometrics.faceSensorPropertiesInternal import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality -import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.toSensorStrength +import com.android.systemui.biometrics.shared.model.toSensorType import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -66,6 +71,7 @@ import org.mockito.junit.MockitoJUnit private const val USER_ID = 4 private const val CHALLENGE = 2L +private const val DELAY = 1000L @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -88,11 +94,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private lateinit var selector: PromptSelectorInteractor private lateinit var viewModel: PromptViewModel + private lateinit var iconViewModel: PromptIconViewModel private val featureFlags = FakeFeatureFlags() @Before fun setup() { fingerprintRepository = FakeFingerprintPropertyRepository() + testCase.fingerprint?.let { + fingerprintRepository.setProperties( + it.sensorId, + it.sensorStrength.toSensorStrength(), + it.sensorType.toSensorType(), + it.allLocations.associateBy { sensorLocationInternal -> + sensorLocationInternal.displayId + } + ) + } promptRepository = FakePromptRepository() displayStateRepository = FakeDisplayStateRepository() displayRepository = FakeDisplayRepository() @@ -110,6 +127,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa viewModel = PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags) + iconViewModel = viewModel.iconViewModel featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false) } @@ -123,7 +141,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val modalities by collectLastValue(viewModel.modalities) val message by collectLastValue(viewModel.message) val size by collectLastValue(viewModel.size) - val legacyState by collectLastValue(viewModel.legacyState) assertThat(authenticating).isFalse() assertThat(authenticated?.isNotAuthenticated).isTrue() @@ -133,7 +150,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } assertThat(message).isEqualTo(PromptMessage.Empty) assertThat(size).isEqualTo(expectedSize) - assertThat(legacyState).isEqualTo(BiometricState.STATE_IDLE) val startMessage = "here we go" viewModel.showAuthenticating(startMessage, isRetry = false) @@ -143,7 +159,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(authenticated?.isNotAuthenticated).isTrue() assertThat(size).isEqualTo(expectedSize) assertButtonsVisible(negative = expectedSize != PromptSize.SMALL) - assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING) } @Test @@ -205,6 +220,472 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT) } + @Test + fun start_idle_and_show_authenticating_iconUpdate() = + runGenericTest(doNotStart = true) { + val currentRotation by collectLastValue(displayStateInteractor.currentRotation) + val iconAsset by collectLastValue(iconViewModel.iconAsset) + val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId) + val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView) + + val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint + if (forceExplicitFlow) { + viewModel.ensureFingerprintHasStarted(isDelayed = true) + } + + val startMessage = "here we go" + viewModel.showAuthenticating(startMessage, isRetry = false) + + if (testCase.isFingerprintOnly) { + val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset) + val shouldAnimateIconOverlay by + collectLastValue(iconViewModel.shouldAnimateIconOverlay) + + if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + val expectedOverlayAsset = + when (currentRotation) { + DisplayRotation.ROTATION_0 -> + R.raw.biometricprompt_fingerprint_to_error_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft + DisplayRotation.ROTATION_180 -> + R.raw.biometricprompt_fingerprint_to_error_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright + else -> throw Exception("invalid rotation") + } + assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } else { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(false) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } + + if (testCase.isFaceOnly) { + val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation) + val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation) + val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark) + + val expectedIconAsset = + if (shouldPulseAnimation!!) { + if (lastPulseLightToDark!!) { + R.drawable.face_dialog_pulse_dark_to_light + } else { + R.drawable.face_dialog_pulse_light_to_dark + } + } else { + R.drawable.face_dialog_pulse_dark_to_light + } + assertThat(iconAsset).isEqualTo(expectedIconAsset) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldRepeatAnimation).isEqualTo(true) + } + + if (testCase.isCoex) { + if (testCase.confirmationRequested || forceExplicitFlow) { + // explicit flow + val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset) + val shouldAnimateIconOverlay by + collectLastValue(iconViewModel.shouldAnimateIconOverlay) + + // TODO: Update when SFPS co-ex is implemented + if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(false) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } else { + // implicit flow + val shouldRepeatAnimation by + collectLastValue(iconViewModel.shouldRepeatAnimation) + val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation) + val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark) + + val expectedIconAsset = + if (shouldPulseAnimation!!) { + if (lastPulseLightToDark!!) { + R.drawable.face_dialog_pulse_dark_to_light + } else { + R.drawable.face_dialog_pulse_light_to_dark + } + } else { + R.drawable.face_dialog_pulse_dark_to_light + } + assertThat(iconAsset).isEqualTo(expectedIconAsset) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldRepeatAnimation).isEqualTo(true) + } + } + } + + @Test + fun start_authenticating_show_and_clear_error_iconUpdate() = runGenericTest { + val currentRotation by collectLastValue(displayStateInteractor.currentRotation) + + val iconAsset by collectLastValue(iconViewModel.iconAsset) + val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId) + val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView) + + val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint + if (forceExplicitFlow) { + viewModel.ensureFingerprintHasStarted(isDelayed = true) + } + + val errorJob = launch { + viewModel.showTemporaryError( + "so sad", + messageAfterError = "", + authenticateAfterError = testCase.isFingerprintOnly || testCase.isCoex, + ) + // Usually done by binder + iconViewModel.setPreviousIconWasError(true) + iconViewModel.setPreviousIconOverlayWasError(true) + } + + if (testCase.isFingerprintOnly) { + val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset) + val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay) + + if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + val expectedOverlayAsset = + when (currentRotation) { + DisplayRotation.ROTATION_0 -> + R.raw.biometricprompt_fingerprint_to_error_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft + DisplayRotation.ROTATION_180 -> + R.raw.biometricprompt_fingerprint_to_error_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright + else -> throw Exception("invalid rotation") + } + assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset) + assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again) + assertThat(shouldAnimateIconOverlay).isEqualTo(true) + } else { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + + // Clear error, restart authenticating + errorJob.join() + + if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + val expectedOverlayAsset = + when (currentRotation) { + DisplayRotation.ROTATION_0 -> + R.raw.biometricprompt_symbol_error_to_fingerprint_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft + DisplayRotation.ROTATION_180 -> + R.raw.biometricprompt_symbol_error_to_fingerprint_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright + else -> throw Exception("invalid rotation") + } + assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) + assertThat(shouldAnimateIconOverlay).isEqualTo(true) + } else { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } + + if (testCase.isFaceOnly) { + val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation) + val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation) + + assertThat(shouldPulseAnimation!!).isEqualTo(false) + assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error) + assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldRepeatAnimation).isEqualTo(false) + + // Clear error, go to idle + errorJob.join() + + assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_error_to_idle) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.biometric_dialog_face_icon_description_idle) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldRepeatAnimation).isEqualTo(false) + } + + if (testCase.isCoex) { + val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset) + val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay) + + // TODO: Update when SFPS co-ex is implemented + if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + + // Clear error, restart authenticating + errorJob.join() + + if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } + } + + @Test + fun shows_authenticated_no_errors_no_confirmation_required_iconUpdate() = runGenericTest { + if (!testCase.confirmationRequested) { + val currentRotation by collectLastValue(displayStateInteractor.currentRotation) + + val iconAsset by collectLastValue(iconViewModel.iconAsset) + val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId) + val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView) + + viewModel.showAuthenticated( + modality = testCase.authenticatedModality, + dismissAfterDelay = DELAY + ) + + if (testCase.isFingerprintOnly) { + val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset) + val shouldAnimateIconOverlay by + collectLastValue(iconViewModel.shouldAnimateIconOverlay) + + if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + val expectedOverlayAsset = + when (currentRotation) { + DisplayRotation.ROTATION_0 -> + R.raw.biometricprompt_symbol_fingerprint_to_success_landscape + DisplayRotation.ROTATION_90 -> + R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft + DisplayRotation.ROTATION_180 -> + R.raw.biometricprompt_symbol_fingerprint_to_success_landscape + DisplayRotation.ROTATION_270 -> + R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright + else -> throw Exception("invalid rotation") + } + assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) + assertThat(shouldAnimateIconOverlay).isEqualTo(true) + } else { + val isAuthenticated by collectLastValue(viewModel.isAuthenticated) + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } + + // If co-ex, using implicit flow (explicit flow always requires confirmation) + if (testCase.isFaceOnly || testCase.isCoex) { + val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation) + val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation) + + assertThat(shouldPulseAnimation!!).isEqualTo(false) + assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldRepeatAnimation).isEqualTo(false) + } + } + } + + @Test + fun shows_pending_confirmation_iconUpdate() = runGenericTest { + if ( + (testCase.isFaceOnly || testCase.isCoex) && + testCase.authenticatedByFace && + testCase.confirmationRequested + ) { + val iconAsset by collectLastValue(iconViewModel.iconAsset) + val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId) + val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView) + + viewModel.showAuthenticated( + modality = testCase.authenticatedModality, + dismissAfterDelay = DELAY + ) + + if (testCase.isFaceOnly) { + val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation) + val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation) + + assertThat(shouldPulseAnimation!!).isEqualTo(false) + assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldRepeatAnimation).isEqualTo(false) + } + + // explicit flow because confirmation requested + if (testCase.isCoex) { + val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset) + val shouldAnimateIconOverlay by + collectLastValue(iconViewModel.shouldAnimateIconOverlay) + + // TODO: Update when SFPS co-ex is implemented + if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } + } + } + + @Test + fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest { + if ( + (testCase.isFaceOnly || testCase.isCoex) && + testCase.authenticatedByFace && + testCase.confirmationRequested + ) { + val iconAsset by collectLastValue(iconViewModel.iconAsset) + val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId) + val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView) + + viewModel.showAuthenticated( + modality = testCase.authenticatedModality, + dismissAfterDelay = DELAY + ) + + viewModel.confirmAuthenticated() + + if (testCase.isFaceOnly) { + val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation) + val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation) + + assertThat(shouldPulseAnimation!!).isEqualTo(false) + assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldRepeatAnimation).isEqualTo(false) + } + + // explicit flow because confirmation requested + if (testCase.isCoex) { + val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset) + val shouldAnimateIconOverlay by + collectLastValue(iconViewModel.shouldAnimateIconOverlay) + + // TODO: Update when SFPS co-ex is implemented + if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } + } + } + + @Test + fun sfpsIconUpdates_onConfigurationChanged() = runGenericTest { + if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + val testConfig = Configuration() + val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1 + val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1 + val currentIcon by collectLastValue(iconViewModel.iconAsset) + + testConfig.smallestScreenWidthDp = folded + iconViewModel.onConfigurationChanged(testConfig) + val foldedIcon = currentIcon + + testConfig.smallestScreenWidthDp = unfolded + iconViewModel.onConfigurationChanged(testConfig) + val unfoldedIcon = currentIcon + + assertThat(foldedIcon).isNotEqualTo(unfoldedIcon) + } + } + + @Test + fun sfpsIconUpdates_onRotation() = runGenericTest { + if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + val currentIcon by collectLastValue(iconViewModel.iconAsset) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0) + val iconRotation0 = currentIcon + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + val iconRotation90 = currentIcon + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + val iconRotation180 = currentIcon + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + val iconRotation270 = currentIcon + + assertThat(iconRotation0).isEqualTo(iconRotation180) + assertThat(iconRotation0).isNotEqualTo(iconRotation90) + assertThat(iconRotation0).isNotEqualTo(iconRotation270) + } + } + + @Test + fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest { + if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + val currentIcon by collectLastValue(iconViewModel.iconAsset) + + displayStateRepository.setIsInRearDisplayMode(false) + val iconNotRearDisplayMode = currentIcon + + displayStateRepository.setIsInRearDisplayMode(true) + val iconRearDisplayMode = currentIcon + + assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode) + } + } + private suspend fun TestScope.showAuthenticated( authenticatedModality: BiometricModality, expectConfirmation: Boolean, @@ -213,7 +694,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val authenticated by collectLastValue(viewModel.isAuthenticated) val fpStartMode by collectLastValue(viewModel.fingerprintStartMode) val size by collectLastValue(viewModel.size) - val legacyState by collectLastValue(viewModel.legacyState) val authWithSmallPrompt = testCase.shouldStartAsImplicitFlow && @@ -221,14 +701,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(authenticating).isTrue() assertThat(authenticated?.isNotAuthenticated).isTrue() assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM) - assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING) assertButtonsVisible(negative = !authWithSmallPrompt) - val delay = 1000L - viewModel.showAuthenticated(authenticatedModality, delay) + viewModel.showAuthenticated(authenticatedModality, DELAY) assertThat(authenticated?.isAuthenticated).isTrue() - assertThat(authenticated?.delay).isEqualTo(delay) + assertThat(authenticated?.delay).isEqualTo(DELAY) assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation) assertThat(size) .isEqualTo( @@ -238,14 +716,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa PromptSize.SMALL } ) - assertThat(legacyState) - .isEqualTo( - if (expectConfirmation) { - BiometricState.STATE_PENDING_CONFIRMATION - } else { - BiometricState.STATE_AUTHENTICATED - } - ) + assertButtonsVisible( cancel = expectConfirmation, confirm = expectConfirmation, @@ -298,7 +769,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val message by collectLastValue(viewModel.message) val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible) val size by collectLastValue(viewModel.size) - val legacyState by collectLastValue(viewModel.legacyState) val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow) val errorJob = launch { @@ -312,7 +782,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(size).isEqualTo(PromptSize.MEDIUM) assertThat(message).isEqualTo(PromptMessage.Error(errorMessage)) assertThat(messageVisible).isTrue() - assertThat(legacyState).isEqualTo(BiometricState.STATE_ERROR) // temporary error should disappear after a delay errorJob.join() @@ -323,17 +792,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(message).isEqualTo(PromptMessage.Empty) assertThat(messageVisible).isFalse() } - val clearIconError = !restart - assertThat(legacyState) - .isEqualTo( - if (restart) { - BiometricState.STATE_AUTHENTICATING - } else if (clearIconError) { - BiometricState.STATE_IDLE - } else { - BiometricState.STATE_HELP - } - ) assertThat(authenticating).isEqualTo(restart) assertThat(authenticated?.isNotAuthenticated).isTrue() @@ -488,7 +946,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val authenticated by collectLastValue(viewModel.isAuthenticated) val message by collectLastValue(viewModel.message) val size by collectLastValue(viewModel.size) - val legacyState by collectLastValue(viewModel.legacyState) val canTryAgain by collectLastValue(viewModel.canTryAgainNow) assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation) @@ -506,7 +963,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(authenticating).isFalse() assertThat(authenticated?.isAuthenticated).isTrue() - assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED) assertThat(canTryAgain).isFalse() } @@ -524,7 +980,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val authenticated by collectLastValue(viewModel.isAuthenticated) val message by collectLastValue(viewModel.message) val size by collectLastValue(viewModel.size) - val legacyState by collectLastValue(viewModel.legacyState) val canTryAgain by collectLastValue(viewModel.canTryAgainNow) assertThat(authenticating).isFalse() @@ -532,8 +987,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(authenticated?.isAuthenticated).isTrue() if (testCase.isFaceOnly && expectConfirmation) { - assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION) - assertThat(size).isEqualTo(PromptSize.MEDIUM) assertButtonsVisible( cancel = true, @@ -543,8 +996,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa viewModel.confirmAuthenticated() assertThat(message).isEqualTo(PromptMessage.Empty) assertButtonsVisible() - } else { - assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED) } } @@ -563,7 +1014,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val authenticated by collectLastValue(viewModel.isAuthenticated) val message by collectLastValue(viewModel.message) val size by collectLastValue(viewModel.size) - val legacyState by collectLastValue(viewModel.legacyState) val canTryAgain by collectLastValue(viewModel.canTryAgainNow) assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation) @@ -581,7 +1031,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(authenticating).isFalse() assertThat(authenticated?.isAuthenticated).isTrue() - assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED) assertThat(canTryAgain).isFalse() } @@ -610,12 +1059,10 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val message by collectLastValue(viewModel.message) val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible) val size by collectLastValue(viewModel.size) - val legacyState by collectLastValue(viewModel.legacyState) viewModel.showHelp(helpMessage) assertThat(size).isEqualTo(PromptSize.MEDIUM) - assertThat(legacyState).isEqualTo(BiometricState.STATE_HELP) assertThat(message).isEqualTo(PromptMessage.Help(helpMessage)) assertThat(messageVisible).isTrue() @@ -632,7 +1079,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa val message by collectLastValue(viewModel.message) val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible) val size by collectLastValue(viewModel.size) - val legacyState by collectLastValue(viewModel.legacyState) val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired) if (testCase.isCoex && testCase.authenticatedByFingerprint) { @@ -642,11 +1088,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa viewModel.showHelp(helpMessage) assertThat(size).isEqualTo(PromptSize.MEDIUM) - if (confirmationRequired == true) { - assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION) - } else { - assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED) - } + assertThat(message).isEqualTo(PromptMessage.Help(helpMessage)) assertThat(messageVisible).isTrue() assertThat(authenticating).isFalse() @@ -784,6 +1226,15 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), authenticatedModality = BiometricModality.Fingerprint, ), + TestCase( + fingerprint = + fingerprintSensorPropertiesInternal( + strong = true, + sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + ) + .first(), + authenticatedModality = BiometricModality.Fingerprint, + ), TestCase( face = faceSensorPropertiesInternal(strong = true).first(), authenticatedModality = BiometricModality.Face, @@ -794,6 +1245,16 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa authenticatedModality = BiometricModality.Fingerprint, confirmationRequested = true, ), + TestCase( + fingerprint = + fingerprintSensorPropertiesInternal( + strong = true, + sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + ) + .first(), + authenticatedModality = BiometricModality.Fingerprint, + confirmationRequested = true, + ), ) private val coexTestCases = @@ -834,7 +1295,9 @@ internal data class TestCase( val modality = when { fingerprint != null && face != null -> "coex" - fingerprint != null -> "fingerprint only" + fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps" + fingerprint != null && !fingerprint.isAnySidefpsType -> + "fingerprint only, non-sideFps" face != null -> "face only" else -> "?" } @@ -864,6 +1327,8 @@ internal data class TestCase( val isCoex: Boolean get() = face != null && fingerprint != null + @FingerprintSensorProperties.SensorType val sensorType: Int? = fingerprint?.sensorType + val shouldStartAsImplicitFlow: Boolean get() = (isFaceOnly || isCoex) && !confirmationRequested } @@ -890,3 +1355,5 @@ private fun PromptSelectorInteractor.initializePrompt( BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), ) } + +internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600 diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt index 9373ada75003b81b1c344bae977614bc8888a581..f6b284fffa3dd7c9742c9e0475a5cdba29d0f33c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -110,6 +110,27 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { resources = context.orCreateTestableResources } + @Test + fun show_nullDelegate() { + testScope.run { + whenever(bouncerView.delegate).thenReturn(null) + mainHandler.setMode(FakeHandler.Mode.QUEUEING) + + // WHEN bouncer show is requested + underTest.show(true) + + // WHEN all queued messages are dispatched + mainHandler.dispatchQueuedMessages() + + // THEN primary bouncer state doesn't update to show since delegate was null + verify(repository, never()).setPrimaryShow(true) + verify(repository, never()).setPrimaryShowingSoon(false) + verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow() + verify(mPrimaryBouncerCallbackInteractor, never()) + .dispatchVisibilityChanged(View.VISIBLE) + } + } + @Test fun testShow_isScrimmed() { underTest.show(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java index 80b281e458d0b923cec9e919feb98db42dcc2a08..882bcab5fab6d45ed69596de9273b71f1437791b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java +++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java @@ -17,7 +17,6 @@ package com.android.systemui.colorextraction; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -104,21 +103,6 @@ public class SysuiColorExtractorTests extends SysuiTestCase { } } - @Test - public void getColors_fallbackWhenMediaIsVisible() { - simulateEvent(mColorExtractor); - mColorExtractor.setHasMediaArtwork(true); - - ColorExtractor.GradientColors fallbackColors = mColorExtractor.getNeutralColors(); - - for (int type : sTypes) { - assertEquals("Not using fallback!", - mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, type), fallbackColors); - assertNotEquals("Media visibility should not affect system wallpaper.", - mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, type), fallbackColors); - } - } - @Test public void onUiModeChanged_reloadsColors() { Tonal tonal = mock(Tonal.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 91409a37655685dec6049d9b49857dfe6454b0ac..fcb191b4cbd60fa48b27e0fd2d97e071ffdc32a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -12,9 +12,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.model.CommunalWidgetMetadata -import com.android.systemui.communal.shared.CommunalContentSize +import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.FakeLogBuffer @@ -38,6 +39,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyInt +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -58,10 +60,16 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var userTracker: UserTracker - @Mock private lateinit var featureFlags: FeatureFlags + @Mock private lateinit var featureFlags: FeatureFlagsClassic @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo + @Mock private lateinit var providerInfoA: AppWidgetProviderInfo + + @Mock private lateinit var providerInfoB: AppWidgetProviderInfo + + @Mock private lateinit var providerInfoC: AppWidgetProviderInfo + private lateinit var communalRepository: FakeCommunalRepository private lateinit var logBuffer: LogBuffer @@ -70,29 +78,34 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { private val testScope = TestScope(testDispatcher) + private val fakeAllowlist = + listOf( + "com.android.fake/WidgetProviderA", + "com.android.fake/WidgetProviderB", + "com.android.fake/WidgetProviderC", + ) + @Before fun setUp() { MockitoAnnotations.initMocks(this) logBuffer = FakeLogBuffer.Factory.create() - - featureFlagEnabled(true) communalRepository = FakeCommunalRepository() - communalRepository.setIsCommunalEnabled(true) - overrideResource( - R.array.config_communalWidgetAllowlist, - arrayOf(componentName1, componentName2) - ) + communalEnabled(true) + widgetOnKeyguardEnabled(true) + setAppWidgetIds(emptyList()) + + overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray()) whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch") whenever(userTracker.userHandle).thenReturn(userHandle) } @Test - fun broadcastReceiver_featureDisabled_doNotRegisterUserUnlockedBroadcastReceiver() = + fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() = testScope.runTest { - featureFlagEnabled(false) + communalEnabled(false) val repository = initCommunalWidgetRepository() collectLastValue(repository.stopwatchAppWidgetInfo)() verifyBroadcastReceiverNeverRegistered() @@ -129,7 +142,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { job.cancel() runCurrent() - Mockito.verify(broadcastDispatcher).unregisterReceiver(receiver) + verify(broadcastDispatcher).unregisterReceiver(receiver) } @Test @@ -166,7 +179,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { installedProviders(listOf(stopwatchProviderInfo)) val repository = initCommunalWidgetRepository() collectLastValue(repository.stopwatchAppWidgetInfo)() - Mockito.verify(appWidgetHost).allocateAppWidgetId() + verify(appWidgetHost).allocateAppWidgetId() } @Test @@ -185,8 +198,8 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { // Verify app widget id allocated assertThat(lastStopwatchProviderInfo()?.appWidgetId).isEqualTo(123456) - Mockito.verify(appWidgetHost).allocateAppWidgetId() - Mockito.verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt()) + verify(appWidgetHost).allocateAppWidgetId() + verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt()) // User locked again userUnlocked(false) @@ -194,7 +207,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { // Verify app widget id deleted assertThat(lastStopwatchProviderInfo()).isNull() - Mockito.verify(appWidgetHost).deleteAppWidgetId(123456) + verify(appWidgetHost).deleteAppWidgetId(123456) } @Test @@ -203,13 +216,13 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { userUnlocked(false) val repository = initCommunalWidgetRepository() collectLastValue(repository.stopwatchAppWidgetInfo)() - Mockito.verify(appWidgetHost, Mockito.never()).startListening() + verify(appWidgetHost, Mockito.never()).startListening() userUnlocked(true) broadcastReceiverUpdate() collectLastValue(repository.stopwatchAppWidgetInfo)() - Mockito.verify(appWidgetHost).startListening() + verify(appWidgetHost).startListening() } @Test @@ -223,14 +236,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { broadcastReceiverUpdate() collectLastValue(repository.stopwatchAppWidgetInfo)() - Mockito.verify(appWidgetHost).startListening() - Mockito.verify(appWidgetHost, Mockito.never()).stopListening() + verify(appWidgetHost).startListening() + verify(appWidgetHost, Mockito.never()).stopListening() userUnlocked(false) broadcastReceiverUpdate() collectLastValue(repository.stopwatchAppWidgetInfo)() - Mockito.verify(appWidgetHost).stopListening() + verify(appWidgetHost).stopListening() } @Test @@ -241,21 +254,80 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { assertThat( listOf( CommunalWidgetMetadata( - componentName = componentName1, + componentName = fakeAllowlist[0], + priority = 3, + sizes = listOf(CommunalContentSize.HALF), + ), + CommunalWidgetMetadata( + componentName = fakeAllowlist[1], priority = 2, - sizes = listOf(CommunalContentSize.HALF) + sizes = listOf(CommunalContentSize.HALF), ), CommunalWidgetMetadata( - componentName = componentName2, + componentName = fakeAllowlist[2], priority = 1, - sizes = listOf(CommunalContentSize.HALF) - ) + sizes = listOf(CommunalContentSize.HALF), + ), ) ) .containsExactly(*communalWidgetAllowlist.toTypedArray()) } } + // This behavior is temporary before the local database is set up. + @Test + fun communalWidgets_withPreviouslyBoundWidgets_removeEachBinding() = + testScope.runTest { + whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(1, 2, 3) + setAppWidgetIds(listOf(1, 2, 3)) + whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA) + userUnlocked(true) + + val repository = initCommunalWidgetRepository() + + collectLastValue(repository.communalWidgets)() + + verify(appWidgetHost).deleteAppWidgetId(1) + verify(appWidgetHost).deleteAppWidgetId(2) + verify(appWidgetHost).deleteAppWidgetId(3) + } + + @Test + fun communalWidgets_allowlistNotEmpty_bindEachWidgetFromTheAllowlist() = + testScope.runTest { + whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(0, 1, 2) + userUnlocked(true) + + whenever(appWidgetManager.getAppWidgetInfo(0)).thenReturn(providerInfoA) + whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfoB) + whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfoC) + + val repository = initCommunalWidgetRepository() + + val inventory by collectLastValue(repository.communalWidgets) + + assertThat( + listOf( + CommunalWidgetContentModel( + appWidgetId = 0, + providerInfo = providerInfoA, + priority = 3, + ), + CommunalWidgetContentModel( + appWidgetId = 1, + providerInfo = providerInfoB, + priority = 2, + ), + CommunalWidgetContentModel( + appWidgetId = 2, + providerInfo = providerInfoC, + priority = 1, + ), + ) + ) + .containsExactly(*inventory!!.toTypedArray()) + } + private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl { return CommunalWidgetRepositoryImpl( context, @@ -272,7 +344,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { } private fun verifyBroadcastReceiverRegistered() { - Mockito.verify(broadcastDispatcher) + verify(broadcastDispatcher) .registerReceiver( any(), any(), @@ -284,7 +356,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { } private fun verifyBroadcastReceiverNeverRegistered() { - Mockito.verify(broadcastDispatcher, Mockito.never()) + verify(broadcastDispatcher, Mockito.never()) .registerReceiver( any(), any(), @@ -297,7 +369,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { private fun broadcastReceiverUpdate(): BroadcastReceiver { val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>() - Mockito.verify(broadcastDispatcher) + verify(broadcastDispatcher) .registerReceiver( broadcastReceiverCaptor.capture(), any(), @@ -310,7 +382,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { return broadcastReceiverCaptor.value } - private fun featureFlagEnabled(enabled: Boolean) { + private fun communalEnabled(enabled: Boolean) { + communalRepository.setIsCommunalEnabled(enabled) + } + + private fun widgetOnKeyguardEnabled(enabled: Boolean) { whenever(featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)).thenReturn(enabled) } @@ -322,8 +398,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { whenever(appWidgetManager.installedProviders).thenReturn(providers) } - companion object { - const val componentName1 = "component name 1" - const val componentName2 = "component name 2" + private fun setAppWidgetIds(ids: List<Int>) { + whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index cdc42e09683080583ecf56d0b03179a333a324f5..8e21f294a3615a74ab545bd8affbec2e07650cc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -22,7 +22,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository -import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo import com.android.systemui.coroutines.collectLastValue import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 7a13a0a96ee62862ada4b0aca3f3df0ab2f04b22..489665cd130aabf5d866294e1bddc1315b09bee4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -106,7 +106,6 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java)) whenever(powerManager.isInteractive).thenReturn(true) - whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false) // All of these fields are final, so we can't mock them, but are needed so that the surface // appear amount setter doesn't short circuit. diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..1ff46db9962460b33be6b81e8b4e49ee5770e561 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +import com.android.systemui.keyguard.shared.model.ScrimAlpha +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.util.mockito.whenever +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import dagger.Lazy +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BouncerToGoneFlowsTest : SysuiTestCase() { + private lateinit var underTest: BouncerToGoneFlows + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var featureFlags: FakeFeatureFlags + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + @Mock + private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor> + @Mock private lateinit var shadeInteractor: ShadeInteractor + + private val shadeExpansionStateFlow = MutableStateFlow(0.1f) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansionStateFlow) + + repository = FakeKeyguardTransitionRepository() + val featureFlags = + FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) } + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = + BouncerToGoneFlows( + interactor, + statusBarStateController, + primaryBouncerInteractor, + keyguardDismissActionInteractor, + featureFlags, + shadeInteractor, + ) + + whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false) + whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false) + } + + @Test + fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() = + runTest(UnconfinedTestDispatcher()) { + val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) + shadeExpansionStateFlow.value = 1f + whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) } + values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) } + values.forEach { assertThat(it.notificationsAlpha).isIn(Range.closed(0f, 1f)) } + } + + @Test + fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() = + runTest(UnconfinedTestDispatcher()) { + val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) + shadeExpansionStateFlow.value = 0f + + whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) } + } + + @Test + fun scrimBehindAlpha_leaveShadeOpen() = + runTest(UnconfinedTestDispatcher()) { + val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) + + whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { + assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f)) + } + } + + @Test + fun scrimBehindAlpha_doNotLeaveShadeOpen() = + runTest(UnconfinedTestDispatcher()) { + val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) + + whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) } + values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) } + values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) } + assertThat(values[3].behindAlpha).isEqualTo(0f) + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.GONE, + value = value, + transitionState = state, + ownerName = "PrimaryBouncerToGoneTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index d7802aabb298dad4c60c0a88753800042f546315..6cab023d59b0f1beb8facbded15c9f33e33aff54 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -35,6 +34,7 @@ import com.android.systemui.util.mockito.whenever import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import dagger.Lazy +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -52,12 +52,16 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { private lateinit var featureFlags: FakeFeatureFlags @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor + @Mock private lateinit var bouncerToGoneFlows: BouncerToGoneFlows @Mock private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor> + private val shadeExpansionStateFlow = MutableStateFlow(0.1f) + @Before fun setUp() { MockitoAnnotations.initMocks(this) + repository = FakeKeyguardTransitionRepository() val featureFlags = FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) } @@ -74,6 +78,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { primaryBouncerInteractor, keyguardDismissActionInteractor, featureFlags, + bouncerToGoneFlows, ) whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false) @@ -148,59 +153,6 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { values.forEach { assertThat(it).isEqualTo(1f) } } - @Test - fun scrimAlpha_runDimissFromKeyguard() = - runTest(UnconfinedTestDispatcher()) { - val values by collectValues(underTest.scrimAlpha) - - whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) - - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.6f)) - repository.sendTransitionStep(step(1f)) - - assertThat(values.size).isEqualTo(4) - values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) } - } - - @Test - fun scrimBehindAlpha_leaveShadeOpen() = - runTest(UnconfinedTestDispatcher()) { - val values by collectValues(underTest.scrimAlpha) - - whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true) - - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.6f)) - repository.sendTransitionStep(step(1f)) - - assertThat(values.size).isEqualTo(4) - values.forEach { - assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f)) - } - } - - @Test - fun scrimBehindAlpha_doNotLeaveShadeOpen() = - runTest(UnconfinedTestDispatcher()) { - val values by collectValues(underTest.scrimAlpha) - - whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false) - - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.6f)) - repository.sendTransitionStep(step(1f)) - - assertThat(values.size).isEqualTo(4) - values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) } - values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) } - values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) } - assertThat(values[3].behindAlpha).isEqualTo(0f) - } - private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..429aad113a9a414e7ebdfb26b69fa8453ebb217c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt @@ -0,0 +1,66 @@ +package com.android.systemui.mediaprojection + +import android.media.projection.IMediaProjectionManager +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class MediaProjectionMetricsLoggerTest : SysuiTestCase() { + + private val service = mock<IMediaProjectionManager>() + private val logger = MediaProjectionMetricsLogger(service) + + @Test + fun notifyProjectionInitiated_sourceApp_forwardsToServiceWithMetricsValue() { + val hostUid = 123 + val sessionCreationSource = SessionCreationSource.APP + + logger.notifyProjectionInitiated(hostUid, sessionCreationSource) + + verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_APP) + } + + @Test + fun notifyProjectionInitiated_sourceCast_forwardsToServiceWithMetricsValue() { + val hostUid = 123 + val sessionCreationSource = SessionCreationSource.CAST + + logger.notifyProjectionInitiated(hostUid, sessionCreationSource) + + verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_CAST) + } + + @Test + fun notifyProjectionInitiated_sourceSysUI_forwardsToServiceWithMetricsValue() { + val hostUid = 123 + val sessionCreationSource = SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER + + logger.notifyProjectionInitiated(hostUid, sessionCreationSource) + + verify(service) + .notifyPermissionRequestInitiated( + hostUid, + METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER + ) + } + + @Test + fun notifyProjectionInitiated_sourceUnknown_forwardsToServiceWithMetricsValue() { + val hostUid = 123 + val sessionCreationSource = SessionCreationSource.UNKNOWN + + logger.notifyProjectionInitiated(hostUid, sessionCreationSource) + + verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_UNKNOWN) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt index 0552ced1d678df328db13f83923c1415781f3ed5..0e4b113f57ca842068a88363f0a1182bf97b9e33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt @@ -124,11 +124,44 @@ class CameraToggleTileTest : SysuiTestCase() { } @Test - fun testLongClickIntent() { + fun testLongClickIntent_safetyCenterEnabled() { whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true) - assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS) + val cameraTile = CameraToggleTile( + host, + uiEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + metricsLogger, + FalsingManagerFake(), + statusBarStateController, + activityStarter, + qsLogger, + privacyController, + keyguardStateController, + safetyCenterManager) + assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS) + cameraTile.destroy() + testableLooper.processAllMessages() + } + @Test + fun testLongClickIntent_safetyCenterDisabled() { whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false) + val cameraTile = CameraToggleTile( + host, + uiEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + metricsLogger, + FalsingManagerFake(), + statusBarStateController, + activityStarter, + qsLogger, + privacyController, + keyguardStateController, + safetyCenterManager) assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS) + cameraTile.destroy() + testableLooper.processAllMessages() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt index 0fcfdb6f318fe1469bdc47630413fbe93284a59d..b98a7570bb6c9d0eed5d0c9d8daf902d5da44ba9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt @@ -123,11 +123,44 @@ class MicrophoneToggleTileTest : SysuiTestCase() { } @Test - fun testLongClickIntent() { + fun testLongClickIntent_safetyCenterEnabled() { whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true) - assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS) + val micTile = MicrophoneToggleTile( + host, + uiEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + metricsLogger, + FalsingManagerFake(), + statusBarStateController, + activityStarter, + qsLogger, + privacyController, + keyguardStateController, + safetyCenterManager) + assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS) + micTile.destroy() + testableLooper.processAllMessages() + } + @Test + fun testLongClickIntent_safetyCenterDisabled() { whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false) - assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS) + val micTile = MicrophoneToggleTile( + host, + uiEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + metricsLogger, + FalsingManagerFake(), + statusBarStateController, + activityStarter, + qsLogger, + privacyController, + keyguardStateController, + safetyCenterManager) + assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS) + micTile.destroy() + testableLooper.processAllMessages() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 90d2e78a411fd7480aa774ac828fe8fb1f54fc87..c439cfe6270fd87e08fd407b058af3cc664f2298 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -23,11 +23,13 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Dialog; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.os.Looper; import android.testing.AndroidTestingRunner; @@ -64,6 +66,8 @@ import org.mockito.MockitoAnnotations; */ public class RecordingControllerTest extends SysuiTestCase { + private static final int TEST_USER_ID = 12345; + private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @Mock @@ -91,6 +95,11 @@ public class RecordingControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + Context spiedContext = spy(mContext); + when(spiedContext.getUserId()).thenReturn(TEST_USER_ID); + + when(mUserContextProvider.getUserContext()).thenReturn(spiedContext); + mFeatureFlags = new FakeFeatureFlags(); mController = new RecordingController( mMainExecutor, @@ -288,7 +297,6 @@ public class RecordingControllerTest extends SysuiTestCase { if (Looper.myLooper() == null) { Looper.prepare(); } - mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); @@ -297,6 +305,8 @@ public class RecordingControllerTest extends SysuiTestCase { mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); verify(mMediaProjectionMetricsLogger) - .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); + .notifyProjectionInitiated( + TEST_USER_ID, + SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); } } 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 4f3216da737022085f30cc34e664af605b94fa55..b421e1b454773e5c82e2cea122d06d8c769858fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.KeyEvent import android.view.MotionEvent +import android.view.View import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardMessageAreaController @@ -43,11 +44,20 @@ import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.compose.ComposeFacade.isComposeAvailable import com.android.systemui.dock.DockManager import com.android.systemui.dump.DumpManager import com.android.systemui.dump.logcatLogBuffer -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW +import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED +import com.android.systemui.flags.Flags.MIGRATE_NSSL +import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES +import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION +import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON +import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.DismissCallbackRegistry @@ -83,6 +93,7 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -97,6 +108,7 @@ import org.mockito.Mock import org.mockito.Mockito.anyFloat import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.Optional @@ -135,6 +147,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener @Mock private lateinit var notificationInsetsController: NotificationInsetsController + @Mock private lateinit var mCommunalViewModel: CommunalViewModel + private lateinit var mCommunalRepository: FakeCommunalRepository @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @@ -159,7 +173,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var testScope: TestScope - private lateinit var featureFlags: FakeFeatureFlags + private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic @Before fun setUp() { @@ -174,14 +188,16 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) .thenReturn(emptyFlow<TransitionStep>()) - featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true) - featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false) - featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) - featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) - featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - featureFlags.set(Flags.MIGRATE_NSSL, false) - featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false) + featureFlagsClassic = FakeFeatureFlagsClassic() + featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true) + featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false) + featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) + featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true) + featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) + featureFlagsClassic.set(MIGRATE_NSSL, false) + featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false) + + mCommunalRepository = FakeCommunalRepository() testScope = TestScope() fakeClock = FakeSystemClock() @@ -216,14 +232,16 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { mock(KeyguardMessageAreaController.Factory::class.java), keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, + mCommunalViewModel, + mCommunalRepository, notificationExpansionRepository, - featureFlags, + featureFlagsClassic, fakeClock, BouncerMessageInteractor( repository = BouncerMessageRepositoryImpl(), userRepository = FakeUserRepository(), countDownTimerUtil = mock(CountDownTimerUtil::class.java), - featureFlags = featureFlags, + featureFlags = featureFlagsClassic, updateMonitor = mock(KeyguardUpdateMonitor::class.java), biometricSettingsRepository = FakeBiometricSettingsRepository(), applicationScope = testScope.backgroundScope, @@ -443,7 +461,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)) .thenReturn(true) - featureFlags.set(Flags.MIGRATE_NSSL, true) + featureFlagsClassic.set(MIGRATE_NSSL, true) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() @@ -460,7 +478,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)) .thenReturn(false) - featureFlags.set(Flags.MIGRATE_NSSL, true) + featureFlagsClassic.set(MIGRATE_NSSL, true) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -473,6 +491,48 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) } + @Test + fun setsUpCommunalHubLayout_whenFlagEnabled() { + if (!isComposeAvailable()) { + return + } + + mCommunalRepository.setIsCommunalEnabled(true) + + val mockCommunalPlaceholder = mock(View::class.java) + val fakeViewIndex = 20 + whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder) + whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex) + whenever(view.context).thenReturn(context) + + underTest.setupCommunalHubLayout() + + // Communal view added as a child of the container at the proper index, the stub is removed. + verify(view).removeView(mockCommunalPlaceholder) + verify(view).addView(any(), eq(fakeViewIndex)) + } + + @Test + fun doesNotSetupCommunalHubLayout_whenFlagDisabled() { + if (!isComposeAvailable()) { + return + } + + mCommunalRepository.setIsCommunalEnabled(false) + + val mockCommunalPlaceholder = mock(View::class.java) + val fakeViewIndex = 20 + whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder) + whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex) + whenever(view.context).thenReturn(context) + + underTest.setupCommunalHubLayout() + + // No adding or removing of views occurs. + verify(view, times(0)).removeView(mockCommunalPlaceholder) + verify(view, times(0)).addView(any(), eq(fakeViewIndex)) + } + @Test fun forwardsDispatchKeyEvent() { val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 4d3eab45d0010e068236297c15bd4f5a5d367a16..9c571015f750da7b6f96896309b762e488605f84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -42,6 +42,8 @@ import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.communal.data.repository.FakeCommunalRepository +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.dock.DockManager import com.android.systemui.dump.DumpManager import com.android.systemui.dump.logcatLogBuffer @@ -142,6 +144,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { private lateinit var unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> @Mock private lateinit var notificationInsetsController: NotificationInsetsController + @Mock private lateinit var mCommunalViewModel: CommunalViewModel + private lateinit var mCommunalRepository: FakeCommunalRepository @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @@ -178,6 +182,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition) .thenReturn(emptyFlow()) + mCommunalRepository = FakeCommunalRepository() + val featureFlags = FakeFeatureFlags() featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true) featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false) @@ -218,6 +224,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { Mockito.mock(KeyguardMessageAreaController.Factory::class.java), keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, + mCommunalViewModel, + mCommunalRepository, NotificationExpansionRepository(), featureFlags, FakeSystemClock(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index 81382a44def6eb9ca0dee2656dd132030077146c..3a260ae374c63d5f53bdfde64e333140113dd422 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -444,105 +444,6 @@ class ShadeInteractorTest : SysuiTestCase() { assertThat(underTest.anyExpansion.value).isEqualTo(.5f) } - @Test - fun expanding_shadeDraggedDown_expandingTrue() = - testScope.runTest() { - val actual by collectLastValue(underTest.isAnyExpanding) - - // GIVEN shade and QS collapsed - shadeRepository.setLegacyShadeExpansion(0f) - shadeRepository.setQsExpansion(0f) - runCurrent() - - // WHEN shade partially expanded - shadeRepository.setLegacyShadeExpansion(.5f) - runCurrent() - - // THEN anyExpanding is true - assertThat(actual).isTrue() - } - - @Test - fun expanding_qsDraggedDown_expandingTrue() = - testScope.runTest() { - val actual by collectLastValue(underTest.isAnyExpanding) - - // GIVEN shade and QS collapsed - shadeRepository.setLegacyShadeExpansion(0f) - shadeRepository.setQsExpansion(0f) - runCurrent() - - // WHEN shade partially expanded - shadeRepository.setQsExpansion(.5f) - runCurrent() - - // THEN anyExpanding is true - assertThat(actual).isTrue() - } - - @Test - fun expanding_shadeDraggedUpAndDown() = - testScope.runTest() { - val actual by collectLastValue(underTest.isAnyExpanding) - - // WHEN shade starts collapsed then partially expanded - shadeRepository.setLegacyShadeExpansion(0f) - shadeRepository.setLegacyShadeExpansion(.5f) - shadeRepository.setQsExpansion(0f) - runCurrent() - - // THEN anyExpanding is true - assertThat(actual).isTrue() - - // WHEN shade dragged up a bit - shadeRepository.setLegacyShadeExpansion(.2f) - runCurrent() - - // THEN anyExpanding is still true - assertThat(actual).isTrue() - - // WHEN shade dragged down a bit - shadeRepository.setLegacyShadeExpansion(.7f) - runCurrent() - - // THEN anyExpanding is still true - assertThat(actual).isTrue() - - // WHEN shade fully expanded - shadeRepository.setLegacyShadeExpansion(1f) - runCurrent() - - // THEN anyExpanding is now false - assertThat(actual).isFalse() - - // WHEN shade dragged up a bit - shadeRepository.setLegacyShadeExpansion(.7f) - runCurrent() - - // THEN anyExpanding is still false - assertThat(actual).isFalse() - } - - @Test - fun expanding_shadeDraggedDownThenUp_expandingFalse() = - testScope.runTest() { - val actual by collectLastValue(underTest.isAnyExpanding) - - // GIVEN shade starts collapsed - shadeRepository.setLegacyShadeExpansion(0f) - shadeRepository.setQsExpansion(0f) - runCurrent() - - // WHEN shade expands but doesn't complete - shadeRepository.setLegacyShadeExpansion(.5f) - runCurrent() - shadeRepository.setLegacyShadeExpansion(0f) - runCurrent() - - // THEN anyExpanding is false - assertThat(actual).isFalse() - } - @Test fun lockscreenShadeExpansion_idle_onScene() = testScope.runTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index a5f5fc7e36f2afc46a6d74afb84f00a964d0d26a..43adc69be13fd021f7dd42c2c9a0e0e51ed159d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -22,14 +22,18 @@ import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.os.UserHandle.USER_ALL; import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; +import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -99,6 +103,7 @@ import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class NotificationLockscreenUserManagerTest extends SysuiTestCase { + private static final int TEST_PROFILE_USERHANDLE = 12; @Mock private NotificationPresenter mPresenter; @Mock @@ -701,6 +706,60 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId)); } + @Test + public void testProfileAvailabilityIntent() { + mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + mLockscreenUserManager.mCurrentProfiles.clear(); + assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); + mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); + simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_AVAILABLE); + assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size()); + } + + @Test + public void testProfileUnAvailabilityIntent() { + mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + mLockscreenUserManager.mCurrentProfiles.clear(); + assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); + mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); + simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE); + assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size()); + } + + @Test + public void testManagedProfileAvailabilityIntent() { + mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + mLockscreenUserManager.mCurrentProfiles.clear(); + mLockscreenUserManager.mCurrentManagedProfiles.clear(); + assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); + assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size()); + mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); + simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); + assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size()); + assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size()); + } + + @Test + public void testManagedProfileUnAvailabilityIntent() { + mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + mLockscreenUserManager.mCurrentProfiles.clear(); + mLockscreenUserManager.mCurrentManagedProfiles.clear(); + assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); + assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size()); + mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); + simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size()); + assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size()); + } + + private void simulateProfileAvailabilityActions(String intentAction) { + BroadcastReceiver broadcastReceiver = + mLockscreenUserManager.getBaseBroadcastReceiverForTest(); + final Intent intent = new Intent(intentAction); + intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE); + broadcastReceiver.onReceive(mContext, intent); + } + private class TestNotificationLockscreenUserManager extends NotificationLockscreenUserManagerImpl { public TestNotificationLockscreenUserManager(Context context) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt deleted file mode 100644 index cfcf4257ce28c2f3d27d325f36fda847e132dd07..0000000000000000000000000000000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt +++ /dev/null @@ -1,68 +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 - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.util.mockito.whenever -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.anyBoolean -import org.mockito.Mockito.doCallRealMethod -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -/** - * Temporary test for the lock screen live wallpaper project. - * - * TODO(b/273443374): remove this test - */ -@RunWith(AndroidTestingRunner::class) -@SmallTest -class NotificationMediaManagerTest : SysuiTestCase() { - - @Mock private lateinit var notificationMediaManager: NotificationMediaManager - - @Mock private lateinit var mockBackDropView: BackDropView - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean()) - doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView - } - - @After fun tearDown() {} - - /** Check that updateMediaMetaData is a no-op with mIsLockscreenLiveWallpaperEnabled = true */ - @Test - fun testUpdateMediaMetaDataDisabled() { - notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true - for (metaDataChanged in listOf(true, false)) { - for (allowEnterAnimation in listOf(true, false)) { - notificationMediaManager.updateMediaMetaData(metaDataChanged) - verify(notificationMediaManager, never()).mediaMetadata - } - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt index 839770267c749ab3b44b27ce3d91dc981c1b75bf..df8afde1b9a36917e1a7010ec9156face0e16ae5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt @@ -26,6 +26,7 @@ class FakeStatusEvent( override var forceVisible: Boolean = false, override val showAnimation: Boolean = true, override var contentDescription: String? = "", + override val shouldAnnounceAccessibilityEvent: Boolean = false ) : StatusEvent class FakePrivacyStatusEvent( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 4fcccf887e587c3af624a5980703e19c7f3436ac..fee8b82a3038acc543da9d93d437c9ccc6ac8a3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -33,6 +33,8 @@ import com.android.systemui.statusbar.BatteryStatusChip import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert.assertEquals @@ -369,6 +371,63 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { verify(listener, times(1)).onHidePersistentDot() } + @Test + fun testAccessibilityAnnouncement_announced() = runTest { + // Instantiate class under test with TestScope from runTest + initializeSystemStatusAnimationScheduler(testScope = this) + val accessibilityDesc = "Some desc" + val mockView = mock<View>() + val mockAnimatableView = + mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) } + + scheduleFakeEventWithView( + accessibilityDesc, + mockAnimatableView, + shouldAnnounceAccessibilityEvent = true + ) + fastForwardAnimationToState(ANIMATING_OUT) + + verify(mockView).announceForAccessibility(eq(accessibilityDesc)) + } + + @Test + fun testAccessibilityAnnouncement_nullDesc_noAnnouncement() = runTest { + // Instantiate class under test with TestScope from runTest + initializeSystemStatusAnimationScheduler(testScope = this) + val accessibilityDesc = null + val mockView = mock<View>() + val mockAnimatableView = + mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) } + + scheduleFakeEventWithView( + accessibilityDesc, + mockAnimatableView, + shouldAnnounceAccessibilityEvent = true + ) + fastForwardAnimationToState(ANIMATING_OUT) + + verify(mockView, never()).announceForAccessibility(any()) + } + + @Test + fun testAccessibilityAnnouncement_notNeeded_noAnnouncement() = runTest { + // Instantiate class under test with TestScope from runTest + initializeSystemStatusAnimationScheduler(testScope = this) + val accessibilityDesc = "something" + val mockView = mock<View>() + val mockAnimatableView = + mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) } + + scheduleFakeEventWithView( + accessibilityDesc, + mockAnimatableView, + shouldAnnounceAccessibilityEvent = false + ) + fastForwardAnimationToState(ANIMATING_OUT) + + verify(mockView, never()).announceForAccessibility(any()) + } + @Test fun testPrivacyDot_isRemovedDuringChipAnimation() = runTest { // Instantiate class under test with TestScope from runTest @@ -572,6 +631,20 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { return privacyChip } + private fun scheduleFakeEventWithView( + desc: String?, + view: BackgroundAnimatableView, + shouldAnnounceAccessibilityEvent: Boolean + ) { + val fakeEvent = + FakeStatusEvent( + viewCreator = { view }, + contentDescription = desc, + shouldAnnounceAccessibilityEvent = shouldAnnounceAccessibilityEvent + ) + systemStatusAnimationScheduler.onStatusEvent(fakeEvent) + } + private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip { val batteryChip = BatteryStatusChip(mContext) val fakeBatteryEvent = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java index c664c39f443265b10b7e6a552c068f091d6a3217..2ef4374ce13ab6c59bcb0deda83c5d7d9d7f55e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java @@ -18,10 +18,16 @@ package com.android.systemui.statusbar.notification; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.test.suitebuilder.annotation.SmallTest; @@ -33,8 +39,8 @@ import android.view.View; import android.view.animation.Interpolator; import com.android.app.animation.Interpolators; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.stack.AnimationFilter; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -85,7 +91,7 @@ public class PropertyAnimatorTest extends SysuiTestCase { return mEffectiveProperty; } }; - private AnimatorListenerAdapter mFinishListener = mock(AnimatorListenerAdapter.class); + private AnimatorListenerAdapter mFinishListener; private AnimationProperties mAnimationProperties = new AnimationProperties() { @Override public AnimationFilter getAnimationFilter() { @@ -104,6 +110,7 @@ public class PropertyAnimatorTest extends SysuiTestCase { @Before public void setUp() { mView = new View(getContext()); + mFinishListener = mock(AnimatorListenerAdapter.class); } @Test @@ -228,6 +235,32 @@ public class PropertyAnimatorTest extends SysuiTestCase { assertTrue(animator.getListeners().contains(mFinishListener)); } + @Test + public void testListenerCallbackOrderAndTagState() { + mAnimationFilter.reset(); + mAnimationFilter.animate(mProperty.getProperty()); + mAnimationProperties.setCustomInterpolator(mEffectiveProperty, mTestInterpolator); + mAnimationProperties.setDuration(500); + + // Validates that the onAnimationEnd function set by PropertyAnimator was run first. + doAnswer(invocation -> { + assertNull(mView.getTag(mProperty.getAnimatorTag())); + return null; + }) + .when(mFinishListener) + .onAnimationEnd(any(Animator.class), anyBoolean()); + + // Begin the animation and verify it set state correctly + PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties); + ValueAnimator animator = ViewState.getChildTag(mView, mProperty.getAnimatorTag()); + assertNotNull(animator); + assertNotNull(mView.getTag(mProperty.getAnimatorTag())); + + // Terminate the animation to run end runners, and validate they executed. + animator.end(); + verify(mFinishListener).onAnimationEnd(animator, false); + } + @Test public void testIsAnimating() { mAnimationFilter.reset(); @@ -236,4 +269,4 @@ public class PropertyAnimatorTest extends SysuiTestCase { PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties); assertTrue(PropertyAnimator.isAnimating(mView, mProperty)); } -} +} \ No newline at end of file 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 c8cbe42fb0d54915d8157be70c230da7d818517a..a59cd87d78da769e34ac3a2f21334bf81630d19b 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 @@ -277,8 +277,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mNotificationShadeWindowViewControllerLazy; @Mock private NotificationShelfController mNotificationShelfController; @Mock private DozeParameters mDozeParameters; - @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy; - @Mock private LockscreenWallpaper mLockscreenWallpaper; @Mock private DozeServiceHost mDozeServiceHost; @Mock private BackActionInteractor mBackActionInteractor; @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback; @@ -404,7 +402,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mGradientColors.supportsDarkText()).thenReturn(true); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); - when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper); when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController); when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher); when(mNotificationShadeWindowViewControllerLazy.get()) @@ -508,7 +505,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { new NotificationExpansionRepository(), mDozeParameters, mScrimController, - mLockscreenWallpaperLazy, mBiometricUnlockControllerLazy, mAuthRippleController, mDozeServiceHost, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt deleted file mode 100644 index 47671fbadd0a6aae302d7e21c154aa566d2b2c46..0000000000000000000000000000000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt +++ /dev/null @@ -1,101 +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.phone - -import android.app.WallpaperManager -import android.content.pm.UserInfo -import android.os.Looper -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.user.data.model.SelectionStatus -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.util.kotlin.JavaAdapter -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever -import com.android.systemui.utils.os.FakeHandler -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.verify - -@SmallTest -@OptIn(ExperimentalCoroutinesApi::class) -class LockscreenWallpaperTest : SysuiTestCase() { - - private lateinit var underTest: LockscreenWallpaper - - private val testScope = TestScope(StandardTestDispatcher()) - private val userRepository = FakeUserRepository() - - private val wallpaperManager: WallpaperManager = mock() - - @Before - fun setUp() { - whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false) - whenever(wallpaperManager.isWallpaperSupported).thenReturn(true) - underTest = - LockscreenWallpaper( - /* wallpaperManager= */ wallpaperManager, - /* iWallpaperManager= */ mock(), - /* keyguardUpdateMonitor= */ mock(), - /* dumpManager= */ mock(), - /* mediaManager= */ mock(), - /* mainHandler= */ FakeHandler(Looper.getMainLooper()), - /* javaAdapter= */ JavaAdapter(testScope.backgroundScope), - /* userRepository= */ userRepository, - /* userTracker= */ mock(), - ) - underTest.start() - } - - @Test - fun getBitmap_matchesUserIdFromUserRepo() = - testScope.runTest { - val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) - userRepository.setUserInfos(listOf(info)) - userRepository.setSelectedUserInfo(info) - - underTest.bitmap - - verify(wallpaperManager).getWallpaperFile(any(), eq(5)) - } - - @Test - fun getBitmap_usesOldUserIfNewUserInProgress() = - testScope.runTest { - val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) - val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0) - userRepository.setUserInfos(listOf(info5, info6)) - userRepository.setSelectedUserInfo(info5) - - // WHEN the selection of user 6 is only in progress - userRepository.setSelectedUserInfo( - info6, - selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS - ) - - underTest.bitmap - - // THEN we still use user 5 for wallpaper selection - verify(wallpaperManager).getWallpaperFile(any(), eq(5)) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 6b3bd22d5e625fd0243323b33a99394b19d8e6e4..15c09b53938f50cb4476b41bb5ad3b87d07fbb07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -70,6 +70,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; +import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; @@ -141,6 +142,8 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private ScreenOffAnimationController mScreenOffAnimationController; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; + @Mock private AlternateBouncerToGoneTransitionViewModel + mAlternateBouncerToGoneTransitionViewModel; @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository(); @Mock private CoroutineDispatcher mMainDispatcher; @@ -264,10 +267,12 @@ public class ScrimControllerTest extends SysuiTestCase { when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock); when(mDockManager.isDocked()).thenReturn(false); - when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition()) + when(mKeyguardTransitionInteractor.transition(any(), any())) .thenReturn(emptyFlow()); when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha()) .thenReturn(emptyFlow()); + when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha()) + .thenReturn(emptyFlow()); mScrimController = new ScrimController( mLightBarController, @@ -285,6 +290,7 @@ public class ScrimControllerTest extends SysuiTestCase { mKeyguardUnlockAnimationController, mStatusBarKeyguardViewManager, mPrimaryBouncerToGoneTransitionViewModel, + mAlternateBouncerToGoneTransitionViewModel, mKeyguardTransitionInteractor, mWallpaperRepository, mMainDispatcher, @@ -992,6 +998,7 @@ public class ScrimControllerTest extends SysuiTestCase { mKeyguardUnlockAnimationController, mStatusBarKeyguardViewManager, mPrimaryBouncerToGoneTransitionViewModel, + mAlternateBouncerToGoneTransitionViewModel, mKeyguardTransitionInteractor, mWallpaperRepository, mMainDispatcher, @@ -1775,7 +1782,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void ignoreTransitionRequestWhileKeyguardTransitionRunning() { mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.mPrimaryBouncerToGoneTransition.accept( + mScrimController.mBouncerToGoneTransition.accept( new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f, TransitionState.RUNNING, "ScrimControllerTest")); @@ -1787,7 +1794,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() { when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); - mScrimController.mPrimaryBouncerToGoneTransition.accept( + mScrimController.mBouncerToGoneTransition.accept( new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f, TransitionState.FINISHED, "ScrimControllerTest")); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..21d22bcf3d2afc4354b279e304b2b87153248c9b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt @@ -0,0 +1,56 @@ +/* + * 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.phone + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import kotlin.test.Test +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +class SystemUIBottomSheetDialogTest : SysuiTestCase() { + + private val configurationController = mock<ConfigurationController>() + + private lateinit var dialog: SystemUIBottomSheetDialog + + @Before + fun setup() { + dialog = SystemUIBottomSheetDialog(mContext, configurationController) + } + + @Test + fun onStart_registersConfigCallback() { + dialog.show() + + verify(configurationController).addCallback(any()) + } + + @Test + fun onStop_unregisterConfigCallback() { + dialog.show() + dialog.dismiss() + + verify(configurationController).removeCallback(any()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..4eb159103b49bdf3d496c56a8cc347038147d0c4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt @@ -0,0 +1,68 @@ +/* + * 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.unfold.updates + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.unfold.system.DeviceStateRepositoryImpl +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class DeviceStateRepositoryTest : SysuiTestCase() { + + private val foldProvider = mock<FoldProvider>() + private val testScope = TestScope(UnconfinedTestDispatcher()) + + private val foldStateRepository = DeviceStateRepositoryImpl(foldProvider) { r -> r.run() } + + @Test + fun onHingeAngleUpdate_received() = + testScope.runTest { + val flowValue = collectLastValue(foldStateRepository.isFolded) + val foldCallback = argumentCaptor<FoldProvider.FoldCallback>() + + verify(foldProvider).registerCallback(capture(foldCallback), any()) + + foldCallback.value.onFoldUpdated(true) + assertThat(flowValue()).isEqualTo(true) + + foldCallback.value.onFoldUpdated(false) + assertThat(flowValue()).isEqualTo(false) + } + + @Test + fun onHingeAngleUpdate_unregisters() { + testScope.runTest { + val flowValue = collectLastValue(foldStateRepository.isFolded) + + verify(foldProvider).registerCallback(any(), any()) + } + verify(foldProvider).unregisterCallback(any()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..065132300564a0360913b6728959d1cc238d0732 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt @@ -0,0 +1,78 @@ +/* + * 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.unfold.updates + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class FoldStateRepositoryTest : SysuiTestCase() { + + private val foldStateProvider = mock<FoldStateProvider>() + private val foldUpdatesListener = argumentCaptor<FoldStateProvider.FoldUpdatesListener>() + private val testScope = TestScope(UnconfinedTestDispatcher()) + + private val foldStateRepository = FoldStateRepositoryImpl(foldStateProvider) + @Test + fun onHingeAngleUpdate_received() = + testScope.runTest { + val flowValue = collectLastValue(foldStateRepository.hingeAngle) + + verify(foldStateProvider).addCallback(capture(foldUpdatesListener)) + foldUpdatesListener.value.onHingeAngleUpdate(42f) + + assertThat(flowValue()).isEqualTo(42f) + } + + @Test + fun onFoldUpdate_received() = + testScope.runTest { + val flowValue = collectLastValue(foldStateRepository.foldUpdate) + + verify(foldStateProvider).addCallback(capture(foldUpdatesListener)) + foldUpdatesListener.value.onFoldUpdate(FOLD_UPDATE_START_OPENING) + + assertThat(flowValue()).isEqualTo(FoldUpdate.START_OPENING) + } + + @Test + fun foldUpdates_mappedCorrectly() { + mapOf( + FOLD_UPDATE_START_OPENING to FoldUpdate.START_OPENING, + FOLD_UPDATE_START_CLOSING to FoldUpdate.START_CLOSING, + FOLD_UPDATE_FINISH_HALF_OPEN to FoldUpdate.FINISH_HALF_OPEN, + FOLD_UPDATE_FINISH_FULL_OPEN to FoldUpdate.FINISH_FULL_OPEN, + FOLD_UPDATE_FINISH_CLOSED to FoldUpdate.FINISH_CLOSED + ) + .forEach { (id, expected) -> + assertThat(FoldUpdate.fromFoldUpdateId(id)).isEqualTo(expected) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java index 468c5a73645b82032078688c815f67d225520ee4..fc2030f694ad16659014bf349ba609957e384e7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java @@ -16,15 +16,20 @@ package com.android.systemui.wallpapers; +import static android.app.WallpaperManager.FLAG_LOCK; +import static android.app.WallpaperManager.FLAG_SYSTEM; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.hamcrest.Matchers.equalTo; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -111,7 +116,8 @@ public class ImageWallpaperTest extends SysuiTestCase { when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888); // set up wallpaper manager - when(mWallpaperManager.getBitmapAsUser(eq(ActivityManager.getCurrentUser()), anyBoolean())) + when(mWallpaperManager.getBitmapAsUser( + eq(ActivityManager.getCurrentUser()), anyBoolean(), anyInt(), anyBoolean())) .thenReturn(mWallpaperBitmap); when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager); @@ -208,6 +214,7 @@ public class ImageWallpaperTest extends SysuiTestCase { ImageWallpaper.CanvasEngine spyEngine = spy(engine); doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class)); doNothing().when(spyEngine).reportEngineShown(anyBoolean()); + doReturn(FLAG_SYSTEM | FLAG_LOCK).when(spyEngine).getWallpaperFlags(); doAnswer(invocation -> { ((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated(); return null; @@ -216,7 +223,7 @@ public class ImageWallpaperTest extends SysuiTestCase { } private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) { - when(mWallpaperManager.peekBitmapDimensions()) + when(mWallpaperManager.peekBitmapDimensions(anyInt(), anyBoolean())) .thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight)); when(mWallpaperBitmap.getWidth()).thenReturn(bitmapWidth); when(mWallpaperBitmap.getHeight()).thenReturn(bitmapHeight); @@ -234,9 +241,7 @@ public class ImageWallpaperTest extends SysuiTestCase { clearInvocations(mSurfaceHolder); setBitmapDimensions(bitmapWidth, bitmapHeight); - ImageWallpaper imageWallpaper = createImageWallpaper(); - ImageWallpaper.CanvasEngine engine = - (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine(); + ImageWallpaper.CanvasEngine engine = getSpyEngine(); engine.onCreate(mSurfaceHolder); verify(mSurfaceHolder, times(1)).setFixedSize( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 30132f7747b7e2356d1658a466bb3b7893b72fbb..08adda32eb6db8ad9dcb32ad65aee91e16a89415 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -1,7 +1,8 @@ package com.android.systemui.communal.data.repository import com.android.systemui.communal.data.model.CommunalWidgetMetadata -import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -11,7 +12,14 @@ class FakeCommunalWidgetRepository : CommunalWidgetRepository { override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList() + private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList()) + override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets + fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) { _stopwatchAppWidgetInfo.value = appWidgetInfo } + + fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) { + _communalWidgets.value = inventory + } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt index 5ffc094b88b3de70900130a21fc51576d8556b3f..7473ca6a64861d89f163fc1d2b5f754bb7dc314e 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt @@ -22,6 +22,8 @@ import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgress import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder import com.android.systemui.unfold.updates.DeviceFoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider +import com.android.systemui.unfold.updates.FoldStateRepository +import com.android.systemui.unfold.updates.FoldStateRepositoryImpl import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider @@ -55,6 +57,12 @@ class UnfoldSharedModule { fun unfoldKeyguardVisibilityManager( impl: UnfoldKeyguardVisibilityManagerImpl ): UnfoldKeyguardVisibilityManager = impl + + @Provides + @Singleton + fun foldStateRepository( + impl: FoldStateRepositoryImpl + ): FoldStateRepository = impl } /** diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 6743515c2ec7d12ee69e7ad650b3eb9f7ac271d2..003013e185832c00c69ba76cdc12296ddb4f8e61 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -17,7 +17,6 @@ package com.android.systemui.unfold.updates import android.content.Context import android.os.Handler -import android.os.Trace import android.util.Log import androidx.annotation.FloatRange import androidx.annotation.VisibleForTesting @@ -130,7 +129,6 @@ constructor( "lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition" ) } - Trace.setCounter("DeviceFoldStateProvider#onHingeAngle", angle.toLong()) val currentDirection = if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt index 6e87beeb295f78a1cae2f699ca6edd29666b1af8..ea6786e6c4bcce7d0d78fd5baf308489854963e8 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt @@ -20,7 +20,7 @@ interface FoldProvider { fun registerCallback(callback: FoldCallback, executor: Executor) fun unregisterCallback(callback: FoldCallback) - interface FoldCallback { + fun interface FoldCallback { fun onFoldUpdated(isFolded: Boolean) } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..61b0b40a55bfcf80103719ecd81e38fc307b3032 --- /dev/null +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt @@ -0,0 +1,93 @@ +/* + * 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.unfold.updates + +import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate +import javax.inject.Inject +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.callbackFlow + +/** + * Allows to subscribe to main events related to fold/unfold process such as hinge angle update, + * start folding/unfolding, screen availability + */ +interface FoldStateRepository { + /** Latest fold update, as described by [FoldStateProvider.FoldUpdate]. */ + val foldUpdate: Flow<FoldUpdate> + + /** Provides the hinge angle while the fold/unfold is in progress. */ + val hingeAngle: Flow<Float> + + enum class FoldUpdate { + START_OPENING, + START_CLOSING, + FINISH_HALF_OPEN, + FINISH_FULL_OPEN, + FINISH_CLOSED; + + companion object { + /** Maps the old [FoldStateProvider.FoldUpdate] to [FoldStateRepository.FoldUpdate]. */ + fun fromFoldUpdateId(@FoldStateProvider.FoldUpdate oldId: Int): FoldUpdate { + return when (oldId) { + FOLD_UPDATE_START_OPENING -> START_OPENING + FOLD_UPDATE_START_CLOSING -> START_CLOSING + FOLD_UPDATE_FINISH_HALF_OPEN -> FINISH_HALF_OPEN + FOLD_UPDATE_FINISH_FULL_OPEN -> FINISH_FULL_OPEN + FOLD_UPDATE_FINISH_CLOSED -> FINISH_CLOSED + else -> error("FoldUpdateNotFound") + } + } + } + } +} + +class FoldStateRepositoryImpl +@Inject +constructor( + private val foldStateProvider: FoldStateProvider, +) : FoldStateRepository { + + override val hingeAngle: Flow<Float> + get() = + callbackFlow { + val callback = + object : FoldStateProvider.FoldUpdatesListener { + override fun onHingeAngleUpdate(angle: Float) { + trySend(angle) + } + } + foldStateProvider.addCallback(callback) + awaitClose { foldStateProvider.removeCallback(callback) } + } + .buffer(capacity = Channel.CONFLATED) + + override val foldUpdate: Flow<FoldUpdate> + get() = + callbackFlow { + val callback = + object : FoldStateProvider.FoldUpdatesListener { + override fun onFoldUpdate(update: Int) { + trySend(FoldUpdate.fromFoldUpdateId(update)) + } + } + foldStateProvider.addCallback(callback) + awaitClose { foldStateProvider.removeCallback(callback) } + } + .buffer(capacity = Channel.CONFLATED) +} diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java index 3406102b28ac4d0fc6e1a5c149060a7d979ba70f..98421a9e1d3e62d39b54b6bdc6284199d6f0bfd2 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -367,11 +367,7 @@ public class WallpaperBackupAgent extends BackupAgent { ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); mSystemHasLiveComponent = wpService != null; - ComponentName kwpService = null; - boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled(); - if (lockscreenLiveWallpaper) { - kwpService = parseWallpaperComponent(infoStage, "kwp"); - } + ComponentName kwpService = parseWallpaperComponent(infoStage, "kwp"); mLockHasLiveComponent = kwpService != null; boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists(); @@ -381,17 +377,16 @@ public class WallpaperBackupAgent extends BackupAgent { // It is valid for the imagery to be absent; it means that we were not permitted // to back up the original image on the source device, or there was no user-supplied // wallpaper image present. - if (!lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich); if (lockImageStageExists) { restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK); } - if (lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich); + restoreFromStage(imageStage, infoStage, "wp", sysWhich); // And reset to the wallpaper service we should be using - if (lockscreenLiveWallpaper && mLockHasLiveComponent) { - updateWallpaperComponent(kwpService, false, FLAG_LOCK); + if (mLockHasLiveComponent) { + updateWallpaperComponent(kwpService, FLAG_LOCK); } - updateWallpaperComponent(wpService, !lockImageStageExists, sysWhich); + updateWallpaperComponent(wpService, sysWhich); } catch (Exception e) { Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage()); mEventLogger.onRestoreException(e); @@ -410,36 +405,24 @@ public class WallpaperBackupAgent extends BackupAgent { } @VisibleForTesting - void updateWallpaperComponent(ComponentName wpService, boolean applyToLock, int which) + void updateWallpaperComponent(ComponentName wpService, int which) throws IOException { - boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled(); if (servicePackageExists(wpService)) { Slog.i(TAG, "Using wallpaper service " + wpService); - if (lockscreenLiveWallpaper) { - mWallpaperManager.setWallpaperComponentWithFlags(wpService, which); - if ((which & FLAG_LOCK) != 0) { - mEventLogger.onLockLiveWallpaperRestored(wpService); - } - if ((which & FLAG_SYSTEM) != 0) { - mEventLogger.onSystemLiveWallpaperRestored(wpService); - } - return; - } - mWallpaperManager.setWallpaperComponent(wpService); - if (applyToLock) { - // We have a live wallpaper and no static lock image, - // allow live wallpaper to show "through" on lock screen. - mWallpaperManager.clear(FLAG_LOCK); + mWallpaperManager.setWallpaperComponentWithFlags(wpService, which); + if ((which & FLAG_LOCK) != 0) { mEventLogger.onLockLiveWallpaperRestored(wpService); } - mEventLogger.onSystemLiveWallpaperRestored(wpService); + if ((which & FLAG_SYSTEM) != 0) { + mEventLogger.onSystemLiveWallpaperRestored(wpService); + } } else { // If we've restored a live wallpaper, but the component doesn't exist, // we should log it as an error so we can easily identify the problem // in reports from users if (wpService != null) { // TODO(b/268471749): Handle delayed case - applyComponentAtInstall(wpService, applyToLock, which); + applyComponentAtInstall(wpService, which); Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. " + " Will try to apply later"); } @@ -579,21 +562,17 @@ public class WallpaperBackupAgent extends BackupAgent { // Intentionally blank } - private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock, - int which) { + private void applyComponentAtInstall(ComponentName componentName, int which) { PackageMonitor packageMonitor = getWallpaperPackageMonitor( - componentName, applyToLock, which); + componentName, which); packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true); } @VisibleForTesting - PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock, - int which) { + PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) { return new PackageMonitor() { @Override public void onPackageAdded(String packageName, int uid) { - boolean lockscreenLiveWallpaper = - mWallpaperManager.isLockscreenLiveWallpaperEnabled(); if (!isDeviceInRestore()) { // We don't want to reapply the wallpaper outside a restore. unregister(); @@ -601,9 +580,11 @@ public class WallpaperBackupAgent extends BackupAgent { // We have finished restore and not succeeded, so let's log that as an error. WallpaperEventLogger logger = new WallpaperEventLogger( mBackupManager.getDelayedRestoreLogger()); - logger.onSystemLiveWallpaperRestoreFailed( - WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED); - if (applyToLock) { + if ((which & FLAG_SYSTEM) != 0) { + logger.onSystemLiveWallpaperRestoreFailed( + WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED); + } + if ((which & FLAG_LOCK) != 0) { logger.onLockLiveWallpaperRestoreFailed( WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED); } @@ -614,37 +595,27 @@ public class WallpaperBackupAgent extends BackupAgent { if (componentName.getPackageName().equals(packageName)) { Slog.d(TAG, "Applying component " + componentName); - boolean success = lockscreenLiveWallpaper - ? mWallpaperManager.setWallpaperComponentWithFlags(componentName, which) - : mWallpaperManager.setWallpaperComponent(componentName); + boolean success = mWallpaperManager.setWallpaperComponentWithFlags( + componentName, which); WallpaperEventLogger logger = new WallpaperEventLogger( mBackupManager.getDelayedRestoreLogger()); if (success) { - if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) { + if ((which & FLAG_SYSTEM) != 0) { logger.onSystemLiveWallpaperRestored(componentName); } - if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) { + if ((which & FLAG_LOCK) != 0) { logger.onLockLiveWallpaperRestored(componentName); } } else { - if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) { + if ((which & FLAG_SYSTEM) != 0) { logger.onSystemLiveWallpaperRestoreFailed( WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION); } - if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) { + if ((which & FLAG_LOCK) != 0) { logger.onLockLiveWallpaperRestoreFailed( WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION); } } - if (applyToLock && !lockscreenLiveWallpaper) { - try { - mWallpaperManager.clear(FLAG_LOCK); - logger.onLockLiveWallpaperRestored(componentName); - } catch (IOException e) { - Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e); - logger.onLockLiveWallpaperRestoreFailed(e.getClass().getName()); - } - } // We're only expecting to restore the wallpaper component once. unregister(); mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger()); diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index dc1126edde410fd1924b9f363f99dd263f099c0e..4c224fb33b26dc3b65cd91638b6588b0cd112d8c 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -116,8 +116,6 @@ public class WallpaperBackupAgentTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - - when(mWallpaperManager.isLockscreenLiveWallpaperEnabled()).thenReturn(true); when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true); when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true); @@ -363,25 +361,19 @@ public class WallpaperBackupAgentTest { @Test public void testUpdateWallpaperComponent_doesApplyLater() throws IOException { mWallpaperBackupAgent.mIsDeviceInRestore = true; - mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, - /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM); + /* which */ FLAG_LOCK | FLAG_SYSTEM); // Imitate wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, /* uid */0); - if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - verify(mWallpaperManager, times(1)) - .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM); - verify(mWallpaperManager, never()) - .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM); - verify(mWallpaperManager, never()) - .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK); - verify(mWallpaperManager, never()).clear(anyInt()); - } else { - verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent); - verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK)); - } + verify(mWallpaperManager, times(1)) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM); + verify(mWallpaperManager, never()) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM); + verify(mWallpaperManager, never()) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK); + verify(mWallpaperManager, never()).clear(anyInt()); } @Test @@ -390,24 +382,19 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.mIsDeviceInRestore = true; mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, - /* applyToLock */ false, FLAG_SYSTEM); + /* which */ FLAG_SYSTEM); // Imitate wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, /* uid */0); - if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - verify(mWallpaperManager, times(1)) - .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM); - verify(mWallpaperManager, never()) - .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK); - verify(mWallpaperManager, never()) - .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM); - verify(mWallpaperManager, never()).clear(anyInt()); - } else { - verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent); - verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK)); - } + verify(mWallpaperManager, times(1)) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM); + verify(mWallpaperManager, never()) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK); + verify(mWallpaperManager, never()) + .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM); + verify(mWallpaperManager, never()).clear(anyInt()); } @Test @@ -416,7 +403,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.mIsDeviceInRestore = false; mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, - /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM); + /* which */ FLAG_LOCK | FLAG_SYSTEM); // Imitate wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, @@ -432,7 +419,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.mIsDeviceInRestore = false; mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, - /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM); + /* which */ FLAG_LOCK | FLAG_SYSTEM); // Imitate "wrong" wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"", @@ -622,7 +609,7 @@ public class WallpaperBackupAgentTest { } @Test - public void testOnRestore_systemWallpaperImgSuccess_logsSuccess() throws Exception { + public void testOnRestore_wallpaperImgSuccess_logsSuccess() throws Exception { mockStagedWallpaperFile(WALLPAPER_INFO_STAGE); mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE); mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, @@ -630,17 +617,16 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.onRestoreFinished(); + // wallpaper will be applied to home & lock screen, a success for both screens in expected DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); assertThat(result).isNotNull(); assertThat(result.getSuccessCount()).isEqualTo(1); - if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - result = getLoggingResult(WALLPAPER_IMG_LOCK, - mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); - assertThat(result).isNotNull(); - assertThat(result.getSuccessCount()).isEqualTo(1); - } + result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); } @Test @@ -758,7 +744,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager); mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, - /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM); + /* which */ FLAG_LOCK | FLAG_SYSTEM); // Imitate wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, /* uid */0); @@ -782,7 +768,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager); mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, - /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM); + /* which */ FLAG_LOCK | FLAG_SYSTEM); // Imitate wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, /* uid */0); @@ -804,7 +790,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager); mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, - /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM); + /* which */ FLAG_LOCK | FLAG_SYSTEM); // Imitate wallpaper component installation. mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE, @@ -938,10 +924,8 @@ public class WallpaperBackupAgentTest { } @Override - PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, - boolean applyToLock, int which) { - mWallpaperPackageMonitor = super.getWallpaperPackageMonitor( - componentName, applyToLock, which); + PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) { + mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, which); return mWallpaperPackageMonitor; } diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 1a735f89a2bc38109135e151fd60a48b8dab2358..75ecdb78fe007633be28309f63ebf71ecca3291a 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -1,38 +1,40 @@ package: "com.android.server.accessibility" +# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. + flag { - name: "proxy_use_apps_on_virtual_device_listener" + name: "add_window_token_without_lock" namespace: "accessibility" - description: "Fixes race condition described in b/286587811" - bug: "286587811" + description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock" + bug: "297972548" } flag { - name: "enable_magnification_multiple_finger_multiple_tap_gesture" + name: "deprecate_package_list_observer" namespace: "accessibility" - description: "Whether to enable multi-finger-multi-tap gesture for magnification" - bug: "257274411" + description: "Stops using the deprecated PackageListObserver." + bug: "304561459" } flag { - name: "enable_magnification_joystick" + name: "disable_continuous_shortcut_on_force_stop" namespace: "accessibility" - description: "Whether to enable joystick controls for magnification" - bug: "297211257" + description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts." + bug: "198018180" } flag { - name: "send_a11y_events_based_on_state" + name: "enable_magnification_joystick" namespace: "accessibility" - description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness." - bug: "295575684" + description: "Whether to enable joystick controls for magnification" + bug: "297211257" } flag { - name: "add_window_token_without_lock" + name: "enable_magnification_multiple_finger_multiple_tap_gesture" namespace: "accessibility" - description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock" - bug: "297972548" + description: "Whether to enable multi-finger-multi-tap gesture for magnification" + bug: "257274411" } flag { @@ -43,17 +45,17 @@ flag { } flag { - name: "disable_continuous_shortcut_on_force_stop" + name: "proxy_use_apps_on_virtual_device_listener" namespace: "accessibility" - description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts." - bug: "198018180" + description: "Fixes race condition described in b/286587811" + bug: "286587811" } flag { - name: "deprecate_package_list_observer" + name: "reduce_touch_exploration_sensitivity" namespace: "accessibility" - description: "Stops using the deprecated PackageListObserver." - bug: "304561459" + description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop." + bug: "303677860" } flag { @@ -64,8 +66,8 @@ flag { } flag { - name: "reduce_touch_exploration_sensitivity" + name: "send_a11y_events_based_on_state" namespace: "accessibility" - description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop." - bug: "303677860" + description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness." + bug: "295575684" } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index e65a185adfd2c53044f90c9809d55e34089e8c34..87f9cf10f824b928f5fca143488e27a70e9ac7fc 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -4929,8 +4929,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor( Settings.Secure.TOUCH_EXPLORATION_ENABLED); - private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor( - Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + private final Uri mMagnificationmSingleFingerTripleTapEnabledUri = Settings.Secure + .getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED); @@ -4987,7 +4987,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public void register(ContentResolver contentResolver) { contentResolver.registerContentObserver(mTouchExplorationEnabledUri, false, this, UserHandle.USER_ALL); - contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri, + contentResolver.registerContentObserver(mMagnificationmSingleFingerTripleTapEnabledUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mAutoclickEnabledUri, false, this, UserHandle.USER_ALL); @@ -5035,7 +5035,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (readTouchExplorationEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); } - } else if (mDisplayMagnificationEnabledUri.equals(uri)) { + } else if (mMagnificationmSingleFingerTripleTapEnabledUri.equals(uri)) { if (readMagnificationEnabledSettingsLocked(userState)) { onUserStateChangedLocked(userState); } diff --git a/services/core/Android.bp b/services/core/Android.bp index 898cdcc30e0f11edf7a719fb9683dd1f25739875..4dca5a5c807b8f5d481f1b151bdf179e09bf3bcf 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -148,6 +148,7 @@ java_library_static { "android.hardware.light-V2.0-java", "android.hardware.gnss-V2-java", "android.hardware.vibrator-V2-java", + "android.nfc.flags-aconfig-java", "app-compat-annotations", "framework-tethering.stubs.module_lib", "service-art.stubs.system_server", diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index beea063221fb197625e7388a5dbca80632f0550f..8624dd5f3e6bf1a091e3eeaf8ee6c59fb0e5a97a 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1236,7 +1236,7 @@ class StorageManagerService extends IStorageManager.Stub private void onUserStopped(int userId) { Slog.d(TAG, "onUserStopped " + userId); - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow"); try { mVold.onUserStopped(userId); @@ -1320,7 +1320,7 @@ class StorageManagerService extends IStorageManager.Stub unlockedUsers.add(userId); } } - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow"); for (Integer userId : unlockedUsers) { try { @@ -2341,7 +2341,7 @@ class StorageManagerService extends IStorageManager.Stub try { // TODO(b/135341433): Remove cautious logging when FUSE is stable Slog.i(TAG, "Mounting volume " + vol); - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow"); mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() { @Override @@ -2472,7 +2472,7 @@ class StorageManagerService extends IStorageManager.Stub final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1); @@ -2491,7 +2491,7 @@ class StorageManagerService extends IStorageManager.Stub final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1); @@ -2510,7 +2510,7 @@ class StorageManagerService extends IStorageManager.Stub final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow"); try { mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio); @@ -3620,7 +3620,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public ParcelFileDescriptor open() throws AppFuseMountException { - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#open might be slow"); try { final FileDescriptor fd = mVold.mountAppFuse(uid, mountId); @@ -3634,7 +3634,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public ParcelFileDescriptor openFile(int mountId, int fileId, int flags) throws AppFuseMountException { - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#openFile might be slow"); try { return new ParcelFileDescriptor( @@ -3646,7 +3646,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public void close() throws Exception { - Watchdog.getInstance().setOneOffTimeoutForMonitors( + Watchdog.getInstance().pauseWatchingMonitorsFor( SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow"); if (mMounted) { mVold.unmountAppFuse(uid, mountId); diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 55aa7164a67bbd2610f4c68032bf205fb4a7f1ed..003046ab884f7f784677490a31dca9328e664233 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -250,7 +250,7 @@ public class Watchdog implements Dumpable { private Monitor mCurrentMonitor; private long mStartTimeMillis; private int mPauseCount; - private long mOneOffTimeoutMillis; + private long mPauseEndTimeMillis; HandlerChecker(Handler handler, String name) { mHandler = handler; @@ -270,20 +270,19 @@ public class Watchdog implements Dumpable { * @param handlerCheckerTimeoutMillis the timeout to use for this run */ public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) { - if (mOneOffTimeoutMillis > 0) { - mWaitMaxMillis = mOneOffTimeoutMillis; - mOneOffTimeoutMillis = 0; - } else { - mWaitMaxMillis = handlerCheckerTimeoutMillis; - } + mWaitMaxMillis = handlerCheckerTimeoutMillis; if (mCompleted) { // Safe to update monitors in queue, Handler is not in the middle of work mMonitors.addAll(mMonitorQueue); mMonitorQueue.clear(); } + + long nowMillis = SystemClock.uptimeMillis(); + boolean isPaused = mPauseCount > 0 + || (mPauseEndTimeMillis > 0 && mPauseEndTimeMillis < nowMillis); if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) - || (mPauseCount > 0)) { + || isPaused) { // Don't schedule until after resume OR // If the target looper has recently been polling, then // there is no reason to enqueue our checker on it since that @@ -301,7 +300,8 @@ public class Watchdog implements Dumpable { mCompleted = false; mCurrentMonitor = null; - mStartTimeMillis = SystemClock.uptimeMillis(); + mStartTimeMillis = nowMillis; + mPauseEndTimeMillis = 0; mHandler.postAtFrontOfQueue(this); } @@ -360,20 +360,19 @@ public class Watchdog implements Dumpable { } /** - * Sets the timeout of the HandlerChecker for one run. - * - * <p>The current run will be ignored and the next run will be set to this timeout. + * Pauses the checks for the given time. * - * <p>If a one off timeout is already set, the maximum timeout will be used. + * <p>The current run will be ignored and another run will be scheduled after + * the given time. */ - public void setOneOffTimeoutLocked(int temporaryTimeoutMillis, String reason) { - mOneOffTimeoutMillis = Math.max(temporaryTimeoutMillis, mOneOffTimeoutMillis); + public void pauseForLocked(int pauseMillis, String reason) { + mPauseEndTimeMillis = SystemClock.uptimeMillis() + pauseMillis; // Mark as completed, because there's a chance we called this after the watchog // thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure // the next call to #getCompletionStateLocked for this checker returns 'COMPLETED' mCompleted = true; - Slog.i(TAG, "Extending timeout of HandlerChecker: " + mName + " for reason: " - + reason + ". New timeout: " + mOneOffTimeoutMillis); + Slog.i(TAG, "Pausing of HandlerChecker: " + mName + " for reason: " + + reason + ". Pause end time: " + mPauseEndTimeMillis); } /** Pause the HandlerChecker. */ @@ -623,34 +622,32 @@ public class Watchdog implements Dumpable { } /** - * Sets a one-off timeout for the next run of the watchdog for this thread. This is useful + * Pauses the checks of the watchdog for this thread. This is useful * to run a slow operation on one of the monitored thread. * - * <p>After the next run, the timeout will go back to the default value. - * - * <p>If the current thread has not been added to the Watchdog, this call is a no-op. - * - * <p>If a one-off timeout for the current thread is already, the max value will be used. + * <p>After the given time, the timeout will go back to the default value. + * <p>This method does not require resume to be called. */ - public void setOneOffTimeoutForCurrentThread(int oneOffTimeoutMillis, String reason) { + public void pauseWatchingCurrentThreadFor(int pauseMillis, String reason) { synchronized (mLock) { for (HandlerCheckerAndTimeout hc : mHandlerCheckers) { HandlerChecker checker = hc.checker(); if (Thread.currentThread().equals(checker.getThread())) { - checker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason); + checker.pauseForLocked(pauseMillis, reason); } } } } /** - * Sets a one-off timeout for the next run of the watchdog for the monitor thread. + * Pauses the checks of the watchdog for the monitor thread for the given time * - * <p>Simiar to {@link setOneOffTimeoutForCurrentThread} but used for monitors added through - * {@link #addMonitor} + * <p>Similar to {@link pauseWatchingCurrentThreadFor} but used for monitors added + * through {@link #addMonitor} + * <p>This method does not require resume to be called. */ - public void setOneOffTimeoutForMonitors(int oneOffTimeoutMillis, String reason) { - mMonitorChecker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason); + public void pauseWatchingMonitorsFor(int pauseMillis, String reason) { + mMonitorChecker.pauseForLocked(pauseMillis, reason); } /** @@ -664,7 +661,7 @@ public class Watchdog implements Dumpable { * adds another pause and will require an additional {@link #resumeCurrentThread} to resume. * * <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all - * pauses have been resumed. Prefer to use #setOneOffTimeoutForCurrentThread. + * pauses have been resumed. Prefer to use #pauseWatchingCurrentThreadFor. */ public void pauseWatchingCurrentThread(String reason) { synchronized (mLock) { diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java index 9b5f18caf71a42632bce63a4bccbfaebe7f27c4b..710278d6b3c604a7ad910d9261f53577d9560dfa 100644 --- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java +++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + import android.content.Context; import android.content.DialogInterface; import android.os.Handler; @@ -54,6 +56,7 @@ final class AppWaitingForDebuggerDialog extends BaseErrorDialog { setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app)); setTitle("Waiting For Debugger"); WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS; attrs.setTitle("Waiting For Debugger: " + app.info.processName); getWindow().setAttributes(attrs); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index e0e6cade5f2770643c72dee8d9c6fc3f023f6966..59d8e7e96ba62283dbb42366bdc96eeb01f9394b 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -869,6 +869,8 @@ public final class ProcessList { ApplicationExitInfo.REASON_LOW_MEMORY, ApplicationExitInfo.SUBREASON_OOM_KILL, "oom"); + + oomKill.logKillOccurred(); } } } diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index bb9ea285385b13c57839a8096b29e1d21cb4bbed..cbaf05bb4e231e788bcbd88b9bf70219b8d09de4 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -15,3 +15,10 @@ flag { description: "Feature flag for the ANR timer service" bug: "282428924" } + +flag { + name: "fgs_abuse_detection" + namespace: "backstage_power" + description: "Detect abusive FGS behavior for certain types (camera, mic, media, location)." + bug: "295545575" +} diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index ba43c8df92c5d9476a0b9b538e95383bcd1a92a4..292fc14ac6eb1e51e9edce1565b0952f4b1fc906 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -188,7 +188,7 @@ import java.util.Objects; * {@link AdiDeviceState#toPersistableString()}. */ public static int getPeristedMaxSize() { - return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1 + return 36; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1 + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1 + (SETTINGS_FIELD_SEPARATOR)5 */ } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 35260ed6f14800d4985d1ad7d3a3cabeaaac0518..7abd9c7f750b9aea2dd3d0f01775096b157c402d 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -39,10 +39,9 @@ import android.media.ISpatializerHeadTrackingCallback; import android.media.ISpatializerHeadTrackingModeCallback; import android.media.ISpatializerOutputCallback; import android.media.MediaMetrics; -import android.media.SpatializationLevel; -import android.media.SpatializationMode; import android.media.Spatializer; -import android.media.SpatializerHeadTrackingMode; +import android.media.audio.common.HeadTracking; +import android.media.audio.common.Spatialization; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.text.TextUtils; @@ -84,22 +83,22 @@ public class SpatializerHelper { /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) { { - append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); - append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); - append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL); + append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, Spatialization.Mode.TRANSAURAL); + append(AudioDeviceInfo.TYPE_WIRED_HEADSET, Spatialization.Mode.BINAURAL); + append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, Spatialization.Mode.BINAURAL); // assumption for A2DP: mostly headsets - append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL); - append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL); - append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL); - append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL); - append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); - append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL); - append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL); - append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL); - append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); - append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); + append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, Spatialization.Mode.BINAURAL); + append(AudioDeviceInfo.TYPE_DOCK, Spatialization.Mode.TRANSAURAL); + append(AudioDeviceInfo.TYPE_USB_ACCESSORY, Spatialization.Mode.TRANSAURAL); + append(AudioDeviceInfo.TYPE_USB_DEVICE, Spatialization.Mode.TRANSAURAL); + append(AudioDeviceInfo.TYPE_USB_HEADSET, Spatialization.Mode.BINAURAL); + append(AudioDeviceInfo.TYPE_LINE_ANALOG, Spatialization.Mode.TRANSAURAL); + append(AudioDeviceInfo.TYPE_LINE_DIGITAL, Spatialization.Mode.TRANSAURAL); + append(AudioDeviceInfo.TYPE_AUX_LINE, Spatialization.Mode.TRANSAURAL); + append(AudioDeviceInfo.TYPE_BLE_HEADSET, Spatialization.Mode.BINAURAL); + append(AudioDeviceInfo.TYPE_BLE_SPEAKER, Spatialization.Mode.TRANSAURAL); // assumption that BLE broadcast would be mostly consumed on headsets - append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL); + append(AudioDeviceInfo.TYPE_BLE_BROADCAST, Spatialization.Mode.BINAURAL); } }; @@ -226,12 +225,12 @@ public class SpatializerHelper { ArrayList<Integer> list = new ArrayList<>(0); for (byte value : values) { switch (value) { - case SpatializerHeadTrackingMode.OTHER: - case SpatializerHeadTrackingMode.DISABLED: + case HeadTracking.Mode.OTHER: + case HeadTracking.Mode.DISABLED: // not expected here, skip break; - case SpatializerHeadTrackingMode.RELATIVE_WORLD: - case SpatializerHeadTrackingMode.RELATIVE_SCREEN: + case HeadTracking.Mode.RELATIVE_WORLD: + case HeadTracking.Mode.RELATIVE_SCREEN: list.add(headTrackingModeTypeToSpatializerInt(value)); break; default: @@ -254,10 +253,10 @@ public class SpatializerHelper { byte[] spatModes = spat.getSupportedModes(); for (byte mode : spatModes) { switch (mode) { - case SpatializationMode.SPATIALIZER_BINAURAL: + case Spatialization.Mode.BINAURAL: mBinauralSupported = true; break; - case SpatializationMode.SPATIALIZER_TRANSAURAL: + case Spatialization.Mode.TRANSAURAL: mTransauralSupported = true; break; default: @@ -274,8 +273,8 @@ public class SpatializerHelper { // initialize list of compatible devices for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) { int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i); - if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) - || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL + if ((mode == (int) Spatialization.Mode.BINAURAL && mBinauralSupported) + || (mode == (int) Spatialization.Mode.TRANSAURAL && mTransauralSupported)) { mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i)); } @@ -577,9 +576,9 @@ public class SpatializerHelper { int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(), Integer.MIN_VALUE); - device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL + device.setSAEnabled(spatMode == Spatialization.Mode.BINAURAL ? mBinauralEnabledDefault - : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL + : spatMode == Spatialization.Mode.TRANSAURAL ? mTransauralEnabledDefault : false); device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault); @@ -629,9 +628,9 @@ public class SpatializerHelper { if (isBluetoothDevice(internalDeviceType)) return deviceType; final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); - if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) { + if (spatMode == Spatialization.Mode.TRANSAURAL) { return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; - } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) { + } else if (spatMode == Spatialization.Mode.BINAURAL) { return AudioDeviceInfo.TYPE_WIRED_HEADPHONES; } return AudioDeviceInfo.TYPE_UNKNOWN; @@ -690,8 +689,7 @@ public class SpatializerHelper { // since their physical characteristics are unknown if (deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_UNKNOWN || deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES) { - available = (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) - && mBinauralSupported; + available = (spatMode == Spatialization.Mode.BINAURAL) && mBinauralSupported; } else { available = false; } @@ -804,8 +802,8 @@ public class SpatializerHelper { // not be included. final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(), /*default when type not found*/ -1); - if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) - || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL + if ((modeForDevice == Spatialization.Mode.BINAURAL && mBinauralSupported) + || (modeForDevice == Spatialization.Mode.TRANSAURAL && mTransauralSupported)) { return true; } @@ -1479,7 +1477,7 @@ public class SpatializerHelper { } synchronized void onInitSensors() { - final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE); + final boolean init = mFeatureEnabled && (mSpatLevel != Spatialization.Level.NONE); final String action = init ? "initializing" : "releasing"; if (mSpat == null) { logloge("not " + action + " sensors, null spatializer"); @@ -1545,13 +1543,13 @@ public class SpatializerHelper { // SDK <-> AIDL converters private static int headTrackingModeTypeToSpatializerInt(byte mode) { switch (mode) { - case SpatializerHeadTrackingMode.OTHER: + case HeadTracking.Mode.OTHER: return Spatializer.HEAD_TRACKING_MODE_OTHER; - case SpatializerHeadTrackingMode.DISABLED: + case HeadTracking.Mode.DISABLED: return Spatializer.HEAD_TRACKING_MODE_DISABLED; - case SpatializerHeadTrackingMode.RELATIVE_WORLD: + case HeadTracking.Mode.RELATIVE_WORLD: return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; - case SpatializerHeadTrackingMode.RELATIVE_SCREEN: + case HeadTracking.Mode.RELATIVE_SCREEN: return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE; default: throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode)); @@ -1561,13 +1559,13 @@ public class SpatializerHelper { private static byte spatializerIntToHeadTrackingModeType(int sdkMode) { switch (sdkMode) { case Spatializer.HEAD_TRACKING_MODE_OTHER: - return SpatializerHeadTrackingMode.OTHER; + return HeadTracking.Mode.OTHER; case Spatializer.HEAD_TRACKING_MODE_DISABLED: - return SpatializerHeadTrackingMode.DISABLED; + return HeadTracking.Mode.DISABLED; case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: - return SpatializerHeadTrackingMode.RELATIVE_WORLD; + return HeadTracking.Mode.RELATIVE_WORLD; case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE: - return SpatializerHeadTrackingMode.RELATIVE_SCREEN; + return HeadTracking.Mode.RELATIVE_SCREEN; default: throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode)); } @@ -1575,11 +1573,11 @@ public class SpatializerHelper { private static int spatializationLevelToSpatializerInt(byte level) { switch (level) { - case SpatializationLevel.NONE: + case Spatialization.Level.NONE: return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; - case SpatializationLevel.SPATIALIZER_MULTICHANNEL: + case Spatialization.Level.MULTICHANNEL: return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL; - case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS: + case Spatialization.Level.BED_PLUS_OBJECTS: return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS; default: throw (new IllegalArgumentException("Unexpected spatializer level:" + level)); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 3d347bea6bae94302b8020935db898425057db3e..f9bc8dcc7d0bf1ca2770144308f949a3b4e082fa 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -53,6 +53,8 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.media.AudioManager; import android.nfc.INfcAdapter; +import android.nfc.NfcAdapter; +import android.nfc.NfcManager; import android.os.Binder; import android.os.Handler; import android.os.HandlerExecutor; @@ -163,10 +165,6 @@ public class CameraServiceProxy extends SystemService * SCALER_ROTATE_AND_CROP_NONE -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE */ - // Flags arguments to NFC adapter to enable/disable NFC - public static final int DISABLE_POLLING_FLAGS = 0x1000; - public static final int ENABLE_POLLING_FLAGS = 0x0000; - // Handler message codes private static final int MSG_SWITCH_USER = 1; private static final int MSG_NOTIFY_DEVICE_STATE = 2; @@ -216,7 +214,6 @@ public class CameraServiceProxy extends SystemService private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>(); private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc"; - private static final String NFC_SERVICE_BINDER_NAME = "nfc"; private static final IBinder nfcInterfaceToken = new Binder(); private final boolean mNotifyNfc; @@ -1274,8 +1271,13 @@ public class CameraServiceProxy extends SystemService } } - private void notifyNfcService(boolean enablePolling) { - + // TODO(b/303286040): Remove the raw INfcAdapter usage once |ENABLE_NFC_MAINLINE_FLAG| is + // rolled out. + private static final String NFC_SERVICE_BINDER_NAME = "nfc"; + // Flags arguments to NFC adapter to enable/disable NFC + public static final int DISABLE_POLLING_FLAGS = 0x1000; + public static final int ENABLE_POLLING_FLAGS = 0x0000; + private void setNfcReaderModeUsingINfcAdapter(boolean enablePolling) { IBinder nfcServiceBinder = getBinderService(NFC_SERVICE_BINDER_NAME); if (nfcServiceBinder == null) { Slog.w(TAG, "Could not connect to NFC service to notify it of camera state"); @@ -1291,6 +1293,25 @@ public class CameraServiceProxy extends SystemService } } + private void notifyNfcService(boolean enablePolling) { + if (android.nfc.Flags.enableNfcMainline()) { + NfcManager nfcManager = mContext.getSystemService(NfcManager.class); + if (nfcManager == null) { + Slog.w(TAG, "Could not connect to NFC service to notify it of camera state"); + return; + } + NfcAdapter nfcAdapter = nfcManager.getDefaultAdapter(); + if (nfcAdapter == null) { + Slog.w(TAG, "Could not connect to NFC service to notify it of camera state"); + return; + } + if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling); + nfcAdapter.setReaderMode(enablePolling); + } else { + setNfcReaderModeUsingINfcAdapter(enablePolling); + } + } + private static int[] toArray(Collection<Integer> c) { int len = c.size(); int[] ret = new int[len]; diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java index 425a1af1bbaa3ef87dfa4fc68f03d082de354e42..669580189d7c402f842018c52f980149f25f2c06 100644 --- a/services/core/java/com/android/server/display/DisplayBrightnessState.java +++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java @@ -27,6 +27,8 @@ import java.util.Objects; * the DisplayBrightnessModeStrategies when updating the brightness. */ public final class DisplayBrightnessState { + public static final float CUSTOM_ANIMATION_RATE_NOT_SET = -1f; + private final float mBrightness; private final float mSdrBrightness; @@ -37,6 +39,8 @@ public final class DisplayBrightnessState { private final boolean mIsSlowChange; + private final float mCustomAnimationRate; + private DisplayBrightnessState(Builder builder) { mBrightness = builder.getBrightness(); mSdrBrightness = builder.getSdrBrightness(); @@ -45,6 +49,7 @@ public final class DisplayBrightnessState { mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness(); mIsSlowChange = builder.isSlowChange(); mMaxBrightness = builder.getMaxBrightness(); + mCustomAnimationRate = builder.getCustomAnimationRate(); } /** @@ -97,7 +102,12 @@ public final class DisplayBrightnessState { return mMaxBrightness; } - + /** + * @return custom animation rate + */ + public float getCustomAnimationRate() { + return mCustomAnimationRate; + } @Override public String toString() { @@ -112,6 +122,7 @@ public final class DisplayBrightnessState { stringBuilder.append(getShouldUseAutoBrightness()); stringBuilder.append("\n isSlowChange:").append(mIsSlowChange); stringBuilder.append("\n maxBrightness:").append(mMaxBrightness); + stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate); return stringBuilder.toString(); } @@ -137,13 +148,14 @@ public final class DisplayBrightnessState { otherState.getDisplayBrightnessStrategyName()) && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness() && mIsSlowChange == otherState.isSlowChange() - && mMaxBrightness == otherState.getMaxBrightness(); + && mMaxBrightness == otherState.getMaxBrightness() + && mCustomAnimationRate == otherState.getCustomAnimationRate(); } @Override public int hashCode() { return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason, - mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness); + mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate); } /** @@ -164,6 +176,7 @@ public final class DisplayBrightnessState { private boolean mShouldUseAutoBrightness; private boolean mIsSlowChange; private float mMaxBrightness; + private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET; /** * Create a builder starting with the values from the specified {@link @@ -180,6 +193,7 @@ public final class DisplayBrightnessState { builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness()); builder.setIsSlowChange(state.isSlowChange()); builder.setMaxBrightness(state.getMaxBrightness()); + builder.setCustomAnimationRate(state.getCustomAnimationRate()); return builder; } @@ -303,6 +317,22 @@ public final class DisplayBrightnessState { return mMaxBrightness; } + + /** + * See {@link DisplayBrightnessState#getCustomAnimationRate()}. + */ + public Builder setCustomAnimationRate(float animationRate) { + this.mCustomAnimationRate = animationRate; + return this; + } + + /** + * See {@link DisplayBrightnessState#getCustomAnimationRate()}. + */ + public float getCustomAnimationRate() { + return mCustomAnimationRate; + } + /** * This is used to construct an immutable DisplayBrightnessState object from its builder */ diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index cb2302a602485f4d474be0745c4d1226a8c84365..e5f01df75fcdfa79615a2cb261454802d4cd4db4 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -586,7 +586,8 @@ public final class DisplayManagerService extends SystemService { mSystemReady = false; mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null); - mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName); + // TODO: b/306170135 - return TextUtils package name check instead + mExtraDisplayEventLogging = true; } public void setupSchedulerPolicies() { @@ -2306,8 +2307,10 @@ public final class DisplayManagerService extends SystemService { @GuardedBy("mSyncRoot") private boolean hdrConversionIntroducesLatencyLocked() { + HdrConversionMode mode = getHdrConversionModeSettingInternal(); final int preferredHdrOutputType = - getHdrConversionModeSettingInternal().getPreferredHdrOutputType(); + mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM + ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType(); if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) { int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency(); return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType); @@ -2588,16 +2591,14 @@ public final class DisplayManagerService extends SystemService { // TODO(b/202378408) set minimal post-processing only if it's supported once we have a // separate API for disabling on-device processing. boolean mppRequest = isMinimalPostProcessingAllowed() && preferMinimalPostProcessing; - boolean disableHdrConversionForLatency = false; + // If HDR conversion introduces latency, disable that in case minimal + // post-processing is requested + boolean disableHdrConversionForLatency = + mppRequest ? hdrConversionIntroducesLatencyLocked() : false; if (display.getRequestedMinimalPostProcessingLocked() != mppRequest) { display.setRequestedMinimalPostProcessingLocked(mppRequest); shouldScheduleTraversal = true; - // If HDR conversion introduces latency, disable that in case minimal - // post-processing is requested - if (mppRequest) { - disableHdrConversionForLatency = hdrConversionIntroducesLatencyLocked(); - } } if (shouldScheduleTraversal) { @@ -2933,8 +2934,15 @@ public final class DisplayManagerService extends SystemService { // Send a display event if the display is enabled private void sendDisplayEventIfEnabledLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) { + final boolean displayIsEnabled = display.isEnabledLocked(); + if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) { + Trace.instant(Trace.TRACE_TAG_POWER, + "sendDisplayEventLocked#event=" + event + ",displayEnabled=" + + displayIsEnabled); + } + // Only send updates outside of DisplayManagerService for enabled displays - if (display.isEnabledLocked()) { + if (displayIsEnabled) { sendDisplayEventLocked(display, event); } else if (mExtraDisplayEventLogging) { Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display); @@ -2991,7 +2999,11 @@ public final class DisplayManagerService extends SystemService { + displayId + ", event=" + event + (uids != null ? ", uids=" + uids : "")); } - + if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) { + Trace.instant(Trace.TRACE_TAG_POWER, + "deliverDisplayEvent#event=" + event + ",displayId=" + + displayId + (uids != null ? ", uids=" + uids : "")); + } // Grab the lock and copy the callbacks. final int count; synchronized (mSyncRoot) { @@ -3031,7 +3043,8 @@ public final class DisplayManagerService extends SystemService { } private boolean extraLogging(String packageName) { - return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName); + // TODO: b/306170135 - return mExtraDisplayLoggingPackageName & package name check instead + return true; } // Runs on Handler thread. @@ -3498,10 +3511,13 @@ public final class DisplayManagerService extends SystemService { @Override public void binderDied() { - if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals( - mPackageName)) { + if (DEBUG || extraLogging(mPackageName)) { Slog.d(TAG, "Display listener for pid " + mPid + " died."); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) { + Trace.instant(Trace.TRACE_TAG_POWER, + "displayManagerBinderDied#mPid=" + mPid); + } onCallbackDied(this); } @@ -3510,11 +3526,15 @@ public final class DisplayManagerService extends SystemService { */ public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) { if (!shouldSendEvent(event)) { - if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals( - mPackageName)) { + if (extraLogging(mPackageName)) { Slog.i(TAG, "Not sending displayEvent: " + event + " due to mask:" + mEventsMask); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) { + Trace.instant(Trace.TRACE_TAG_POWER, + "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask=" + + mEventsMask); + } return true; } diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index d97c8e71c73c34bdc6989930f3a539ba79e494d9..8c39d7de54f7923abcde3568be395c629bfb04f7 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -16,6 +16,13 @@ package com.android.server.display; +import static android.view.Display.TYPE_EXTERNAL; +import static android.view.Display.TYPE_INTERNAL; +import static android.view.Display.TYPE_OVERLAY; +import static android.view.Display.TYPE_UNKNOWN; +import static android.view.Display.TYPE_VIRTUAL; +import static android.view.Display.TYPE_WIFI; + import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; @@ -26,7 +33,10 @@ import android.view.Display; import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Locale; class DisplayManagerShellCommand extends ShellCommand { private static final String TAG = "DisplayManagerShellCommand"; @@ -153,9 +163,12 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" Sets the user disabled HDR types as TYPES"); pw.println(" get-user-disabled-hdr-types"); pw.println(" Returns the user disabled HDR types"); - pw.println(" get-displays [CATEGORY]"); + pw.println(" get-displays [-c|--category CATEGORY] [-i|--ids-only] [-t|--type TYPE]"); + pw.println(" [CATEGORY]"); pw.println(" Returns the current displays. Can specify string category among"); pw.println(" DisplayManager.DISPLAY_CATEGORY_*; must use the actual string value."); + pw.println(" Can choose to print only the ids of the displays. " + "Can filter by"); + pw.println(" display types. For example, '--type external'"); pw.println(" dock"); pw.println(" Sets brightness to docked + idle screen brightness mode"); pw.println(" undock"); @@ -171,17 +184,94 @@ class DisplayManagerShellCommand extends ShellCommand { } private int getDisplays() { - String category = getNextArg(); + String opt = "", requestedType, category = null; + PrintWriter out = getOutPrintWriter(); + + List<Integer> displayTypeList = new ArrayList<>(); + boolean showIdsOnly = false, filterByType = false; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-i": + case "--ids-only": + showIdsOnly = true; + break; + case "-t": + case "--type": + requestedType = getNextArgRequired(); + int displayType = getType(requestedType, out); + if (displayType == -1) { + return 1; + } + displayTypeList.add(displayType); + filterByType = true; + break; + case "-c": + case "--category": + if (category != null) { + out.println("Error: the category has been specified more than one time. " + + "Please select only one category."); + return 1; + } + category = getNextArgRequired(); + break; + case "": + break; + default: + out.println("Error: unknown option '" + opt + "'"); + return 1; + } + } + + String lastCategoryArgument = getNextArg(); + if (lastCategoryArgument != null) { + if (category != null) { + out.println("Error: the category has been specified both with the -c option and " + + "the positional argument. Please select only one category."); + return 1; + } + category = lastCategoryArgument; + } + DisplayManager dm = mService.getContext().getSystemService(DisplayManager.class); Display[] displays = dm.getDisplays(category); - PrintWriter out = getOutPrintWriter(); - out.println("Displays:"); + + if (filterByType) { + displays = Arrays.stream(displays).filter(d -> displayTypeList.contains(d.getType())) + .toArray(Display[]::new); + } + + if (!showIdsOnly) { + out.println("Displays:"); + } for (int i = 0; i < displays.length; i++) { - out.println(" " + displays[i]); + out.println((showIdsOnly ? displays[i].getDisplayId() : displays[i])); } return 0; } + private int getType(String type, PrintWriter out) { + type = type.toUpperCase(Locale.ENGLISH); + switch (type) { + case "UNKNOWN": + return TYPE_UNKNOWN; + case "INTERNAL": + return TYPE_INTERNAL; + case "EXTERNAL": + return TYPE_EXTERNAL; + case "WIFI": + return TYPE_WIFI; + case "OVERLAY": + return TYPE_OVERLAY; + case "VIRTUAL": + return TYPE_VIRTUAL; + default: + out.println("Error: argument for display type should be " + + "one of 'UNKNOWN', 'INTERNAL', 'EXTERNAL', 'WIFI', 'OVERLAY', 'VIRTUAL', " + + "but got '" + type + "' instead."); + return -1; + } + } + private int showNotification() { final String notificationType = getNextArg(); if (notificationType == null) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 7d9c0182a6913d67422d003862103c7cdfa3701c..0f0002781f93a9c10f24ebef0bcfce3454f48f1c 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -561,8 +561,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal brightnessSetting, () -> postBrightnessChangeRunnable(), new HandlerExecutor(mHandler)); - mBrightnessClamperController = new BrightnessClamperController(mHandler, - modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData( + mBrightnessClamperController = mInjector.getBrightnessClamperController( + mHandler, modeChangeCallback::run, + new BrightnessClamperController.DisplayDeviceData( mUniqueDisplayId, mThermalBrightnessThrottlingDataId, logicalDisplay.getPowerThrottlingDataIdLocked(), @@ -1353,6 +1354,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal float rawBrightnessState = displayBrightnessState.getBrightness(); mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason()); boolean slowChange = displayBrightnessState.isSlowChange(); + // custom transition duration + float customAnimationRate = displayBrightnessState.getCustomAnimationRate(); // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor // doesn't yet have a valid lux value to use with auto-brightness. @@ -1485,6 +1488,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal brightnessState = clampedState.getBrightness(); slowChange = clampedState.isSlowChange(); + // faster rate wins, at this point customAnimationRate == -1, strategy does not control + // customAnimationRate. Should be revisited if strategy start setting this value + customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate()); mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier()); if (updateScreenBrightnessSetting) { @@ -1553,9 +1559,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // allowed range. float animateValue = clampScreenBrightness(brightnessState); - // custom transition duration - float customTransitionRate = -1f; - // If there are any HDR layers on the screen, we have a special brightness value that we // use instead. We still preserve the calculated brightness for Standard Dynamic Range // (SDR) layers, but the main brightness value will be the one for HDR. @@ -1570,10 +1573,21 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // We want to scale HDR brightness level with the SDR level, we also need to restore // SDR brightness immediately when entering dim or low power mode. animateValue = mBrightnessRangeController.getHdrBrightnessValue(); - customTransitionRate = mBrightnessRangeController.getHdrTransitionRate(); + customAnimationRate = Math.max(customAnimationRate, + mBrightnessRangeController.getHdrTransitionRate()); mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR); } + // if doze or suspend state is requested, we want to finish brightnes animation fast + // to allow state animation to start + if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE + && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN // dozing + || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND + || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) { + customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; + slowChange = false; + } + final float currentBrightness = mPowerState.getScreenBrightness(); final float currentSdrBrightness = mPowerState.getSdrScreenBrightness(); @@ -1601,9 +1615,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal if (skipAnimation) { animateScreenBrightness(animateValue, sdrAnimateValue, SCREEN_ANIMATION_RATE_MINIMUM); - } else if (customTransitionRate > 0) { + } else if (customAnimationRate > 0) { animateScreenBrightness(animateValue, sdrAnimateValue, - customTransitionRate, /* ignoreAnimationLimits = */true); + customAnimationRate, /* ignoreAnimationLimits = */true); } else { boolean isIncreasing = animateValue > currentBrightness; final float rampSpeed; @@ -3059,6 +3073,15 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info); } + BrightnessClamperController getBrightnessClamperController(Handler handler, + BrightnessClamperController.ClamperChangeListener clamperChangeListener, + BrightnessClamperController.DisplayDeviceData data, Context context, + DisplayManagerFlags flags) { + + return new BrightnessClamperController(handler, clamperChangeListener, data, context, + flags); + } + DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, SensorManager sensorManager, Resources resources) { return DisplayWhiteBalanceFactory.create(handler, diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java index 68f72d3b085a361e82a91c24fbf23cf153e63d97..dfcda40d8e3c8167f99cdad158f2d222021beda3 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -19,6 +19,8 @@ package com.android.server.display.brightness.clamper; import android.annotation.NonNull; import android.os.PowerManager; +import com.android.server.display.DisplayBrightnessState; + import java.io.PrintWriter; /** @@ -33,6 +35,10 @@ abstract class BrightnessClamper<T> { return mBrightnessCap; } + float getCustomAnimationRate() { + return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; + } + boolean isActive() { return mIsActive; } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 787f786a97dadfd3b0eeab539497d893d46d543d..b57491949196f7eadcec501becda79ac36bcb4de 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -51,6 +51,7 @@ import java.util.concurrent.Executor; */ public class BrightnessClamperController { private static final String TAG = "BrightnessClamperController"; + private final DeviceConfigParameterProvider mDeviceConfigParameterProvider; private final Handler mHandler; private final ClamperChangeListener mClamperChangeListenerExternal; @@ -60,6 +61,8 @@ public class BrightnessClamperController { private final List<BrightnessModifier> mModifiers; private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener; private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; + + private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; @Nullable private Type mClamperType = null; private boolean mClamperApplied = false; @@ -113,6 +116,7 @@ public class BrightnessClamperController { builder.setIsSlowChange(slowChange); builder.setBrightness(cappedBrightness); builder.setMaxBrightness(mBrightnessCap); + builder.setCustomAnimationRate(mCustomAnimationRate); if (mClamperType != null) { builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED); @@ -182,6 +186,7 @@ public class BrightnessClamperController { private void recalculateBrightnessCap() { float brightnessCap = PowerManager.BRIGHTNESS_MAX; Type clamperType = null; + float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; BrightnessClamper<?> minClamper = mClampers.stream() .filter(BrightnessClamper::isActive) @@ -191,11 +196,14 @@ public class BrightnessClamperController { if (minClamper != null) { brightnessCap = minClamper.getBrightnessCap(); clamperType = minClamper.getType(); + customAnimationRate = minClamper.getCustomAnimationRate(); } - if (mBrightnessCap != brightnessCap || mClamperType != clamperType) { + if (mBrightnessCap != brightnessCap || mClamperType != clamperType + || mCustomAnimationRate != customAnimationRate) { mBrightnessCap = brightnessCap; mClamperType = clamperType; + mCustomAnimationRate = customAnimationRate; mClamperChangeListenerExternal.onChanged(); } diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java index 5d5d59bcb6ef8cab8a046fbbb38ef03183319a73..8cb334dc2260d173373ee08e03507e18605ce707 100644 --- a/services/core/java/com/android/server/media/AudioAttributesUtils.java +++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java @@ -23,6 +23,8 @@ import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.MediaRoute2Info; +import com.android.media.flags.Flags; + /* package */ final class AudioAttributesUtils { /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder() @@ -36,6 +38,14 @@ import android.media.MediaRoute2Info; @MediaRoute2Info.Type /* package */ static int mapToMediaRouteType( @NonNull AudioDeviceAttributes audioDeviceAttributes) { + if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) { + switch (audioDeviceAttributes.getType()) { + case AudioDeviceInfo.TYPE_HDMI_ARC: + return MediaRoute2Info.TYPE_HDMI_ARC; + case AudioDeviceInfo.TYPE_HDMI_EARC: + return MediaRoute2Info.TYPE_HDMI_EARC; + } + } switch (audioDeviceAttributes.getType()) { case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: @@ -64,7 +74,6 @@ import android.media.MediaRoute2Info; } } - /* package */ static boolean isDeviceOutputAttributes( @Nullable AudioDeviceAttributes audioDeviceAttributes) { if (audioDeviceAttributes == null) { diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 33190ade4f42cfc2b0d685c91bb6ae9212d39165..360a6a721988c934232511fa793472b020a4188f 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -22,6 +22,8 @@ import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK; import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_DOCK; import static android.media.MediaRoute2Info.TYPE_HDMI; +import static android.media.MediaRoute2Info.TYPE_HDMI_ARC; +import static android.media.MediaRoute2Info.TYPE_HDMI_EARC; import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; @@ -160,7 +162,6 @@ import java.util.Objects; @NonNull private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) { int name = R.string.default_audio_route_name; - switch (type) { case TYPE_WIRED_HEADPHONES: case TYPE_WIRED_HEADSET: @@ -170,6 +171,8 @@ import java.util.Objects; name = R.string.default_audio_route_name_dock_speakers; break; case TYPE_HDMI: + case TYPE_HDMI_ARC: + case TYPE_HDMI_EARC: name = R.string.default_audio_route_name_external_device; break; case TYPE_USB_DEVICE: @@ -211,6 +214,8 @@ import java.util.Objects; case TYPE_WIRED_HEADSET: case TYPE_DOCK: case TYPE_HDMI: + case TYPE_HDMI_ARC: + case TYPE_HDMI_EARC: case TYPE_USB_DEVICE: return true; default: diff --git a/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..5bad06777328bd6791c5443dbf49bcb809a29877 --- /dev/null +++ b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media.projection; + +import com.android.internal.util.FrameworkStatsLog; + +/** Wrapper around {@link FrameworkStatsLog} */ +public class FrameworkStatsLogWrapper { + + /** Wrapper around {@link FrameworkStatsLog#write}. */ + public void write( + int code, + int sessionId, + int state, + int previousState, + int hostUid, + int targetUid, + int timeSinceLastActive, + int creationSource) { + FrameworkStatsLog.write( + code, + sessionId, + state, + previousState, + hostUid, + targetUid, + timeSinceLastActive, + creationSource); + } +} diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 8cbc368467bb96a6456ba282a08f88b72a1fed09..ce35a61143893e5a1fab2e110dc85ffac96307f2 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -162,7 +162,7 @@ public final class MediaProjectionManagerService extends SystemService mWmInternal = LocalServices.getService(WindowManagerInternal.class); mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); mMediaRouterCallback = new MediaRouterCallback(); - mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(); + mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context); Watchdog.getInstance().addMonitor(this); } @@ -197,8 +197,8 @@ public final class MediaProjectionManagerService extends SystemService return Looper.getMainLooper(); } - MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { - return MediaProjectionMetricsLogger.getInstance(); + MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) { + return MediaProjectionMetricsLogger.getInstance(context); } } @@ -293,6 +293,12 @@ public final class MediaProjectionManagerService extends SystemService private void stopProjectionLocked(final MediaProjection projection) { Slog.d(TAG, "Content Recording: Stopped active MediaProjection and " + "dispatching stop to callbacks"); + ContentRecordingSession session = projection.mSession; + int targetUid = + session != null + ? session.getTargetUid() + : ContentRecordingSession.TARGET_UID_UNKNOWN; + mMediaProjectionMetricsLogger.logStopped(projection.uid, targetUid); mProjectionToken = null; mProjectionGrant = null; dispatchStop(projection); @@ -452,6 +458,11 @@ public final class MediaProjectionManagerService extends SystemService .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); } + @VisibleForTesting + void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) { + mMediaProjectionMetricsLogger.logInitiated(hostUid, sessionCreationSource); + } + /** * Handles result of dialog shown from * {@link BinderService#buildReviewGrantedConsentIntentLocked()}. @@ -841,6 +852,19 @@ public final class MediaProjectionManagerService extends SystemService } } + @Override // Binder call + @EnforcePermission(MANAGE_MEDIA_PROJECTION) + public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) { + notifyPermissionRequestInitiated_enforcePermission(); + final long token = Binder.clearCallingIdentity(); + try { + MediaProjectionManagerService.this.notifyPermissionRequestInitiated( + hostUid, sessionCreationSource); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override // Binder call public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java index f18ecad09c42e64e4ba032cd384b57c4a9414ca9..c8b932ab6ccc748910670af425cb42591d0e4bbd 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java @@ -16,35 +16,127 @@ package com.android.server.media.projection; +import android.content.Context; import com.android.internal.util.FrameworkStatsLog; -/** - * Class for emitting logs describing a MediaProjection session. - */ +import java.time.Duration; + +/** Class for emitting logs describing a MediaProjection session. */ public class MediaProjectionMetricsLogger { + private static final int TARGET_UID_UNKNOWN = -2; + private static final int TIME_SINCE_LAST_ACTIVE_UNKNOWN = -1; + private static MediaProjectionMetricsLogger sSingleton = null; - public static MediaProjectionMetricsLogger getInstance() { + private final FrameworkStatsLogWrapper mFrameworkStatsLogWrapper; + private final MediaProjectionSessionIdGenerator mSessionIdGenerator; + private final MediaProjectionTimestampStore mTimestampStore; + + private int mPreviousState = + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN; + + MediaProjectionMetricsLogger( + FrameworkStatsLogWrapper frameworkStatsLogWrapper, + MediaProjectionSessionIdGenerator sessionIdGenerator, + MediaProjectionTimestampStore timestampStore) { + mFrameworkStatsLogWrapper = frameworkStatsLogWrapper; + mSessionIdGenerator = sessionIdGenerator; + mTimestampStore = timestampStore; + } + + /** Returns a singleton instance of {@link MediaProjectionMetricsLogger}. */ + public static MediaProjectionMetricsLogger getInstance(Context context) { if (sSingleton == null) { - sSingleton = new MediaProjectionMetricsLogger(); + sSingleton = + new MediaProjectionMetricsLogger( + new FrameworkStatsLogWrapper(), + MediaProjectionSessionIdGenerator.getInstance(context), + MediaProjectionTimestampStore.getInstance(context)); } return sSingleton; } - void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) { + /** + * Logs that the media projection session was initiated by the app requesting the user's consent + * to capture. Should be sent even if the permission dialog is not shown. + * + * @param hostUid UID of the package that initiates MediaProjection. + * @param sessionCreationSource Where this session started. One of: + * <ul> + * <li>{@link + * FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP} + * <li>{@link + * FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST} + * <li>{@link + * FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER} + * <li>{@link + * FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN} + * </ul> + */ + public void logInitiated(int hostUid, int sessionCreationSource) { + Duration durationSinceLastActiveSession = mTimestampStore.timeSinceLastActiveSession(); + int timeSinceLastActiveInSeconds = + durationSinceLastActiveSession == null + ? TIME_SINCE_LAST_ACTIVE_UNKNOWN + : (int) durationSinceLastActiveSession.toSeconds(); + write( + mSessionIdGenerator.createAndGetNewSessionId(), + FrameworkStatsLog + .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED, + hostUid, + TARGET_UID_UNKNOWN, + timeSinceLastActiveInSeconds, + sessionCreationSource); + } + + /** Logs that the capturing stopped, either normally or because of error. */ + public void logStopped(int hostUid, int targetUid) { + write( + mSessionIdGenerator.getCurrentSessionId(), + FrameworkStatsLog + .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED, + hostUid, + targetUid, + TIME_SINCE_LAST_ACTIVE_UNKNOWN, + FrameworkStatsLog + .MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + mTimestampStore.registerActiveSessionEnded(); + } + + public void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) { write(hostUid, state, sessionCreationSource); } private void write(int hostUid, int state, int sessionCreationSource) { - FrameworkStatsLog.write( + mFrameworkStatsLogWrapper.write( /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED, /* session_id */ 123, /* state */ state, - /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN, + /* previous_state */ FrameworkStatsLog + .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN, /* host_uid */ hostUid, /* target_uid */ -1, /* time_since_last_active */ 0, /* creation_source */ sessionCreationSource); } + + private void write( + int sessionId, + int state, + int hostUid, + int targetUid, + int timeSinceLastActive, + int creationSource) { + mFrameworkStatsLogWrapper.write( + /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED, + sessionId, + state, + mPreviousState, + hostUid, + targetUid, + timeSinceLastActive, + creationSource); + mPreviousState = state; + } } diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java index ff70cb35f9dcc207d098bac1838e35b77d3e94a9..244de0b3dd99105d4e359f6cb4cc09ce396cc494 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java @@ -47,8 +47,11 @@ public class MediaProjectionSessionIdGenerator { if (sInstance == null) { File preferencesFile = new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME); + // Needed as this class is instantiated before the device is unlocked. + Context directBootContext = context.createDeviceProtectedStorageContext(); SharedPreferences preferences = - context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE); + directBootContext.getSharedPreferences( + preferencesFile, Context.MODE_PRIVATE); sInstance = new MediaProjectionSessionIdGenerator(preferences); } return sInstance; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java index 4026d0c43484b7fe8eb1da5c29cc699a8df2567a..bfec58c8983db7034da395a7653d980b170c657d 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java @@ -59,8 +59,11 @@ public class MediaProjectionTimestampStore { if (sInstance == null) { File preferencesFile = new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME); + // Needed as this class is instantiated before the device is unlocked. + Context directBootContext = context.createDeviceProtectedStorageContext(); SharedPreferences preferences = - context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE); + directBootContext.getSharedPreferences( + preferencesFile, Context.MODE_PRIVATE); sInstance = new MediaProjectionTimestampStore(preferences, InstantSource.system()); } return sInstance; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b4d36db96c01312b7dd09d8e0a20d386f39f94fe..7ca56990f2d05b1e47eeacfbb3c5687b31ff1935 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -71,6 +71,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; +import static android.os.Flags.allowPrivateProfile; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; @@ -289,7 +290,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.IPlatformCompat; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; -import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; @@ -1179,7 +1179,7 @@ public class NotificationManagerService extends SystemService { @Override public void onSetDisabled(int status) { synchronized (mNotificationLock) { - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.updateDisableNotificationEffectsLocked(status); } else { mDisableNotificationEffects = @@ -1325,7 +1325,7 @@ public class NotificationManagerService extends SystemService { public void clearEffects() { synchronized (mNotificationLock) { if (DBG) Slog.d(TAG, "clearEffects"); - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.clearAttentionEffects(); } else { clearSoundLocked(); @@ -1554,8 +1554,7 @@ public class NotificationManagerService extends SystemService { int changedFlags = data.getFlags() ^ flags; if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) { // Suppress notification flag changed, clear any effects - if (mFlagResolver.isEnabled( - NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.clearEffectsLocked(key); } else { clearEffectsLocked(key); @@ -1904,7 +1903,7 @@ public class NotificationManagerService extends SystemService { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (!Flags.refactorAttentionHelper()) { if (action.equals(Intent.ACTION_SCREEN_ON)) { // Keep track of screen on/off state, but do not turn off the notification light // until user passes through the lock screen or views the notification. @@ -1931,7 +1930,8 @@ public class NotificationManagerService extends SystemService { cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle, REASON_USER_STOPPED); } - } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) { + } else if ( + isProfileUnavailable(action)) { int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) { cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle, @@ -1982,6 +1982,12 @@ public class NotificationManagerService extends SystemService { } } } + + private boolean isProfileUnavailable(String action) { + return allowPrivateProfile() ? + action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) : + action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + } }; private final class SettingsObserver extends ContentObserver { @@ -2011,7 +2017,7 @@ public class NotificationManagerService extends SystemService { ContentResolver resolver = getContext().getContentResolver(); resolver.registerContentObserver(NOTIFICATION_BADGING_URI, false, this, UserHandle.USER_ALL); - if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (!Flags.refactorAttentionHelper()) { resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, false, this, UserHandle.USER_ALL); } @@ -2037,7 +2043,7 @@ public class NotificationManagerService extends SystemService { public void update(Uri uri) { ContentResolver resolver = getContext().getContentResolver(); - if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (!Flags.refactorAttentionHelper()) { if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) { boolean pulseEnabled = Settings.System.getIntForUser(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) @@ -2530,7 +2536,7 @@ public class NotificationManagerService extends SystemService { mToastRateLimiter = toastRateLimiter; - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager, mAccessibilityManager, mPackageManagerClient, userManager, usageStats, mNotificationManagerPrivate, mZenModeHelper, flagResolver); @@ -2540,7 +2546,7 @@ public class NotificationManagerService extends SystemService { // If this is called within a test, make sure to unregister the intent receivers by // calling onDestroy() IntentFilter filter = new IntentFilter(); - if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (!Flags.refactorAttentionHelper()) { filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); @@ -2552,6 +2558,9 @@ public class NotificationManagerService extends SystemService { filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_UNLOCKED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + if (allowPrivateProfile()){ + filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE); + } getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); IntentFilter pkgFilter = new IntentFilter(); @@ -2865,7 +2874,7 @@ public class NotificationManagerService extends SystemService { } registerNotificationPreferencesPullers(); new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker); - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.onSystemReady(); } } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { @@ -6490,7 +6499,7 @@ public class NotificationManagerService extends SystemService { pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate); pw.println(" hideSilentStatusBar=" + mPreferencesHelper.shouldHideSilentStatusIcons()); - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.dump(pw, " ", filter); } } @@ -7756,7 +7765,7 @@ public class NotificationManagerService extends SystemService { boolean wasPosted = removeFromNotificationListsLocked(r); cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null, SystemClock.elapsedRealtime()); - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.updateLightsLocked(); } else { updateLightsLocked(); @@ -7889,7 +7898,7 @@ public class NotificationManagerService extends SystemService { cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName, mSendDelete, childrenFlagChecker, mReason, mCancellationElapsedTimeMs); - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.updateLightsLocked(); } else { updateLightsLocked(); @@ -8186,7 +8195,7 @@ public class NotificationManagerService extends SystemService { int buzzBeepBlinkLoggingCode = 0; if (!r.isHidden()) { - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r, new NotificationAttentionHelper.Signals( mUserProfiles.isCurrentProfile(r.getUserId()), @@ -9173,7 +9182,7 @@ public class NotificationManagerService extends SystemService { || interruptiveChanged; if (interceptBefore && !record.isIntercepted() && record.isNewEnoughForAlerting(System.currentTimeMillis())) { - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.buzzBeepBlinkLocked(record, new NotificationAttentionHelper.Signals( mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints)); @@ -9553,7 +9562,7 @@ public class NotificationManagerService extends SystemService { }); } - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.clearEffectsLocked(canceledKey); } else { // sound @@ -9917,7 +9926,7 @@ public class NotificationManagerService extends SystemService { cancellationElapsedTimeMs); } } - if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mAttentionHelper.updateLightsLocked(); } else { updateLightsLocked(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 3a6664a72439185899c6040558caf74af4016f3b..7c0fc998abcfff7fd42e19b922af8a145165ba5a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -139,6 +139,7 @@ import android.os.DeviceIdleManager; import android.os.FactoryTest; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeReason; @@ -715,6 +716,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26; private class PolicyHandler extends Handler { + + private PolicyHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -2166,10 +2172,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { static class Injector { private final Context mContext; private final WindowManagerFuncs mWindowManagerFuncs; + private final Looper mLooper; - Injector(Context context, WindowManagerFuncs funcs) { + Injector(Context context, WindowManagerFuncs funcs, Looper looper) { mContext = context; mWindowManagerFuncs = funcs; + mLooper = looper; } Context getContext() { @@ -2180,6 +2188,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { return mWindowManagerFuncs; } + Looper getLooper() { + return mLooper; + } + AccessibilityShortcutController getAccessibilityShortcutController( Context context, Handler handler, int initialUserId) { return new AccessibilityShortcutController(context, handler, initialUserId); @@ -2208,7 +2220,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public void init(Context context, WindowManagerFuncs funcs) { - init(new Injector(context, funcs)); + init(new Injector(context, funcs, Looper.myLooper())); } @VisibleForTesting @@ -2284,7 +2296,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mContext, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius); } - mHandler = new PolicyHandler(); + mHandler = new PolicyHandler(injector.getLooper()); mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); mSettingsObserver = new SettingsObserver(mHandler); mSettingsObserver.observe(); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index c54e3bdf0d516e7af95c79b1828d6c196fff91ea..5f8bbe5f18ad353e3e6be274180ed82a5ee01a8e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -29,7 +29,6 @@ import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked; import android.annotation.Nullable; import android.app.WallpaperColors; -import android.app.WallpaperManager; import android.app.WallpaperManager.SetWallpaperFlags; import android.app.backup.WallpaperBackupHelper; import android.content.ComponentName; @@ -38,7 +37,6 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Color; import android.os.FileUtils; -import android.os.SystemProperties; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -77,8 +75,6 @@ class WallpaperDataParser { private final WallpaperCropper mWallpaperCropper; private final Context mContext; - private final boolean mIsLockscreenLiveWallpaperEnabled; - WallpaperDataParser(Context context, WallpaperDisplayHelper wallpaperDisplayHelper, WallpaperCropper wallpaperCropper) { mContext = context; @@ -86,8 +82,6 @@ class WallpaperDataParser { mWallpaperCropper = wallpaperCropper; mImageWallpaper = ComponentName.unflattenFromString( context.getResources().getString(R.string.image_wallpaper_component)); - mIsLockscreenLiveWallpaperEnabled = - SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true); } private JournaledFile makeJournaledFile(int userId) { @@ -127,42 +121,26 @@ class WallpaperDataParser { } /** - * TODO(b/197814683) adapt comment once flag is removed - * * Load the system wallpaper (and the lock wallpaper, if it exists) from disk * @param userId the id of the user for which the wallpaper should be loaded * @param keepDimensionHints if false, parse and set the * {@link DisplayData} width and height for the specified userId - * @param wallpaper the wallpaper object to reuse to do the modifications. - * If null, a new object will be created. - * @param lockWallpaper the lock wallpaper object to reuse to do the modifications. - * If null, a new object will be created. - * @param which The wallpaper(s) to load. Only has effect if - * {@link WallpaperManager#isLockscreenLiveWallpaperEnabled} is true, - * otherwise both wallpaper will always be loaded. + * @param migrateFromOld whether the current wallpaper is pre-N and needs migration + * @param which The wallpaper(s) to load. * @return a {@link WallpaperLoadingResult} object containing the wallpaper data. - * This object will contain the {@code wallpaper} and - * {@code lockWallpaper} provided as parameters, if they are not null. */ public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints, - WallpaperData wallpaper, WallpaperData lockWallpaper, @SetWallpaperFlags int which) { + boolean migrateFromOld, @SetWallpaperFlags int which) { JournaledFile journal = makeJournaledFile(userId); FileInputStream stream = null; File file = journal.chooseForRead(); - boolean migrateFromOld = wallpaper == null; - - boolean separateLockscreenEngine = mIsLockscreenLiveWallpaperEnabled; - boolean loadSystem = !separateLockscreenEngine || (which & FLAG_SYSTEM) != 0; - boolean loadLock = !separateLockscreenEngine || (which & FLAG_LOCK) != 0; - - // don't reuse the wallpaper objects in the new version - if (separateLockscreenEngine) { - wallpaper = null; - lockWallpaper = null; - } + boolean loadSystem = (which & FLAG_SYSTEM) != 0; + boolean loadLock = (which & FLAG_LOCK) != 0; + WallpaperData wallpaper = null; + WallpaperData lockWallpaper = null; - if (wallpaper == null && loadSystem) { + if (loadSystem) { // Do this once per boot if (migrateFromOld) migrateFromOld(); wallpaper = new WallpaperData(userId, FLAG_SYSTEM); @@ -188,11 +166,8 @@ class WallpaperDataParser { type = parser.next(); if (type == XmlPullParser.START_TAG) { String tag = parser.getName(); - if (("wp".equals(tag) && loadSystem) - || ("kwp".equals(tag) && mIsLockscreenLiveWallpaperEnabled - && loadLock)) { - - if ("kwp".equals(tag) && lockWallpaper == null) { + if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) { + if ("kwp".equals(tag)) { lockWallpaper = new WallpaperData(userId, FLAG_LOCK); } WallpaperData wallpaperToParse = @@ -219,12 +194,6 @@ class WallpaperDataParser { Slog.v(TAG, "mNextWallpaperComponent:" + wallpaper.nextWallpaperComponent); } - } else if ("kwp".equals(tag) && !mIsLockscreenLiveWallpaperEnabled) { - // keyguard-specific wallpaper for this user (legacy code) - if (lockWallpaper == null) { - lockWallpaper = new WallpaperData(userId, FLAG_LOCK); - } - parseWallpaperAttributes(parser, lockWallpaper, false); } } } while (type != XmlPullParser.END_DOCUMENT); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index c7a3c4349f4cfa97f7a14eb7c05aa317e79e3048..bdcde66a8102b6315c3625d64dddcc5de03fd562 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -188,8 +188,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private final Object mLock = new Object(); - /** True to enable a second engine for lock screen wallpaper when different from system wp. */ - private final boolean mIsLockscreenLiveWallpaperEnabled; /** True to support different crops for different display dimensions */ private final boolean mIsMultiCropEnabled; /** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */ @@ -230,7 +228,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG); } - WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) { + WallpaperData dataForEvent(boolean lockChanged) { WallpaperData wallpaper = null; synchronized (mLock) { if (lockChanged) { @@ -252,7 +250,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final File changedFile = new File(mWallpaperDir, path); final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile)); final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile)); - final WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged); + final WallpaperData wallpaper = dataForEvent(lockWallpaperChanged); final boolean moved = (event == MOVED_TO); final boolean written = (event == CLOSE_WRITE || moved); @@ -378,7 +376,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } saveSettingsLocked(wallpaper.userId); - if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) { + if (localSync != null) { localSync.complete(); } } @@ -389,129 +387,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - // Handles static wallpaper changes generated by WallpaperObserver events when - // enableSeparateLockScreenEngine() is false. - // TODO(b/266818039) Remove this method - private void updateWallpapersLegacy(int event, String path) { - final boolean moved = (event == MOVED_TO); - final boolean written = (event == CLOSE_WRITE || moved); - final File changedFile = new File(mWallpaperDir, path); - - // System and system+lock changes happen on the system wallpaper input file; - // lock-only changes happen on the dedicated lock wallpaper input file - final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile)); - final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile)); - int notifyColorsWhich = 0; - WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged); - - if (DEBUG) { - Slog.v(TAG, "Wallpaper file change: evt=" + event - + " path=" + path - + " sys=" + sysWallpaperChanged - + " lock=" + lockWallpaperChanged - + " imagePending=" + wallpaper.imageWallpaperPending - + " mWhich=0x" + Integer.toHexString(wallpaper.mWhich) - + " written=" + written); - } - - if (moved && lockWallpaperChanged) { - // We just migrated sys -> lock to preserve imagery for an impending - // new system-only wallpaper. Tell keyguard about it and make sure it - // has the right SELinux label. - if (DEBUG) { - Slog.i(TAG, "Sys -> lock MOVED_TO"); - } - SELinux.restorecon(changedFile); - notifyLockWallpaperChanged(); - notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK); - return; - } - - synchronized (mLock) { - if (sysWallpaperChanged || lockWallpaperChanged) { - notifyCallbacksLocked(wallpaper); - if (wallpaper.wallpaperComponent == null - || event != CLOSE_WRITE // includes the MOVED_TO case - || wallpaper.imageWallpaperPending) { - if (written) { - // The image source has finished writing the source image, - // so we now produce the crop rect (in the background), and - // only publish the new displayable (sub)image as a result - // of that work. - if (DEBUG) { - Slog.v(TAG, "Wallpaper written; generating crop"); - } - SELinux.restorecon(changedFile); - if (moved) { - // This is a restore, so generate the crop using any just-restored new - // crop guidelines, making sure to preserve our local dimension hints. - // We also make sure to reapply the correct SELinux label. - if (DEBUG) { - Slog.v(TAG, "moved-to, therefore restore; reloading metadata"); - } - loadSettingsLocked(wallpaper.userId, true, FLAG_SYSTEM | FLAG_LOCK); - } - mWallpaperCropper.generateCrop(wallpaper); - if (DEBUG) { - Slog.v(TAG, "Crop done; invoking completion callback"); - } - wallpaper.imageWallpaperPending = false; - if (sysWallpaperChanged) { - IRemoteCallback.Stub callback = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - Slog.d(TAG, "publish system wallpaper changed!"); - notifyWallpaperChanged(wallpaper); - } - }; - // If this was the system wallpaper, rebind... - bindWallpaperComponentLocked(mImageWallpaper, true, - false, wallpaper, callback); - notifyColorsWhich |= FLAG_SYSTEM; - } - if (lockWallpaperChanged - || (wallpaper.mWhich & FLAG_LOCK) != 0) { - if (DEBUG) { - Slog.i(TAG, "Lock-relevant wallpaper changed"); - } - // either a lock-only wallpaper commit or a system+lock event. - // if it's system-plus-lock we need to wipe the lock bookkeeping; - // we're falling back to displaying the system wallpaper there. - if (!lockWallpaperChanged) { - mLockWallpaperMap.remove(wallpaper.userId); - } - // and in any case, tell keyguard about it - notifyLockWallpaperChanged(); - notifyColorsWhich |= FLAG_LOCK; - } - - saveSettingsLocked(wallpaper.userId); - // Notify the client immediately if only lockscreen wallpaper changed. - if (lockWallpaperChanged && !sysWallpaperChanged) { - notifyWallpaperChanged(wallpaper); - } - } - } - } - } - - // Outside of the lock since it will synchronize itself - if (notifyColorsWhich != 0) { - notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich); - } - } - @Override public void onEvent(int event, String path) { - if (path == null) { - return; - } - - if (mIsLockscreenLiveWallpaperEnabled) { - updateWallpapers(event, path); - } else { - updateWallpapersLegacy(event, path); - } + if (path != null) updateWallpapers(event, path); } } @@ -528,17 +406,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void notifyLockWallpaperChanged() { - final IWallpaperManagerCallback cb = mKeyguardListener; - if (cb != null) { - try { - cb.onWallpaperChanged(); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify keyguard callback about wallpaper changes", e); - } - } - } - void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) { if (DEBUG) { Slog.i(TAG, "Notifying wallpaper colors changed"); @@ -597,14 +464,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which, int userId, int displayId) { - final IWallpaperManagerCallback keyguardListener; final ArrayList<IWallpaperManagerCallback> colorListeners = new ArrayList<>(); synchronized (mLock) { final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners = getWallpaperCallbacks(userId, displayId); final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners = getWallpaperCallbacks(UserHandle.USER_ALL, displayId); - keyguardListener = mKeyguardListener; if (currentUserColorListeners != null) { final int count = currentUserColorListeners.beginBroadcast(); @@ -633,15 +498,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, "onWallpaperColorsChanged() threw an exception", e); } } - - // Only shows Keyguard on default display - if (keyguardListener != null && displayId == DEFAULT_DISPLAY) { - try { - keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId); - } catch (RemoteException e) { - Slog.w(TAG, "keyguardListener.onWallpaperColorsChanged threw an exception", e); - } - } } /** @@ -762,8 +618,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private final MyPackageMonitor mMonitor; private final AppOpsManager mAppOpsManager; - // TODO("b/264637309") probably move this in WallpaperDisplayUtils, - // after logic is changed for the lockscreen lwp project private final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { @@ -814,8 +668,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub protected WallpaperData mLastWallpaper; // The currently bound lock screen only wallpaper, or null if none protected WallpaperData mLastLockWallpaper; - private IWallpaperManagerCallback mKeyguardListener; - private boolean mWaitingForUnlock; /** * Flag set to true after reboot if the home wallpaper is waiting for the device to be unlocked. @@ -1017,8 +869,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) { Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent + ", reverting to built-in wallpaper!"); - int which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM; - clearWallpaperLocked(which, mWallpaper.userId, null); + int which = mWallpaper.mWhich; + clearWallpaperLocked(which, mWallpaper.userId, false, null); } } }; @@ -1198,7 +1050,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } else { // Timeout Slog.w(TAG, "Reverting to built-in wallpaper!"); - clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, null); + clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null); final String flattened = wpService.flattenToString(); EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED, flattened.substring(0, Math.min(flattened.length(), @@ -1238,7 +1090,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mLmkLimitRebindRetries <= 0) { Slog.w(TAG, "Reverting to built-in wallpaper due to lmk!"); clearWallpaperLocked( - mWallpaper.mWhich, mWallpaper.userId, null); + mWallpaper.mWhich, mWallpaper.userId, false, null); mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES; return; } @@ -1257,7 +1109,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME > SystemClock.uptimeMillis()) { Slog.w(TAG, "Reverting to built-in wallpaper!"); - clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null); + clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, false, null); } else { mWallpaper.lastDiedTime = SystemClock.uptimeMillis(); tryToRebind(); @@ -1294,19 +1146,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) { return; } - - // Live wallpapers always are system wallpapers unless lock screen live wp is - // enabled. - which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM; + which = mWallpaper.mWhich; mWallpaper.primaryColors = primaryColors; - - // It's also the lock screen wallpaper when we don't have a bitmap in there. - if (displayId == DEFAULT_DISPLAY) { - final WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId); - if (lockedWallpaper == null) { - which |= FLAG_LOCK; - } - } } if (which != 0) { notifyWallpaperColorsChangedOnDisplay(mWallpaper, which, displayId); @@ -1492,9 +1333,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper, null)) { Slog.w(TAG, "Wallpaper " + wpService + " no longer available; reverting to default"); - int which = mIsLockscreenLiveWallpaperEnabled - ? wallpaper.mWhich : FLAG_SYSTEM; - clearWallpaperLocked(which, wallpaper.userId, null); + clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } } @@ -1568,7 +1407,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) { boolean changed = false; - int which = mIsLockscreenLiveWallpaperEnabled ? wallpaper.mWhich : FLAG_SYSTEM; if (wallpaper.wallpaperComponent != null) { int change = isPackageDisappearing(wallpaper.wallpaperComponent .getPackageName()); @@ -1578,7 +1416,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (doit) { Slog.w(TAG, "Wallpaper uninstalled, removing: " + wallpaper.wallpaperComponent); - clearWallpaperLocked(which, wallpaper.userId, null); + clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } } @@ -1599,7 +1437,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } catch (NameNotFoundException e) { Slog.w(TAG, "Wallpaper component gone, removing: " + wallpaper.wallpaperComponent); - clearWallpaperLocked(which, wallpaper.userId, null); + clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } if (wallpaper.nextWallpaperComponent != null @@ -1686,9 +1524,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mColorsChangedListeners = new SparseArray<>(); mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper, mWallpaperCropper); - - mIsLockscreenLiveWallpaperEnabled = - SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true); mIsMultiCropEnabled = SystemProperties.getBoolean("persist.wm.debug.wallpaper_multi_crop", false); LocalServices.addService(WallpaperManagerInternal.class, new LocalService()); @@ -1755,8 +1590,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.i(TAG, "Unable to regenerate crop; resetting"); } - int which = isLockscreenLiveWallpaperEnabled() ? wallpaper.mWhich : FLAG_SYSTEM; - clearWallpaperLocked(which, UserHandle.USER_SYSTEM, null); + clearWallpaperLocked(wallpaper.mWhich, UserHandle.USER_SYSTEM, false, null); } } else { if (DEBUG) { @@ -1883,29 +1717,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onUnlockUser(final int userId) { synchronized (mLock) { if (mCurrentUserId == userId) { - if (mIsLockscreenLiveWallpaperEnabled) { - if (mHomeWallpaperWaitingForUnlock) { - final WallpaperData systemWallpaper = - getWallpaperSafeLocked(userId, FLAG_SYSTEM); - switchWallpaper(systemWallpaper, null); - // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper - notifyCallbacksLocked(systemWallpaper); - } - if (mLockWallpaperWaitingForUnlock) { - final WallpaperData lockWallpaper = - getWallpaperSafeLocked(userId, FLAG_LOCK); - switchWallpaper(lockWallpaper, null); - notifyCallbacksLocked(lockWallpaper); - } - } - - if (mWaitingForUnlock && !mIsLockscreenLiveWallpaperEnabled) { - // the desired wallpaper is not direct-boot aware, load it now + if (mHomeWallpaperWaitingForUnlock) { final WallpaperData systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM); switchWallpaper(systemWallpaper, null); + // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper notifyCallbacksLocked(systemWallpaper); } + if (mLockWallpaperWaitingForUnlock) { + final WallpaperData lockWallpaper = + getWallpaperSafeLocked(userId, FLAG_LOCK); + switchWallpaper(lockWallpaper, null); + notifyCallbacksLocked(lockWallpaper); + } // Make sure that the SELinux labeling of all the relevant files is correct. // This corrects for mislabeling bugs that might have arisen from move-to @@ -1954,21 +1778,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } mCurrentUserId = userId; systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM); - - if (mIsLockscreenLiveWallpaperEnabled) { - lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM) - ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK); - } else { - final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId); - lockWallpaper = tmpLockWallpaper == null ? systemWallpaper : tmpLockWallpaper; - } + lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM) + ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK); // Not started watching yet, in case wallpaper data was loaded for other reasons. if (systemWallpaper.wallpaperObserver == null) { systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper); systemWallpaper.wallpaperObserver.startWatching(); } - if (mIsLockscreenLiveWallpaperEnabled && lockWallpaper != systemWallpaper) { + if (lockWallpaper != systemWallpaper) { switchWallpaper(lockWallpaper, null); } switchWallpaper(systemWallpaper, reply); @@ -1988,11 +1806,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) { synchronized (mLock) { - mWaitingForUnlock = false; - if (mIsLockscreenLiveWallpaperEnabled) { - if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false; - if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false; - } + if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false; + if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false; final ComponentName cname = wallpaper.wallpaperComponent != null ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; @@ -2006,37 +1821,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } catch (RemoteException e) { Slog.w(TAG, "Failure starting previous wallpaper; clearing", e); } - - if (mIsLockscreenLiveWallpaperEnabled) { - onSwitchWallpaperFailLocked(wallpaper, reply, si); - return; - } - - if (si == null) { - clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, reply); - } else { - Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked"); - // We might end up persisting the current wallpaper data - // while locked, so pretend like the component was actually - // bound into place - wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent; - final WallpaperData fallback = new WallpaperData(wallpaper.userId, FLAG_LOCK); - bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply); - mWaitingForUnlock = true; - } + onSwitchWallpaperFailLocked(wallpaper, reply, si); } } } /** * Fallback method if a wallpaper fails to load on boot or after a user switch. - * Only called if mIsLockscreenLiveWallpaperEnabled is true. */ private void onSwitchWallpaperFailLocked( WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) { if (serviceInfo == null) { - clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply); + clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, reply); return; } Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked"); @@ -2067,12 +1864,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData data = null; synchronized (mLock) { - if (mIsLockscreenLiveWallpaperEnabled) { - boolean fromForeground = isFromForegroundApp(callingPackage); - clearWallpaperLocked(which, userId, fromForeground, null); - } else { - clearWallpaperLocked(which, userId, null); - } + boolean fromForeground = isFromForegroundApp(callingPackage); + clearWallpaperLocked(which, userId, fromForeground, null); if (which == FLAG_LOCK) { data = mLockWallpaperMap.get(userId); @@ -2153,91 +1946,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - // TODO(b/266818039) remove - private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) { - - if (mIsLockscreenLiveWallpaperEnabled) { - clearWallpaperLocked(which, userId, false, reply); - return; - } - - if (which != FLAG_SYSTEM && which != FLAG_LOCK) { - throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear"); - } - - WallpaperData wallpaper = null; - if (which == FLAG_LOCK) { - wallpaper = mLockWallpaperMap.get(userId); - if (wallpaper == null) { - // It's already gone; we're done. - if (DEBUG) { - Slog.i(TAG, "Lock wallpaper already cleared"); - } - return; - } - } else { - wallpaper = mWallpaperMap.get(userId); - if (wallpaper == null) { - // Might need to bring it in the first time to establish our rewrite - loadSettingsLocked(userId, false, FLAG_SYSTEM); - wallpaper = mWallpaperMap.get(userId); - } - } - if (wallpaper == null) { - return; - } - - final long ident = Binder.clearCallingIdentity(); - try { - if (clearWallpaperBitmaps(wallpaper)) { - if (which == FLAG_LOCK) { - mLockWallpaperMap.remove(userId); - final IWallpaperManagerCallback cb = mKeyguardListener; - if (cb != null) { - if (DEBUG) { - Slog.i(TAG, "Notifying keyguard of lock wallpaper clear"); - } - try { - cb.onWallpaperChanged(); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify keyguard after wallpaper clear", e); - } - } - saveSettingsLocked(userId); - return; - } - } - - RuntimeException e = null; - try { - wallpaper.primaryColors = null; - wallpaper.imageWallpaperPending = false; - if (userId != mCurrentUserId) return; - if (bindWallpaperComponentLocked(null, true, false, wallpaper, reply)) { - return; - } - } catch (IllegalArgumentException e1) { - e = e1; - } - - // This can happen if the default wallpaper component doesn't - // exist. This should be a system configuration problem, but - // let's not let it crash the system and just live with no - // wallpaper. - Slog.e(TAG, "Default wallpaper component not found!", e); - clearWallpaperComponentLocked(wallpaper); - if (reply != null) { - try { - reply.sendResult(null); - } catch (RemoteException e1) { - Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1); - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - private boolean hasCrossUserPermission() { final int interactPermission = mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL); @@ -2615,45 +2323,20 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * @param animationDuration Duration of the animation, or 0 when immediate. */ public void setInAmbientMode(boolean inAmbientMode, long animationDuration) { - if (mIsLockscreenLiveWallpaperEnabled) { - List<IWallpaperEngine> engines = new ArrayList<>(); - synchronized (mLock) { - mInAmbientMode = inAmbientMode; - for (WallpaperData data : getActiveWallpapers()) { - if (data.connection.mInfo == null - || data.connection.mInfo.supportsAmbientMode()) { - // TODO(multi-display) Extends this method with specific display. - IWallpaperEngine engine = data.connection - .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine; - if (engine != null) engines.add(engine); - } - } - } - for (IWallpaperEngine engine : engines) { - try { - engine.setInAmbientMode(inAmbientMode, animationDuration); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to set ambient mode", e); - } - } - return; - } - - final IWallpaperEngine engine; + List<IWallpaperEngine> engines = new ArrayList<>(); synchronized (mLock) { mInAmbientMode = inAmbientMode; - final WallpaperData data = mWallpaperMap.get(mCurrentUserId); - // The wallpaper info is null for image wallpaper, also use the engine in this case. - if (data != null && data.connection != null && (data.connection.mInfo == null - || data.connection.mInfo.supportsAmbientMode())) { - // TODO(multi-display) Extends this method with specific display. - engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine; - } else { - engine = null; + for (WallpaperData data : getActiveWallpapers()) { + if (data.connection.mInfo == null + || data.connection.mInfo.supportsAmbientMode()) { + // TODO(multi-display) Extends this method with specific display. + IWallpaperEngine engine = data.connection + .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine; + if (engine != null) engines.add(engine); + } } } - - if (engine != null) { + for (IWallpaperEngine engine : engines) { try { engine.setInAmbientMode(inAmbientMode, animationDuration); } catch (RemoteException e) { @@ -2664,11 +2347,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void pauseOrResumeRenderingImmediately(boolean pause) { synchronized (mLock) { - final WallpaperData[] wallpapers = mIsLockscreenLiveWallpaperEnabled - ? getActiveWallpapers() : new WallpaperData[] { - mWallpaperMap.get(mCurrentUserId) }; - for (WallpaperData data : wallpapers) { - if (data.connection == null || data.connection.mInfo == null) { + for (WallpaperData data : getActiveWallpapers()) { + if (data.connection.mInfo == null) { continue; } if (pause || LocalServices.getService(ActivityTaskManagerInternal.class) @@ -2697,34 +2377,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void notifyWakingUp(int x, int y, @NonNull Bundle extras) { checkCallerIsSystemOrSystemUi(); synchronized (mLock) { - if (mIsLockscreenLiveWallpaperEnabled) { - for (WallpaperData data : getActiveWallpapers()) { - data.connection.forEachDisplayConnector(displayConnector -> { - if (displayConnector.mEngine != null) { - try { - displayConnector.mEngine.dispatchWallpaperCommand( - WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e); - } + for (WallpaperData data : getActiveWallpapers()) { + data.connection.forEachDisplayConnector(displayConnector -> { + if (displayConnector.mEngine != null) { + try { + displayConnector.mEngine.dispatchWallpaperCommand( + WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e); } - }); - } - return; - } - final WallpaperData data = mWallpaperMap.get(mCurrentUserId); - if (data != null && data.connection != null) { - data.connection.forEachDisplayConnector( - displayConnector -> { - if (displayConnector.mEngine != null) { - try { - displayConnector.mEngine.dispatchWallpaperCommand( - WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e); - } - } - }); + } + }); } } } @@ -2735,36 +2398,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) { checkCallerIsSystemOrSystemUi(); synchronized (mLock) { - if (mIsLockscreenLiveWallpaperEnabled) { - for (WallpaperData data : getActiveWallpapers()) { - data.connection.forEachDisplayConnector(displayConnector -> { - if (displayConnector.mEngine != null) { - try { - displayConnector.mEngine.dispatchWallpaperCommand( - WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1, - extras); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e); - } + for (WallpaperData data : getActiveWallpapers()) { + data.connection.forEachDisplayConnector(displayConnector -> { + if (displayConnector.mEngine != null) { + try { + displayConnector.mEngine.dispatchWallpaperCommand( + WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1, + extras); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e); } - }); - } - return; - } - final WallpaperData data = mWallpaperMap.get(mCurrentUserId); - if (data != null && data.connection != null) { - data.connection.forEachDisplayConnector( - displayConnector -> { - if (displayConnector.mEngine != null) { - try { - displayConnector.mEngine.dispatchWallpaperCommand( - WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1, - extras); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e); - } - } - }); + } + }); } } } @@ -2774,35 +2419,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ private void notifyScreenTurnedOn(int displayId) { synchronized (mLock) { - if (mIsLockscreenLiveWallpaperEnabled) { - for (WallpaperData data : getActiveWallpapers()) { - if (data.connection.containsDisplay(displayId)) { - final IWallpaperEngine engine = data.connection - .getDisplayConnectorOrCreate(displayId).mEngine; - if (engine != null) { - try { - engine.onScreenTurnedOn(); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify that the screen turned on", e); - } + for (WallpaperData data : getActiveWallpapers()) { + if (data.connection.containsDisplay(displayId)) { + final IWallpaperEngine engine = data.connection + .getDisplayConnectorOrCreate(displayId).mEngine; + if (engine != null) { + try { + engine.onScreenTurnedOn(); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify that the screen turned on", e); } } } - return; - } - final WallpaperData data = mWallpaperMap.get(mCurrentUserId); - if (data != null - && data.connection != null - && data.connection.containsDisplay(displayId)) { - final IWallpaperEngine engine = data.connection - .getDisplayConnectorOrCreate(displayId).mEngine; - if (engine != null) { - try { - engine.onScreenTurnedOn(); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify that the screen turned on", e); - } - } } } } @@ -2812,35 +2440,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ private void notifyScreenTurningOn(int displayId) { synchronized (mLock) { - if (mIsLockscreenLiveWallpaperEnabled) { - for (WallpaperData data : getActiveWallpapers()) { - if (data.connection.containsDisplay(displayId)) { - final IWallpaperEngine engine = data.connection - .getDisplayConnectorOrCreate(displayId).mEngine; - if (engine != null) { - try { - engine.onScreenTurningOn(); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify that the screen is turning on", e); - } + for (WallpaperData data : getActiveWallpapers()) { + if (data.connection.containsDisplay(displayId)) { + final IWallpaperEngine engine = data.connection + .getDisplayConnectorOrCreate(displayId).mEngine; + if (engine != null) { + try { + engine.onScreenTurningOn(); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify that the screen is turning on", e); } } } - return; - } - final WallpaperData data = mWallpaperMap.get(mCurrentUserId); - if (data != null - && data.connection != null - && data.connection.containsDisplay(displayId)) { - final IWallpaperEngine engine = data.connection - .getDisplayConnectorOrCreate(displayId).mEngine; - if (engine != null) { - try { - engine.onScreenTurningOn(); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify that the screen is turning on", e); - } - } } } } @@ -2850,25 +2461,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub */ private void notifyKeyguardGoingAway() { synchronized (mLock) { - if (mIsLockscreenLiveWallpaperEnabled) { - for (WallpaperData data : getActiveWallpapers()) { - data.connection.forEachDisplayConnector(displayConnector -> { - if (displayConnector.mEngine != null) { - try { - displayConnector.mEngine.dispatchWallpaperCommand( - WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY, - -1, -1, -1, new Bundle()); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to notify that the keyguard is going away", e); - } - } - }); - } - return; - } - - final WallpaperData data = mWallpaperMap.get(mCurrentUserId); - if (data != null && data.connection != null) { + for (WallpaperData data : getActiveWallpapers()) { data.connection.forEachDisplayConnector(displayConnector -> { if (displayConnector.mEngine != null) { try { @@ -2884,15 +2477,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - @Override - public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) { - checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW); - synchronized (mLock) { - mKeyguardListener = cb; - } - return true; - } - private WallpaperData[] getActiveWallpapers() { WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId); WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId); @@ -2904,12 +2488,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub : new WallpaperData[0]; } - // TODO(b/266818039) remove private WallpaperData[] getWallpapers() { WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId); WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId); boolean systemValid = systemWallpaper != null; - boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled(); + boolean lockValid = lockWallpaper != null; return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper} : systemValid ? new WallpaperData[]{systemWallpaper} : lockValid ? new WallpaperData[]{lockWallpaper} @@ -3043,54 +2626,29 @@ public class WallpaperManagerService extends IWallpaperManager.Stub lockWallpaper.mWallpaperDimAmount = maxDimAmount; } - if (mIsLockscreenLiveWallpaperEnabled) { - boolean changed = false; - for (WallpaperData wp : getActiveWallpapers()) { - if (wp != null && wp.connection != null) { - wp.connection.forEachDisplayConnector(connector -> { - if (connector.mEngine != null) { - try { - connector.mEngine.applyDimming(maxDimAmount); - } catch (RemoteException e) { - Slog.w(TAG, "Can't apply dimming on wallpaper display " - + "connector", e); - } - } - }); - // Need to extract colors again to re-calculate dark hints after - // applying dimming. - wp.mIsColorExtractedFromDim = true; - pendingColorExtraction.add(wp); - changed = true; - } - } - if (changed) { - saveSettingsLocked(wallpaper.userId); - } - } else { - if (wallpaper.connection != null) { - wallpaper.connection.forEachDisplayConnector(connector -> { + boolean changed = false; + for (WallpaperData wp : getActiveWallpapers()) { + if (wp != null && wp.connection != null) { + wp.connection.forEachDisplayConnector(connector -> { if (connector.mEngine != null) { try { connector.mEngine.applyDimming(maxDimAmount); } catch (RemoteException e) { - Slog.w(TAG, - "Can't apply dimming on wallpaper display connector", - e); + Slog.w(TAG, "Can't apply dimming on wallpaper display " + + "connector", e); } } }); // Need to extract colors again to re-calculate dark hints after // applying dimming. - wallpaper.mIsColorExtractedFromDim = true; - notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM); - if (lockWallpaper != null) { - lockWallpaper.mIsColorExtractedFromDim = true; - notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK); - } - saveSettingsLocked(wallpaper.userId); + wp.mIsColorExtractedFromDim = true; + pendingColorExtraction.add(wp); + changed = true; } } + if (changed) { + saveSettingsLocked(wallpaper.userId); + } } for (WallpaperData wp: pendingColorExtraction) { notifyWallpaperColorsChanged(wp, wp.mWhich); @@ -3246,10 +2804,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) { Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + " updating system wallpaper"); - if (!migrateStaticSystemToLockWallpaperLocked(userId) - && !isLockscreenLiveWallpaperEnabled()) { - which |= FLAG_LOCK; - } + migrateStaticSystemToLockWallpaperLocked(userId); } wallpaper = getWallpaperSafeLocked(userId, which); @@ -3277,13 +2832,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private boolean migrateStaticSystemToLockWallpaperLocked(int userId) { + private void migrateStaticSystemToLockWallpaperLocked(int userId) { WallpaperData sysWP = mWallpaperMap.get(userId); if (sysWP == null) { if (DEBUG) { Slog.i(TAG, "No system wallpaper? Not tracking for lock-only"); } - return true; + return; } // We know a-priori that there is no lock-only wallpaper currently @@ -3297,25 +2852,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // Migrate the bitmap files outright; no need to copy try { - if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getWallpaperFile().exists()) { + if (sysWP.getWallpaperFile().exists()) { Os.rename(sysWP.getWallpaperFile().getAbsolutePath(), lockWP.getWallpaperFile().getAbsolutePath()); } - if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getCropFile().exists()) { + if (sysWP.getCropFile().exists()) { Os.rename(sysWP.getCropFile().getAbsolutePath(), lockWP.getCropFile().getAbsolutePath()); } mLockWallpaperMap.put(userId, lockWP); - if (mIsLockscreenLiveWallpaperEnabled) { - SELinux.restorecon(lockWP.getWallpaperFile()); - mLastLockWallpaper = lockWP; - } - return true; + SELinux.restorecon(lockWP.getWallpaperFile()); + mLastLockWallpaper = lockWP; } catch (ErrnoException e) { // can happen when migrating default wallpaper (which is not stored in wallpaperFile) Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage()); clearWallpaperBitmaps(lockWP); - return false; } } @@ -3372,13 +2923,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub @VisibleForTesting boolean setWallpaperComponent(ComponentName name, String callingPackage, @SetWallpaperFlags int which, int userId) { - if (mIsLockscreenLiveWallpaperEnabled) { - boolean fromForeground = isFromForegroundApp(callingPackage); - return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null); - } else { - setWallpaperComponentInternalLegacy(name, callingPackage, which, userId); - return true; - } + boolean fromForeground = isFromForegroundApp(callingPackage); + return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null); } private boolean setWallpaperComponentInternal(ComponentName name, @SetWallpaperFlags int which, @@ -3491,87 +3037,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return bindSuccess; } - // TODO(b/266818039) Remove this method - private void setWallpaperComponentInternalLegacy(ComponentName name, String callingPackage, - @SetWallpaperFlags int which, int userId) { - userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, - false /* all */, true /* full */, "changing live wallpaper", null /* pkg */); - checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); - - int legacyWhich = FLAG_SYSTEM; - boolean shouldNotifyColors = false; - WallpaperData wallpaper; - - synchronized (mLock) { - Slog.v(TAG, "setWallpaperComponentLegacy name=" + name + ", which=" + which); - wallpaper = mWallpaperMap.get(userId); - if (wallpaper == null) { - throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); - } - final long ident = Binder.clearCallingIdentity(); - - // Live wallpapers can't be specified for keyguard. If we're using a static - // system+lock image currently, migrate the system wallpaper to be a lock-only - // image as part of making a different live component active as the system - // wallpaper. - if (mImageWallpaper.equals(wallpaper.wallpaperComponent)) { - if (mLockWallpaperMap.get(userId) == null) { - // We're using the static imagery and there is no lock-specific image in place, - // therefore it's a shared system+lock image that we need to migrate. - Slog.i(TAG, "Migrating current wallpaper to be lock-only before" - + "updating system wallpaper"); - if (!migrateStaticSystemToLockWallpaperLocked(userId)) { - which |= FLAG_LOCK; - } - } - } - - // New live wallpaper is also a lock wallpaper if nothing is set - if (mLockWallpaperMap.get(userId) == null) { - legacyWhich |= FLAG_LOCK; - } - - try { - wallpaper.imageWallpaperPending = false; - wallpaper.mWhich = which; - wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage); - boolean same = changingToSame(name, wallpaper); - - // force rebind when reapplying a system-only wallpaper to system+lock - boolean forceRebind = same && mLockWallpaperMap.get(userId) != null - && which == (FLAG_SYSTEM | FLAG_LOCK); - if (bindWallpaperComponentLocked(name, forceRebind, true, wallpaper, null)) { - if (!same) { - wallpaper.primaryColors = null; - } else { - if (wallpaper.connection != null) { - wallpaper.connection.forEachDisplayConnector(displayConnector -> { - try { - if (displayConnector.mEngine != null) { - displayConnector.mEngine.dispatchWallpaperCommand( - COMMAND_REAPPLY, 0, 0, 0, null); - } - } catch (RemoteException e) { - Slog.w(TAG, "Error sending apply message to wallpaper", e); - } - }); - } - } - wallpaper.wallpaperId = makeWallpaperIdLocked(); - notifyCallbacksLocked(wallpaper); - shouldNotifyColors = true; - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - if (shouldNotifyColors) { - notifyWallpaperColorsChanged(wallpaper, legacyWhich); - notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM); - } - } - /** * Determines if the given component name is the default component. Note: a null name can be * used to represent the default component. @@ -3743,21 +3208,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, msg); return false; } - if (mIsLockscreenLiveWallpaperEnabled) { - maybeDetachLastWallpapers(wallpaper); - } else if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null - && !wallpaper.equals(mFallbackWallpaper)) { - detachWallpaperLocked(mLastWallpaper); - } + maybeDetachLastWallpapers(wallpaper); wallpaper.wallpaperComponent = componentName; wallpaper.connection = newConn; newConn.mReply = reply; - if (mIsLockscreenLiveWallpaperEnabled) { - updateCurrentWallpapers(wallpaper); - } else if (wallpaper.userId == mCurrentUserId && !wallpaper.equals( - mFallbackWallpaper)) { - mLastWallpaper = wallpaper; - } + updateCurrentWallpapers(wallpaper); updateFallbackConnection(); } catch (RemoteException e) { String msg = "Remote exception for " + componentName + "\n" + e; @@ -3773,7 +3228,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Updates tracking of the currently bound wallpapers. - // Assumes isLockscreenLiveWallpaperEnabled is true. private void updateCurrentWallpapers(WallpaperData newWallpaper) { if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) { return; @@ -3787,8 +3241,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - // Detaches previously bound wallpapers if no longer in use. Assumes - // isLockscreenLiveWallpaperEnabled is true. + // Detaches previously bound wallpapers if no longer in use. private void maybeDetachLastWallpapers(WallpaperData newWallpaper) { if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) { return; @@ -3980,11 +3433,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return (wallpaper != null) ? wallpaper.allowBackup : false; } - @Override - public boolean isLockscreenLiveWallpaperEnabled() { - return mIsLockscreenLiveWallpaperEnabled; - } - @Override public boolean isMultiCropEnabled() { return mIsMultiCropEnabled; @@ -4074,13 +3522,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void loadSettingsLocked(int userId, boolean keepDimensionHints, int which) { initializeFallbackWallpaper(); - WallpaperData wallpaperData = mWallpaperMap.get(userId); - WallpaperData lockWallpaperData = mLockWallpaperMap.get(userId); + boolean restoreFromOld = !mWallpaperMap.contains(userId); WallpaperDataParser.WallpaperLoadingResult result = mWallpaperDataParser.loadSettingsLocked( - userId, keepDimensionHints, wallpaperData, lockWallpaperData, which); + userId, keepDimensionHints, restoreFromOld, which); - boolean updateSystem = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_SYSTEM) != 0; - boolean updateLock = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_LOCK) != 0; + boolean updateSystem = (which & FLAG_SYSTEM) != 0; + boolean updateLock = (which & FLAG_LOCK) != 0; if (updateSystem) mWallpaperMap.put(userId, result.getSystemWallpaperData()); if (updateLock) { @@ -4243,8 +3690,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mFallbackWallpaper != null) { dumpWallpaper(mFallbackWallpaper, pw); } - pw.print("mIsLockscreenLiveWallpaperEnabled="); - pw.println(mIsLockscreenLiveWallpaperEnabled); } } } diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 06448d0c1e84962c531689246ee9effab23b1847..022ef61529291fc28de0169d9d46e0b60c2b60a9 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -28,6 +28,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Configuration; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.media.projection.IMediaProjectionManager; import android.os.IBinder; @@ -36,16 +37,28 @@ import android.os.ServiceManager; import android.view.ContentRecordingSession; import android.view.ContentRecordingSession.RecordContent; import android.view.Display; +import android.view.DisplayInfo; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.server.display.feature.DisplayManagerFlags; /** * Manages content recording for a particular {@link DisplayContent}. */ final class ContentRecorder implements WindowContainerListener { + /** + * Maximum acceptable anisotropy for the output image. + * + * Necessary to avoid unnecessary scaling when the anisotropy is almost the same, as it is not + * exact anyway. For external displays, we expect an anisoptry of about 2% even if the pixels + * are, in fact, square due to the imprecision of the display's actual size (rounded to the + * nearest cm). + */ + private static final float MAX_ANISOTROPY = 0.025f; + /** * The display content this class is handling recording for. */ @@ -87,15 +100,20 @@ final class ContentRecorder implements WindowContainerListener { @Configuration.Orientation private int mLastOrientation = ORIENTATION_UNDEFINED; + private final boolean mCorrectForAnisotropicPixels; + ContentRecorder(@NonNull DisplayContent displayContent) { - this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId)); + this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId), + new DisplayManagerFlags().isConnectedDisplayManagementEnabled()); } @VisibleForTesting ContentRecorder(@NonNull DisplayContent displayContent, - @NonNull MediaProjectionManagerWrapper mediaProjectionManager) { + @NonNull MediaProjectionManagerWrapper mediaProjectionManager, + boolean correctForAnisotropicPixels) { mDisplayContent = displayContent; mMediaProjectionManager = mediaProjectionManager; + mCorrectForAnisotropicPixels = correctForAnisotropicPixels; } /** @@ -460,6 +478,33 @@ final class ContentRecorder implements WindowContainerListener { } } + private void computeScaling(int inputSizeX, int inputSizeY, + float inputDpiX, float inputDpiY, + int outputSizeX, int outputSizeY, + float outputDpiX, float outputDpiY, + PointF scaleOut) { + float relAnisotropy = (inputDpiY / inputDpiX) / (outputDpiY / outputDpiX); + if (!mCorrectForAnisotropicPixels + || (relAnisotropy > (1 - MAX_ANISOTROPY) && relAnisotropy < (1 + MAX_ANISOTROPY))) { + // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the + // output surface. + float scaleX = outputSizeX / (float) inputSizeX; + float scaleY = outputSizeY / (float) inputSizeY; + float scale = Math.min(scaleX, scaleY); + scaleOut.x = scale; + scaleOut.y = scale; + return; + } + + float relDpiX = outputDpiX / inputDpiX; + float relDpiY = outputDpiY / inputDpiY; + + float scale = Math.min(outputSizeX / relDpiX / inputSizeX, + outputSizeY / relDpiY / inputSizeY); + scaleOut.x = scale * relDpiX; + scaleOut.y = scale * relDpiY; + } + /** * Apply transformations to the mirrored surface to ensure the captured contents are scaled to * fit and centred in the output surface. @@ -473,13 +518,19 @@ final class ContentRecorder implements WindowContainerListener { */ @VisibleForTesting void updateMirroredSurface(SurfaceControl.Transaction transaction, Rect recordedContentBounds, Point surfaceSize) { - // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the - // output surface. - float scaleX = surfaceSize.x / (float) recordedContentBounds.width(); - float scaleY = surfaceSize.y / (float) recordedContentBounds.height(); - float scale = Math.min(scaleX, scaleY); - int scaledWidth = Math.round(scale * (float) recordedContentBounds.width()); - int scaledHeight = Math.round(scale * (float) recordedContentBounds.height()); + + DisplayInfo inputDisplayInfo = mRecordedWindowContainer.mDisplayContent.getDisplayInfo(); + DisplayInfo outputDisplayInfo = mDisplayContent.getDisplayInfo(); + + PointF scale = new PointF(); + computeScaling(recordedContentBounds.width(), recordedContentBounds.height(), + inputDisplayInfo.physicalXDpi, inputDisplayInfo.physicalYDpi, + surfaceSize.x, surfaceSize.y, + outputDisplayInfo.physicalXDpi, outputDisplayInfo.physicalYDpi, + scale); + + int scaledWidth = Math.round(scale.x * (float) recordedContentBounds.width()); + int scaledHeight = Math.round(scale.y * (float) recordedContentBounds.height()); // Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored // contents in the output surface. @@ -493,10 +544,10 @@ final class ContentRecorder implements WindowContainerListener { } ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka " - + "recorded content size) %d x %d for display %d; display has size %d x " - + "%d; surface has size %d x %d", - shiftedX, shiftedY, scale, recordedContentBounds.width(), + "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop " + + "(aka recorded content size) %d x %d for display %d; display has size " + + "%d x %d; surface has size %d x %d", + shiftedX, shiftedY, scale.x, scale.y, recordedContentBounds.width(), recordedContentBounds.height(), mDisplayContent.getDisplayId(), mDisplayContent.getConfiguration().screenWidthDp, mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y); @@ -508,7 +559,7 @@ final class ContentRecorder implements WindowContainerListener { recordedContentBounds.height()) // Scale the root mirror SurfaceControl, based upon the size difference between the // source (DisplayArea to capture) and output (surface the app reads images from). - .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale) + .setMatrix(mRecordedSurface, scale.x, 0 /* dtdx */, 0 /* dtdy */, scale.y) // Position needs to be updated when the mirrored DisplayArea has changed, since // the content will no longer be centered in the output surface. .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */); diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java index 2e7ff7a6b9b850f53709cd4e85233a1ec3e603d3..f68e39207b21f95883ecd4ab39b5120705815bda 100644 --- a/services/core/java/com/android/server/wm/SnapshotController.java +++ b/services/core/java/com/android/server/wm/SnapshotController.java @@ -85,7 +85,7 @@ class SnapshotController { if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue; if (info.mContainer.isActivityTypeHome()) continue; final Task task = info.mContainer.asTask(); - if (task != null && !task.isVisibleRequested()) { + if (task != null && !task.mCreatedByOrganizer && !task.isVisibleRequested()) { mTaskSnapshotController.recordSnapshot(task, info); } // Won't need to capture activity snapshot in close transition. diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 94e66ffd8373d5ee51e824516f0d0d485dabb9bc..33ef3c5629e307c1466a2f5df3e563acac0361d1 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -45,7 +45,6 @@ import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; import android.util.ArraySet; import android.util.MathUtils; import android.util.Slog; @@ -117,8 +116,6 @@ class WallpaperController { private boolean mShouldOffsetWallpaperCenter; - final boolean mIsLockscreenLiveWallpaperEnabled; - private final Consumer<WindowState> mFindWallpapers = w -> { if (w.mAttrs.type == TYPE_WALLPAPER) { WallpaperWindowToken token = w.mToken.asWallpaperToken(); @@ -236,9 +233,6 @@ class WallpaperController { WallpaperController(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; - mIsLockscreenLiveWallpaperEnabled = - SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true); - Resources resources = service.mContext.getResources(); mMinWallpaperScale = resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale); diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 1ed14310a500e8bcef727347707c057632577930..15bd6078dc2d1fd4a0d443c72ec8d9beb1bad38b 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -82,18 +82,16 @@ class WallpaperWindowToken extends WindowToken { return; } mShowWhenLocked = showWhenLocked; - if (mDisplayContent.mWallpaperController.mIsLockscreenLiveWallpaperEnabled) { - // Move the window token to the front (private) or back (showWhenLocked). This is - // possible - // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER - // windows. - final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP; - - // Note: Moving all the way to the front or back breaks ordering based on addition - // times. - // We should never have more than one non-animating token of each type. - getParent().positionChildAt(position, this /* child */, false /*includingParents */); - } + // Move the window token to the front (private) or back (showWhenLocked). This is + // possible + // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER + // windows. + final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP; + + // Note: Moving all the way to the front or back breaks ordering based on addition + // times. + // We should never have more than one non-animating token of each type. + getParent().positionChildAt(position, this /* child */, false /*includingParents */); } boolean canShowWhenLocked() { diff --git a/services/midi/Android.bp b/services/midi/Android.bp index 4b5f8a7bf0acd77c93dc5f7af50fc4fdfd3d2409..a385fe38495b4ff92da27d5c4a999b3114cb8b0e 100644 --- a/services/midi/Android.bp +++ b/services/midi/Android.bp @@ -18,8 +18,8 @@ java_library_static { name: "services.midi", defaults: ["platform_service_defaults"], srcs: [":services.midi-sources"], - libs: ["services.core"], - static_libs: [ + libs: [ + "services.core", "aconfig_midi_flags_java_lib", ], } diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp index 6eacef767042156486cb599ea050b97114c20e77..c617ec49ab32b3aa186bbd28fbd98f9e9ca91469 100644 --- a/services/tests/PackageManagerServiceTests/host/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -58,6 +58,7 @@ java_test_host { ":PackageManagerTestOverlayTarget", ":PackageManagerTestOverlayTargetNoOverlayable", ":PackageManagerTestAppDeclaresStaticLibrary", + ":PackageManagerTestAppDifferentPkgName", ":PackageManagerTestAppStub", ":PackageManagerTestAppUsesStaticLibrary", ":PackageManagerTestAppVersion1", diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt index c4906041ea5de10c78202c9bf9e782c642e68bd1..304f605d5b95abdcb9b381254a155dfa00d47fe0 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt @@ -44,6 +44,10 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() { private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk" private const val VERSION_TWO_ALT_KEY_IDSIG = "PackageManagerTestAppVersion2AltKey.apk.idsig" + + private const val ANOTHER_PKG_NAME = "com.android.server.pm.test.test_app2" + private const val ANOTHER_PKG = "PackageManagerTestAppDifferentPkgName.apk" + private const val STRICT_SIGNATURE_CONFIG_PATH = "/system/etc/sysconfig/preinstalled-packages-strict-signature.xml" private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref" @@ -74,6 +78,7 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() { @After fun removeApk() { device.uninstallPackage(TEST_PKG_NAME) + device.uninstallPackage(ANOTHER_PKG_NAME) } @Before @@ -90,7 +95,9 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() { .readText() .replace( "</config>", - "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>" + "<require-strict-signature package=\"${TEST_PKG_NAME}\"/>" + + "<require-strict-signature package=\"${ANOTHER_PKG_NAME}\"/>" + + "</config>" ) writeText(newConfigText) } @@ -146,10 +153,7 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() { tempFolder.newFile() ) assertThat(device.installPackage(versionTwoFile, true)).isNull() - val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}") - .lineSequence() - .first() - .replace("package:", "") + val baseApkPath = getBaseApkPath(TEST_PKG_NAME) assertThat(baseApkPath).doesNotContain(productPath.toString()) preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig") @@ -175,4 +179,23 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() { assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) .contains(productPath.toString()) } + + @Test + fun allowlistedPackageIsNotASystemApp() { + // If an allowlisted package isn't a system app, make sure install and boot still works + // normally. + assertThat(device.installJavaResourceApk(tempFolder, ANOTHER_PKG, /* reinstall */ false)) + .isNull() + assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/") + + preparer.reboot() + assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/") + } + + private fun getBaseApkPath(pkgName: String): String { + return device.executeShellCommand("pm path $pkgName") + .lineSequence() + .first() + .replace("package:", "") + } } diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp index bee7c4019fc16ffcb42cf2695736bf9b283f0e51..b826590b7440220be9b2c8982fa6b9a7b5dcdaec 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp @@ -76,3 +76,11 @@ android_test_helper_app { certificate: ":FrameworksServicesTests_keyset_A_cert", v4_signature: true, } + +android_test_helper_app { + name: "PackageManagerTestAppDifferentPkgName", + manifest: "AndroidManifestDifferentPkgName.xml", + srcs: [ + "src/**/*.kt", + ], +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml new file mode 100644 index 0000000000000000000000000000000000000000..0c5d36e389726956e6f9881d0fe88093c02b5258 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.test_app2" + android:versionCode="1" + > + + <permission + android:name="com.android.server.pm.test.test_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + + <application> + <activity android:name="com.android.server.pm.test.test_app.TestActivity" + android:label="PackageManagerTestApp" /> + </application> + +</manifest> diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java index c12aedb72350c12126cc19dca58c7f30edadf5a9..a400f1243afb1e7cd62d7ae900cf3f6eb8862482 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java @@ -90,7 +90,9 @@ public class DisplayBrightnessStateTest { .append("\n isSlowChange:") .append(displayBrightnessState.isSlowChange()) .append("\n maxBrightness:") - .append(displayBrightnessState.getMaxBrightness()); + .append(displayBrightnessState.getMaxBrightness()) + .append("\n customAnimationRate:") + .append(displayBrightnessState.getCustomAnimationRate()); return sb.toString(); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index 8b54d6d22c2b9744d16d91c8f1c2cca89c8c8422..47521d13e49c6804f2a03a335d9b6c54650ac827 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -74,6 +74,7 @@ import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; +import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.color.ColorDisplayService; import com.android.server.display.feature.DisplayManagerFlags; @@ -1313,6 +1314,54 @@ public final class DisplayPowerController2Test { eq(transitionRate), eq(true)); } + @Test + public void testRampRateForClampersControllerApplied() { + float transitionRate = 1.5f; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f); + when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f); + when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer( + invocation -> DisplayBrightnessState.builder() + .setIsSlowChange(invocation.getArgument(2)) + .setBrightness(invocation.getArgument(1)) + .setMaxBrightness(PowerManager.BRIGHTNESS_MAX) + .setCustomAnimationRate(transitionRate).build()); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(), + eq(transitionRate), anyBoolean()); + } + + @Test + public void testRampRateForClampersControllerNotApplied_ifDoze() { + float transitionRate = 1.5f; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + dpr.dozeScreenState = Display.STATE_UNKNOWN; + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f); + when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f); + when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer( + invocation -> DisplayBrightnessState.builder() + .setIsSlowChange(invocation.getArgument(2)) + .setBrightness(invocation.getArgument(1)) + .setMaxBrightness(PowerManager.BRIGHTNESS_MAX) + .setCustomAnimationRate(transitionRate).build()); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(), + eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean()); + verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(), + eq(transitionRate), anyBoolean()); + } + @Test @RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1) public void testRampMaxTimeInteractiveThenIdle() { @@ -1637,13 +1686,20 @@ public final class DisplayPowerController2Test { mock(ScreenOffBrightnessSensorController.class); final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class); final HdrClamper hdrClamper = mock(HdrClamper.class); + BrightnessClamperController clamperController = mock(BrightnessClamperController.class); when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX); + when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer( + invocation -> DisplayBrightnessState.builder() + .setIsSlowChange(invocation.getArgument(2)) + .setBrightness(invocation.getArgument(1)) + .setMaxBrightness(PowerManager.BRIGHTNESS_MAX) + .setCustomAnimationRate(-1).build()); TestInjector injector = spy(new TestInjector(displayPowerState, animator, automaticBrightnessController, wakelockController, brightnessMappingStrategy, hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper, - flags)); + clamperController, flags)); final LogicalDisplay display = mock(LogicalDisplay.class); final DisplayDevice device = mock(DisplayDevice.class); @@ -1662,8 +1718,8 @@ public final class DisplayPowerController2Test { return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, wakelockController, - screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata, - brightnessMappingStrategy, injector, config); + screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController, + hbmMetadata, brightnessMappingStrategy, injector, config); } /** @@ -1682,6 +1738,7 @@ public final class DisplayPowerController2Test { public final HighBrightnessModeController hbmController; public final HdrClamper hdrClamper; + public final BrightnessClamperController clamperController; public final HighBrightnessModeMetadata hbmMetadata; public final BrightnessMappingStrategy brightnessMappingStrategy; public final DisplayPowerController2.Injector injector; @@ -1695,6 +1752,7 @@ public final class DisplayPowerController2Test { ScreenOffBrightnessSensorController screenOffBrightnessSensorController, HighBrightnessModeController hbmController, HdrClamper hdrClamper, + BrightnessClamperController clamperController, HighBrightnessModeMetadata hbmMetadata, BrightnessMappingStrategy brightnessMappingStrategy, DisplayPowerController2.Injector injector, @@ -1709,6 +1767,7 @@ public final class DisplayPowerController2Test { this.screenOffBrightnessSensorController = screenOffBrightnessSensorController; this.hbmController = hbmController; this.hdrClamper = hdrClamper; + this.clamperController = clamperController; this.hbmMetadata = hbmMetadata; this.brightnessMappingStrategy = brightnessMappingStrategy; this.injector = injector; @@ -1728,6 +1787,8 @@ public final class DisplayPowerController2Test { private final HdrClamper mHdrClamper; + private final BrightnessClamperController mClamperController; + private final DisplayManagerFlags mFlags; TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator, @@ -1738,6 +1799,7 @@ public final class DisplayPowerController2Test { ScreenOffBrightnessSensorController screenOffBrightnessSensorController, HighBrightnessModeController highBrightnessModeController, HdrClamper hdrClamper, + BrightnessClamperController clamperController, DisplayManagerFlags flags) { mDisplayPowerState = dps; mAnimator = animator; @@ -1748,6 +1810,7 @@ public final class DisplayPowerController2Test { mScreenOffBrightnessSensorController = screenOffBrightnessSensorController; mHighBrightnessModeController = highBrightnessModeController; mHdrClamper = hdrClamper; + mClamperController = clamperController; mFlags = flags; } @@ -1863,6 +1926,14 @@ public final class DisplayPowerController2Test { displayDeviceConfig, mHdrClamper, mFlags, displayToken, info); } + @Override + BrightnessClamperController getBrightnessClamperController(Handler handler, + BrightnessClamperController.ClamperChangeListener clamperChangeListener, + BrightnessClamperController.DisplayDeviceData data, Context context, + DisplayManagerFlags flags) { + return mClamperController; + } + @Override DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, SensorManager sensorManager, Resources resources) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index c0e0df98213f9fae49cb1ca2f67ec5fc321c1c95..ff2b1f466a5c9dc8f0e9e65b1f28c0383cc56927 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -137,8 +137,10 @@ public class BrightnessClamperControllerTest { float initialBrightness = 0.8f; boolean initialSlowChange = true; float clampedBrightness = 0.6f; + float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(false); mTestInjector.mCapturedChangeListener.onChanged(); mTestHandler.flush(); @@ -150,6 +152,7 @@ public class BrightnessClamperControllerTest { assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE); assertEquals(0, state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED); + assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE); assertEquals(initialSlowChange, state.isSlowChange()); } @@ -158,8 +161,10 @@ public class BrightnessClamperControllerTest { float initialBrightness = 0.8f; boolean initialSlowChange = true; float clampedBrightness = 0.6f; + float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); mTestHandler.flush(); @@ -171,6 +176,7 @@ public class BrightnessClamperControllerTest { assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE); assertEquals(BrightnessReason.MODIFIER_THROTTLED, state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED); + assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE); assertFalse(state.isSlowChange()); } @@ -179,8 +185,10 @@ public class BrightnessClamperControllerTest { float initialBrightness = 0.6f; boolean initialSlowChange = true; float clampedBrightness = 0.8f; + float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); mTestHandler.flush(); @@ -192,6 +200,7 @@ public class BrightnessClamperControllerTest { assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE); assertEquals(BrightnessReason.MODIFIER_THROTTLED, state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED); + assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE); assertFalse(state.isSlowChange()); } @@ -200,8 +209,10 @@ public class BrightnessClamperControllerTest { float initialBrightness = 0.8f; boolean initialSlowChange = true; float clampedBrightness = 0.6f; + float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); mTestHandler.flush(); @@ -216,6 +227,7 @@ public class BrightnessClamperControllerTest { assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE); assertEquals(BrightnessReason.MODIFIER_THROTTLED, state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED); + assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE); assertEquals(initialSlowChange, state.isSlowChange()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index eefe5af314a679f08cbf5c5bc86bd4da97820651..3dbab1360af42896e2e45453126c437e0571a296 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -462,7 +462,7 @@ public class WallpaperManagerServiceTests { wallpaper.wallpaperObserver.stopWatching(); spyOn(wallpaper.wallpaperObserver); - doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false); + doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(false); wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER); // ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged. diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 6bfd93b66f6179dc1c8d2ea4eaa0554db27e6bc3..4bb7d63995acd5d5458d8a74e5bdfeb4d409d265 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -4,6 +4,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static com.android.server.job.JobStore.JOB_FILE_SPLIT_PREFIX; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -46,6 +48,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; +import java.nio.file.Files; import java.time.Clock; import java.time.ZoneOffset; import java.util.ArrayList; @@ -209,6 +212,43 @@ public class JobStoreTest { assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size()); } + @Test + public void testSkipExtraFiles() throws Exception { + setUseSplitFiles(true); + final JobInfo task1 = new Builder(8, mComponent) + .setRequiresDeviceIdle(true) + .setPeriodic(10000L) + .setRequiresCharging(true) + .setPersisted(true) + .build(); + final JobInfo task2 = new Builder(12, mComponent) + .setMinimumLatency(5000L) + .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR) + .setOverrideDeadline(30000L) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) + .setPersisted(true) + .build(); + final int uid1 = SOME_UID; + final int uid2 = uid1 + 1; + final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null); + final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null); + runWritingJobsToDisk(JobStatus1, JobStatus2); + + final File rootDir = new File(mTestContext.getFilesDir(), "system/job"); + final File file1 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml"); + final File file2 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid2 + ".xml"); + + Files.copy(file1.toPath(), + new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml.bak").toPath()); + Files.copy(file1.toPath(), new File(rootDir, "random.xml").toPath()); + Files.copy(file2.toPath(), + new File(rootDir, "blah" + JOB_FILE_SPLIT_PREFIX + uid1 + ".xml").toPath()); + + JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size()); + } + /** * Test that dynamic constraints aren't written to disk. */ @@ -254,22 +294,22 @@ public class JobStoreTest { file = new File(mTestContext.getFilesDir(), "10000"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); - file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX); + file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); - file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml"); + file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "text.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); - file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml"); + file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + ".xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); - file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml"); + file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "-10123.xml"); assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file)); - file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml"); + file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "1.xml"); assertEquals(1, JobStore.extractUidFromJobFileName(file)); - file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml"); + file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "101023.xml"); assertEquals(101023, JobStore.extractUidFromJobFileName(file)); } diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index f94aff706a67015e9225588d6587d78813a85f6e..9f6d7f223716ad3fe2c7fb64ccf63e8fc5b60d2a 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -27,6 +27,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +40,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; @@ -133,7 +135,7 @@ public class MediaProjectionManagerServiceTest { private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector = new MediaProjectionManagerService.Injector() { @Override - MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { + MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) { return mMediaProjectionMetricsLogger; } }; @@ -310,6 +312,70 @@ public class MediaProjectionManagerServiceTest { assertThat(secondProjection).isNotEqualTo(projection); } + @Test + public void stop_noActiveProjections_doesNotLog() throws Exception { + MediaProjectionManagerService service = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = + startProjectionPreconditions(service); + + projection.stop(); + + verifyZeroInteractions(mMediaProjectionMetricsLogger); + } + + @Test + public void stop_noSession_logsHostUidAndUnknownTargetUid() throws Exception { + MediaProjectionManagerService service = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = + startProjectionPreconditions(service); + projection.start(mIMediaProjectionCallback); + + projection.stop(); + + verify(mMediaProjectionMetricsLogger) + .logStopped(UID, ContentRecordingSession.TARGET_UID_UNKNOWN); + } + + @Test + public void stop_displaySession_logsHostUidAndUnknownTargetUidFullScreen() throws Exception { + MediaProjectionManagerService service = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = + startProjectionPreconditions(service); + projection.start(mIMediaProjectionCallback); + doReturn(true) + .when(mWindowManagerInternal) + .setContentRecordingSession(any(ContentRecordingSession.class)); + service.setContentRecordingSession(DISPLAY_SESSION); + + projection.stop(); + + verify(mMediaProjectionMetricsLogger) + .logStopped(UID, ContentRecordingSession.TARGET_UID_FULL_SCREEN); + } + + @Test + public void stop_taskSession_logsHostUidAndTargetUid() throws Exception { + int targetUid = 1234; + MediaProjectionManagerService service = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = + startProjectionPreconditions(service); + projection.start(mIMediaProjectionCallback); + doReturn(true) + .when(mWindowManagerInternal) + .setContentRecordingSession(any(ContentRecordingSession.class)); + ContentRecordingSession taskSession = + ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid); + service.setContentRecordingSession(taskSession); + + projection.stop(); + + verify(mMediaProjectionMetricsLogger).logStopped(UID, targetUid); + } + @Test public void testIsValid_multipleStarts_preventionDisabled() throws NameNotFoundException { MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, @@ -586,6 +652,18 @@ public class MediaProjectionManagerServiceTest { /* isSetSessionSuccessful= */ false, RECORD_CANCEL); } + @Test + public void notifyPermissionRequestInitiated_forwardsToLogger() { + int hostUid = 123; + int sessionCreationSource = 456; + mService = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + + mService.notifyPermissionRequestInitiated(hostUid, sessionCreationSource); + + verify(mMediaProjectionMetricsLogger).logInitiated(hostUid, sessionCreationSource); + } + /** * Executes and validates scenario where the consent result indicates the projection ends. */ @@ -749,8 +827,10 @@ public class MediaProjectionManagerServiceTest { public void setContentRecordingSession_success_logsCaptureInProgress() throws Exception { mService.addCallback(mWatcherCallback); - MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); - MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + MediaProjectionManagerService service = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = + startProjectionPreconditions(service); projection.start(mIMediaProjectionCallback); doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( any(ContentRecordingSession.class)); diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..73b4cc8cc59298b4fab88598118de2156ab9a9c7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media.projection; + +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.util.FrameworkStatsLog; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.time.Duration; + +/** + * Tests for the {@link MediaProjectionMetricsLoggerTest} class. + * + * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionMetricsLoggerTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class MediaProjectionMetricsLoggerTest { + + private static final int TEST_HOST_UID = 123; + private static final int TEST_TARGET_UID = 456; + private static final int TEST_CREATION_SOURCE = 789; + + @Mock private FrameworkStatsLogWrapper mFrameworkStatsLogWrapper; + @Mock private MediaProjectionSessionIdGenerator mSessionIdGenerator; + @Mock private MediaProjectionTimestampStore mTimestampStore; + + private MediaProjectionMetricsLogger mLogger; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mLogger = + new MediaProjectionMetricsLogger( + mFrameworkStatsLogWrapper, mSessionIdGenerator, mTimestampStore); + } + + @Test + public void logInitiated_logsStateChangedAtomId() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + + verifyStateChangedAtomIdLogged(); + } + + @Test + public void logInitiated_logsStateInitiated() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + + verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED); + } + + @Test + public void logInitiated_logsHostUid() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + + verifyHostUidLogged(TEST_HOST_UID); + } + + @Test + public void logInitiated_logsSessionCreationSource() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + + verifyCreationSourceLogged(TEST_CREATION_SOURCE); + } + + @Test + public void logInitiated_logsUnknownTargetUid() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + + verifyTargetUidLogged(-2); + } + + @Test + public void logInitiated_noPreviousSession_logsUnknownTimeSinceLastActive() { + when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(null); + + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + + verifyTimeSinceLastActiveSessionLogged(-1); + } + + @Test + public void logInitiated_previousSession_logsTimeSinceLastActiveInSeconds() { + Duration timeSinceLastActiveSession = Duration.ofHours(1234); + when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(timeSinceLastActiveSession); + + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + + verifyTimeSinceLastActiveSessionLogged((int) timeSinceLastActiveSession.toSeconds()); + } + + @Test + public void logInitiated_logsNewSessionId() { + int newSessionId = 123; + when(mSessionIdGenerator.createAndGetNewSessionId()).thenReturn(newSessionId); + + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + + verifySessionIdLogged(newSessionId); + } + + @Test + public void logInitiated_logsPreviousState() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN); + + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED); + } + + @Test + public void logStopped_logsStateChangedAtomId() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verifyStateChangedAtomIdLogged(); + } + + @Test + public void logStopped_logsCurrentSessionId() { + int currentSessionId = 987; + when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId); + + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verifySessionIdLogged(currentSessionId); + } + + @Test + public void logStopped_logsStateStopped() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED); + } + + @Test + public void logStopped_logsHostUid() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verifyHostUidLogged(TEST_HOST_UID); + } + + @Test + public void logStopped_logsTargetUid() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verifyTargetUidLogged(TEST_TARGET_UID); + } + + @Test + public void logStopped_logsUnknownTimeSinceLastActive() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verifyTimeSinceLastActiveSessionLogged(-1); + } + + @Test + public void logStopped_logsUnknownSessionCreationSource() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verifyCreationSourceLogged( + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + + @Test + public void logStopped_logsPreviousState() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN); + + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED); + + mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED); + } + + @Test + public void logStopped_registersActiveSessionEnded_afterLogging() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + InOrder inOrder = inOrder(mFrameworkStatsLogWrapper, mTimestampStore); + inOrder.verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* state= */ anyInt(), + /* previousState= */ anyInt(), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* timeSinceLastActive= */ anyInt(), + /* creationSource= */ anyInt()); + inOrder.verify(mTimestampStore).registerActiveSessionEnded(); + } + + private void verifyStateChangedAtomIdLogged() { + verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ eq(FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED), + /* sessionId= */ anyInt(), + /* state= */ anyInt(), + /* previousState= */ anyInt(), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* timeSinceLastActive= */ anyInt(), + /* creationSource= */ anyInt()); + } + + private void verifyStateLogged(int state) { + verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + eq(state), + /* previousState= */ anyInt(), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* timeSinceLastActive= */ anyInt(), + /* creationSource= */ anyInt()); + } + + private void verifyHostUidLogged(int hostUid) { + verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* state= */ anyInt(), + /* previousState= */ anyInt(), + eq(hostUid), + /* targetUid= */ anyInt(), + /* timeSinceLastActive= */ anyInt(), + /* creationSource= */ anyInt()); + } + + private void verifyCreationSourceLogged(int creationSource) { + verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* state= */ anyInt(), + /* previousState= */ anyInt(), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* timeSinceLastActive= */ anyInt(), + eq(creationSource)); + } + + private void verifyTargetUidLogged(int targetUid) { + verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* state= */ anyInt(), + /* previousState= */ anyInt(), + /* hostUid= */ anyInt(), + eq(targetUid), + /* timeSinceLastActive= */ anyInt(), + /* creationSource= */ anyInt()); + } + + private void verifyTimeSinceLastActiveSessionLogged(int timeSinceLastActiveSession) { + verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* state= */ anyInt(), + /* previousState= */ anyInt(), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* timeSinceLastActive= */ eq(timeSinceLastActiveSession), + /* creationSource= */ anyInt()); + } + + private void verifySessionIdLogged(int newSessionId) { + verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ anyInt(), + /* sessionId= */ eq(newSessionId), + /* state= */ anyInt(), + /* previousState= */ anyInt(), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* timeSinceLastActive= */ anyInt(), + /* creationSource= */ anyInt()); + } + + private void verifyPreviousStateLogged(int previousState) { + verify(mFrameworkStatsLogWrapper) + .write( + /* code= */ anyInt(), + /* sessionId= */ anyInt(), + /* state= */ anyInt(), + eq(previousState), + /* hostUid= */ anyInt(), + /* targetUid= */ anyInt(), + /* timeSinceLastActive= */ anyInt(), + /* creationSource= */ anyInt()); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 9bd938f2e0a709c4e1b29325b2df414b9b9763aa..cf8548cfe68903f527f782467f67fc00cc84ee31 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -80,7 +80,9 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; + import androidx.test.runner.AndroidJUnit4; + import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.config.sysui.TestableFlagResolver; import com.android.internal.logging.InstanceIdSequence; @@ -93,6 +95,7 @@ import com.android.server.pm.PackageManagerService; import java.util.List; import java.util.Objects; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -190,7 +193,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { assertTrue(mAccessibilityManager.isEnabled()); // TODO (b/291907312): remove feature flag - mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true); + mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER); // Disable feature flags by default. Tests should enable as needed. mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 75d012a8e1f2c1b5c853f8ce844087c1d5735575..6792cfe6e78897dc18ffa31c3a4c02e8ac97bc8c 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -71,6 +71,7 @@ import static android.os.UserHandle.USER_SYSTEM; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; +import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -84,7 +85,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; -import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; @@ -207,6 +207,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.permission.PermissionManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; @@ -318,6 +319,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private static final int UID_HEADLESS = 1_000_000; private static final int TOAST_DURATION = 2_000; private static final int SECONDARY_DISPLAY_ID = 42; + private static final int TEST_PROFILE_USERHANDLE = 12; private final int mUid = Binder.getCallingUid(); private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); @@ -445,7 +447,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker; TestableFlagResolver mTestFlagResolver = new TestableFlagResolver(); - + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @Mock @@ -611,7 +613,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { }); // TODO (b/291907312): remove feature flag - mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, false); + mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER, + Flags.FLAG_POLITE_NOTIFICATIONS); initNMS(); } @@ -652,7 +655,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mHistoryManager).onBootPhaseAppsCanStart(); // TODO b/291907312: remove feature flag - if (mTestFlagResolver.isEnabled(ENABLE_ATTENTION_HELPER_REFACTOR)) { + if (Flags.refactorAttentionHelper()) { mService.mAttentionHelper.setAudioManager(mAudioManager); } else { mService.setAudioManager(mAudioManager); @@ -826,6 +829,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver.onReceive(getContext(), intent); } + private void simulateProfileAvailabilityActions(String intentAction) { + final Intent intent = new Intent(intentAction); + intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE); + mUserSwitchIntentReceiver.onReceive(mContext, intent); + } + private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() { ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>(); changed.put(true, new ArrayList<>()); @@ -1683,7 +1692,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testEnqueueNotificationWithTag_WritesExpectedLogs_NAHRefactor() throws Exception { // TODO b/291907312: remove feature flag - mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true); + mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER); // Cleanup NMS before re-initializing if (mService != null) { try { @@ -9146,7 +9155,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped_NAHRefactor() throws Exception { // TODO b/291907312: remove feature flag - mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true); + mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER); // Cleanup NMS before re-initializing if (mService != null) { try { @@ -12751,6 +12760,23 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(service, times(1)).setDNDMigrationDone(user.id); } + @Test + public void testProfileUnavailableIntent() throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE); + verify(mWorkerHandler).post(any(Runnable.class)); + verify(mSnoozeHelper).clearData(anyInt()); + } + + + @Test + public void testManagedProfileUnavailableIntent() throws RemoteException { + mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + verify(mWorkerHandler).post(any(Runnable.class)); + verify(mSnoozeHelper).clearData(anyInt()); + } + private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index 121e29609df5ccf8ddd0d383fe025e75e201e2fc..337dd22e87120145ca251e16f7a77d5e5ddd1d38 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -91,7 +91,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { private static final Multimap<Class<?>, String> KNOWN_BAD = ImmutableMultimap.<Class<?>, String>builder() .put(Person.Builder.class, "setUri") // TODO: b/281044385 - .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385 .build(); // Types that we can't really produce. No methods receiving these parameters will be invoked. diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index af39b2f027eecdfd2de9bff6df1c5262ced56643..1b8d746f271fa8ebe223fd0d15a435f9d4da3db0 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -60,6 +60,7 @@ android_test { "truth", "testables", "hamcrest-library", + "flag-junit", "platform-compat-test-rules", "CtsSurfaceValidatorLib", "service-sdksandbox.impl", diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java index 8db09f9e3a16119318fdc32169084000c666ed52..61c4d06131e16bf489dcddce85ac6a8430ccf0d9 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -46,7 +46,6 @@ import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER; import static java.util.Collections.unmodifiableMap; import android.content.Context; -import android.os.Looper; import android.os.SystemClock; import android.util.ArrayMap; import android.view.InputDevice; @@ -99,10 +98,6 @@ class ShortcutKeyTestBase { * settings values. */ protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - doReturn(mSettingsProviderRule.mockContentResolver(mContext)) .when(mContext).getContentResolver(); mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate); diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 2244dbe8af98357be24a95253acb68f03e591803..261d3cc3c8d9606ca80161fbad82753964c525de 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -78,6 +78,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.Vibrator; import android.os.VibratorInfo; +import android.os.test.TestLooper; import android.service.dreams.DreamManagerInternal; import android.telecom.TelecomManager; import android.util.FeatureFlagUtils; @@ -160,12 +161,13 @@ class TestPhoneWindowManager { @Mock private KeyguardServiceDelegate mKeyguardServiceDelegate; private StaticMockitoSession mMockitoSession; + private TestLooper mTestLooper = new TestLooper(); private HandlerThread mHandlerThread; private Handler mHandler; private class TestInjector extends PhoneWindowManager.Injector { TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) { - super(context, funcs); + super(context, funcs, mTestLooper.getLooper()); } AccessibilityShortcutController getAccessibilityShortcutController( @@ -184,12 +186,10 @@ class TestPhoneWindowManager { TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) { MockitoAnnotations.initMocks(this); - mHandlerThread = new HandlerThread("fake window manager"); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); + mHandler = new Handler(mTestLooper.getLooper()); mContext = mockingDetails(context).isSpy() ? context : spy(context); - mHandler.runWithScissors(() -> setUp(supportSettingsUpdate), 0 /* timeout */); - waitForIdle(); + mHandler.post(() -> setUp(supportSettingsUpdate)); + mTestLooper.dispatchAll(); } private void setUp(boolean supportSettingsUpdate) { @@ -301,7 +301,6 @@ class TestPhoneWindowManager { } void tearDown() { - mHandlerThread.quitSafely(); LocalServices.removeServiceForTest(InputMethodManagerInternal.class); Mockito.reset(mPhoneWindowManager); mMockitoSession.finishMocking(); @@ -327,10 +326,6 @@ class TestPhoneWindowManager { mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE); } - void waitForIdle() { - mHandler.runWithScissors(() -> { }, 0 /* timeout */); - } - /** * Below functions will override the setting or the policy behavior. */ @@ -504,13 +499,13 @@ class TestPhoneWindowManager { * Below functions will check the policy behavior could be invoked. */ void assertTakeScreenshotCalled() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS)) .takeScreenshot(anyInt(), anyInt()); } void assertShowGlobalActionsCalled() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mPhoneWindowManager).showGlobalActions(); verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS)) .showDialog(anyBoolean(), anyBoolean()); @@ -519,53 +514,53 @@ class TestPhoneWindowManager { } void assertVolumeMute() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS)) .silenceRingerModeInternal(eq("volume_hush")); } void assertAccessibilityKeychordCalled() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mAccessibilityShortcutController, timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut(); } void assertDreamRequest() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mDreamManagerInternal).requestDream(); } void assertPowerSleep() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mPowerManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt()); } void assertPowerWakeUp() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mPowerManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString()); } void assertNoPowerSleep() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt()); } void assertCameraLaunch() { - waitForIdle(); + mTestLooper.dispatchAll(); // GestureLauncherService should receive interceptPowerKeyDown twice. verify(mGestureLauncherService, times(2)) .interceptPowerKeyDown(any(), anyBoolean(), any()); } void assertSearchManagerLaunchAssist() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any()); } void assertLaunchCategory(String category) { - waitForIdle(); + mTestLooper.dispatchAll(); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); try { verify(mContext).startActivityAsUser(intentCaptor.capture(), any()); @@ -578,17 +573,17 @@ class TestPhoneWindowManager { } void assertShowRecentApps() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mStatusBarManagerInternal).showRecentApps(anyBoolean()); } void assertStatusBarStartAssist() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mStatusBarManagerInternal).startAssist(any()); } void assertSwitchKeyboardLayout(int direction) { - waitForIdle(); + mTestLooper.dispatchAll(); if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction)); verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt()); @@ -599,7 +594,7 @@ class TestPhoneWindowManager { } void assertTakeBugreport() { - waitForIdle(); + mTestLooper.dispatchAll(); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext).sendOrderedBroadcastAsUser(intentCaptor.capture(), any(), any(), any(), any(), anyInt(), any(), any()); @@ -607,17 +602,17 @@ class TestPhoneWindowManager { } void assertTogglePanel() throws RemoteException { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mPhoneWindowManager.mStatusBarService).togglePanel(); } void assertToggleShortcutsMenu() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mStatusBarManagerInternal).toggleKeyboardShortcutsMenu(anyInt()); } void assertToggleCapsLock() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mInputManagerInternal).toggleCapsLock(anyInt()); } @@ -642,12 +637,12 @@ class TestPhoneWindowManager { } void assertGoToHomescreen() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt()); } void assertOpenAllAppView() { - waitForIdle(); + mTestLooper.dispatchAll(); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); @@ -655,13 +650,13 @@ class TestPhoneWindowManager { } void assertNotOpenAllAppView() { - waitForIdle(); + mTestLooper.dispatchAll(); verify(mContext, after(TEST_SINGLE_KEY_DELAY_MILLIS).never()) .startActivityAsUser(any(Intent.class), any(), any(UserHandle.class)); } void assertActivityTargetLaunched(ComponentName targetActivity) { - waitForIdle(); + mTestLooper.dispatchAll(); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); @@ -670,7 +665,7 @@ class TestPhoneWindowManager { void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent, int expectedKey, int expectedModifierState, String errorMsg) { - waitForIdle(); + mTestLooper.dispatchAll(); verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED, vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey}, expectedModifierState), description(errorMsg)); diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index d2eb1cc0222b7556a9e53053632b01f952dc8ea9..78566fb06179fe2908b29e0166c5494fc64114e7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -84,31 +84,49 @@ public class ContentRecorderTests extends WindowTestsBase { private final ContentRecordingSession mWaitingDisplaySession = ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY); private ContentRecordingSession mTaskSession; - private static Point sSurfaceSize; + private Point mSurfaceSize; private ContentRecorder mContentRecorder; @Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper; private SurfaceControl mRecordedSurface; + private boolean mHandleAnisotropicDisplayMirroring = false; + @Before public void setUp() { MockitoAnnotations.initMocks(this); - // GIVEN SurfaceControl can successfully mirror the provided surface. - sSurfaceSize = new Point( - mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(), + doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt()); + + // Skip unnecessary operations of relayout. + spyOn(mWm.mWindowPlacerLocked); + doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean()); + } + + private void defaultInit() { + createContentRecorder(createDefaultDisplayInfo()); + } + + private DisplayInfo createDefaultDisplayInfo() { + return createDisplayInfo(mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(), mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height()); - mRecordedSurface = surfaceControlMirrors(sSurfaceSize); + } - doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt()); + private DisplayInfo createDisplayInfo(int width, int height) { + // GIVEN SurfaceControl can successfully mirror the provided surface. + mSurfaceSize = new Point(width, height); + mRecordedSurface = surfaceControlMirrors(mSurfaceSize); - // GIVEN the VirtualDisplay associated with the session (so the display has state ON). DisplayInfo displayInfo = mDisplayInfo; - displayInfo.logicalWidth = sSurfaceSize.x; - displayInfo.logicalHeight = sSurfaceSize.y; + displayInfo.logicalWidth = width; + displayInfo.logicalHeight = height; displayInfo.state = STATE_ON; + return displayInfo; + } + + private void createContentRecorder(DisplayInfo displayInfo) { mVirtualDisplayContent = createNewDisplay(displayInfo); final int displayId = mVirtualDisplayContent.getDisplayId(); mContentRecorder = new ContentRecorder(mVirtualDisplayContent, - mMediaProjectionManagerWrapper); + mMediaProjectionManagerWrapper, mHandleAnisotropicDisplayMirroring); spyOn(mVirtualDisplayContent); // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to @@ -124,14 +142,11 @@ public class ContentRecorderTests extends WindowTestsBase { // GIVEN a session is waiting for the user to review consent. mWaitingDisplaySession.setVirtualDisplayId(displayId); mWaitingDisplaySession.setWaitingForConsent(true); - - // Skip unnecessary operations of relayout. - spyOn(mWm.mWindowPlacerLocked); - doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean()); } @Test public void testIsCurrentlyRecording() { + defaultInit(); assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); mContentRecorder.updateRecording(); @@ -140,6 +155,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testUpdateRecording_display() { + defaultInit(); mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); @@ -147,6 +163,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testUpdateRecording_display_invalidDisplayIdToMirror() { + defaultInit(); ContentRecordingSession session = ContentRecordingSession.createDisplaySession( INVALID_DISPLAY); mContentRecorder.setContentRecordingSession(session); @@ -156,6 +173,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testUpdateRecording_display_noDisplayContentToMirror() { + defaultInit(); doReturn(null).when( mWm.mRoot).getDisplayContent(anyInt()); mContentRecorder.setContentRecordingSession(mDisplaySession); @@ -165,6 +183,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testUpdateRecording_task_nullToken() { + defaultInit(); ContentRecordingSession session = mTaskSession; session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId()); session.setTokenToRecord(null); @@ -176,6 +195,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testUpdateRecording_task_noWindowContainer() { + defaultInit(); // Use the window container token of the DisplayContent, rather than task. ContentRecordingSession invalidTaskSession = ContentRecordingSession.createTaskSession( new WindowContainer.RemoteToken(mDisplayContent)); @@ -187,6 +207,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testUpdateRecording_wasPaused() { + defaultInit(); mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); @@ -197,6 +218,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testUpdateRecording_waitingForConsent() { + defaultInit(); mContentRecorder.setContentRecordingSession(mWaitingDisplaySession); mContentRecorder.updateRecording(); assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); @@ -209,6 +231,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnConfigurationChanged_neverRecording() { + defaultInit(); mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT); verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat()); @@ -218,6 +241,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnConfigurationChanged_resizesSurface() { + defaultInit(); mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); // Ensure a different orientation when we check if something has changed. @@ -234,13 +258,14 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnConfigurationChanged_resizesVirtualDisplay() { + defaultInit(); final int newWidth = 55; mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); // The user rotates the device, so the host app resizes the virtual display for the capture. - resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y); - resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y); + resizeDisplay(mDisplayContent, newWidth, mSurfaceSize.y); + resizeDisplay(mVirtualDisplayContent, newWidth, mSurfaceSize.y); mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation); verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(), @@ -251,6 +276,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnConfigurationChanged_rotateVirtualDisplay() { + defaultInit(); mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); @@ -271,12 +297,13 @@ public class ContentRecorderTests extends WindowTestsBase { */ @Test public void testOnConfigurationChanged_resizeSurface() { + defaultInit(); mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); // Resize the output surface. - final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f), - Math.round(sSurfaceSize.y * 2)); + final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f), + Math.round(mSurfaceSize.y * 2)); doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize( anyInt()); mContentRecorder.onConfigurationChanged( @@ -292,6 +319,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnTaskOrientationConfigurationChanged_resizesSurface() { + defaultInit(); mContentRecorder.setContentRecordingSession(mTaskSession); mContentRecorder.updateRecording(); @@ -314,6 +342,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnTaskBoundsConfigurationChanged_notifiesCallback() { + defaultInit(); mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); final int minWidth = 222; @@ -351,6 +380,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testTaskWindowingModeChanged_pip_stopsRecording() { + defaultInit(); // WHEN a recording is ongoing. mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); mContentRecorder.setContentRecordingSession(mTaskSession); @@ -368,6 +398,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testTaskWindowingModeChanged_fullscreen_startsRecording() { + defaultInit(); // WHEN a recording is ongoing. mTask.setWindowingMode(WINDOWING_MODE_PINNED); mContentRecorder.setContentRecordingSession(mTaskSession); @@ -384,6 +415,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testStartRecording_notifiesCallback_taskSession() { + defaultInit(); // WHEN a recording is ongoing. mContentRecorder.setContentRecordingSession(mTaskSession); mContentRecorder.updateRecording(); @@ -396,6 +428,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testStartRecording_notifiesCallback_displaySession() { + defaultInit(); // WHEN a recording is ongoing. mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); @@ -408,6 +441,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testStartRecording_taskInPIP_recordingNotStarted() { + defaultInit(); // GIVEN a task is in PIP. mContentRecorder.setContentRecordingSession(mTaskSession); mTask.setWindowingMode(WINDOWING_MODE_PINNED); @@ -421,6 +455,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testStartRecording_taskInSplit_recordingStarted() { + defaultInit(); // GIVEN a task is in PIP. mContentRecorder.setContentRecordingSession(mTaskSession); mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); @@ -434,6 +469,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testStartRecording_taskInFullscreen_recordingStarted() { + defaultInit(); // GIVEN a task is in PIP. mContentRecorder.setContentRecordingSession(mTaskSession); mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); @@ -447,6 +483,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnVisibleRequestedChanged_notifiesCallback() { + defaultInit(); // WHEN a recording is ongoing. mContentRecorder.setContentRecordingSession(mTaskSession); mContentRecorder.updateRecording(); @@ -471,6 +508,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() { + defaultInit(); // WHEN a recording is not ongoing. assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); @@ -493,6 +531,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testPauseRecording_pausesRecording() { + defaultInit(); mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); @@ -502,12 +541,14 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testPauseRecording_neverRecording() { + defaultInit(); mContentRecorder.pauseRecording(); assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); } @Test public void testStopRecording_stopsRecording() { + defaultInit(); mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); @@ -517,12 +558,14 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testStopRecording_neverRecording() { + defaultInit(); mContentRecorder.stopRecording(); assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); } @Test public void testRemoveTask_stopsRecording() { + defaultInit(); mContentRecorder.setContentRecordingSession(mTaskSession); mContentRecorder.updateRecording(); @@ -533,6 +576,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() { + defaultInit(); mContentRecorder.setContentRecordingSession(mTaskSession); mContentRecorder.updateRecording(); mContentRecorder.setContentRecordingSession(null); @@ -541,6 +585,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testUpdateMirroredSurface_capturedAreaResized() { + defaultInit(); mContentRecorder.setContentRecordingSession(mDisplaySession); mContentRecorder.updateRecording(); assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); @@ -548,9 +593,9 @@ public class ContentRecorderTests extends WindowTestsBase { // WHEN attempting to mirror on the virtual display, and the captured content is resized. float xScale = 0.7f; float yScale = 2f; - Rect displayAreaBounds = new Rect(0, 0, Math.round(sSurfaceSize.x * xScale), - Math.round(sSurfaceSize.y * yScale)); - mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, sSurfaceSize); + Rect displayAreaBounds = new Rect(0, 0, Math.round(mSurfaceSize.x * xScale), + Math.round(mSurfaceSize.y * yScale)); + mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, mSurfaceSize); assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); // THEN content in the captured DisplayArea is scaled to fit the surface size. @@ -558,15 +603,139 @@ public class ContentRecorderTests extends WindowTestsBase { 1.0f / yScale); // THEN captured content is positioned in the centre of the output surface. int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale); - int xInset = (sSurfaceSize.x - scaledWidth) / 2; + int xInset = (mSurfaceSize.x - scaledWidth) / 2; verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0); // THEN the resize callback is notified. verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized( displayAreaBounds.width(), displayAreaBounds.height()); } + @Test + public void testUpdateMirroredSurface_isotropicPixel() { + mHandleAnisotropicDisplayMirroring = false; + DisplayInfo displayInfo = createDefaultDisplayInfo(); + createContentRecorder(displayInfo); + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1, 0, 0, 1); + } + + @Test + public void testUpdateMirroredSurface_anisotropicPixel_compressY() { + mHandleAnisotropicDisplayMirroring = true; + DisplayInfo displayInfo = createDefaultDisplayInfo(); + DisplayInfo inputDisplayInfo = + mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo(); + displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi; + displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi; + createContentRecorder(displayInfo); + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + float xScale = 1f; + float yScale = 0.5f; + verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0, + yScale); + verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0, + Math.round(0.25 * mSurfaceSize.y)); + } + + @Test + public void testUpdateMirroredSurface_anisotropicPixel_compressX() { + mHandleAnisotropicDisplayMirroring = true; + DisplayInfo displayInfo = createDefaultDisplayInfo(); + DisplayInfo inputDisplayInfo = + mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo(); + displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi; + displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi; + createContentRecorder(displayInfo); + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + float xScale = 0.5f; + float yScale = 1f; + verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0, + yScale); + verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, + Math.round(0.25 * mSurfaceSize.x), 0); + } + + @Test + public void testUpdateMirroredSurface_anisotropicPixel_scaleOnX() { + mHandleAnisotropicDisplayMirroring = true; + int width = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(); + int height = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height(); + DisplayInfo displayInfo = createDisplayInfo(width, height); + DisplayInfo inputDisplayInfo = + mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo(); + displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi; + displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi; + createContentRecorder(displayInfo); + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + float xScale = 2f; + float yScale = 4f; + verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0, + yScale); + verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0, + inputDisplayInfo.logicalHeight); + } + + @Test + public void testUpdateMirroredSurface_anisotropicPixel_scaleOnY() { + mHandleAnisotropicDisplayMirroring = true; + int width = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(); + int height = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height(); + DisplayInfo displayInfo = createDisplayInfo(width, height); + DisplayInfo inputDisplayInfo = + mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo(); + displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi; + displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi; + createContentRecorder(displayInfo); + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + float xScale = 4f; + float yScale = 2f; + verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0, + yScale); + verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, + inputDisplayInfo.logicalWidth, 0); + } + + @Test + public void testUpdateMirroredSurface_anisotropicPixel_shrinkCanvas() { + mHandleAnisotropicDisplayMirroring = true; + int width = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width() / 2; + int height = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height() / 2; + DisplayInfo displayInfo = createDisplayInfo(width, height); + DisplayInfo inputDisplayInfo = + mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo(); + displayInfo.physicalXDpi = 2f * inputDisplayInfo.physicalXDpi; + displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi; + createContentRecorder(displayInfo); + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + float xScale = 0.5f; + float yScale = 0.25f; + verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0, + yScale); + verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0, + (mSurfaceSize.y - height / 2) / 2); + } + @Test public void testDisplayContentUpdatesRecording_withoutSurface() { + defaultInit(); // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to // mirror. setUpDefaultTaskDisplayAreaWindowToken(); @@ -585,6 +754,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testDisplayContentUpdatesRecording_withSurface() { + defaultInit(); // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to // mirror. setUpDefaultTaskDisplayAreaWindowToken(); @@ -602,12 +772,13 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testDisplayContentUpdatesRecording_displayMirroring() { + defaultInit(); // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to // mirror. setUpDefaultTaskDisplayAreaWindowToken(); // GIVEN SurfaceControl can successfully mirror the provided surface. - surfaceControlMirrors(sSurfaceSize); + surfaceControlMirrors(mSurfaceSize); // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct // call in the test. We need to spy on the DC before updateRecording is called or we can't // verify setDisplayMirroring is called diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java index 486486869d05580916d43ff412e92b74a89acbd5..3cb4a1d7b7ec7d302960dd3405b40ddeffea0379 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import android.os.Handler; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.DexmakerShareClassLoaderRule; import org.junit.Rule; @@ -27,11 +28,16 @@ import java.util.concurrent.Callable; /** The base class which provides the common rule for test classes under wm package. */ class SystemServiceTestsBase { - @Rule + @Rule(order = 0) public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); - @Rule - public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule(); + + @Rule(order = 1) + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Rule(order = 2) + public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule( + this::onBeforeSystemServicesCreated); @WindowTestRunner.MethodWrapperRule public final WindowManagerGlobalLockRule mLockRule = @@ -64,6 +70,11 @@ class SystemServiceTestsBase { return mLockRule.waitForLocked(callable); } + /** + * Called before system services are created + */ + protected void onBeforeSystemServicesCreated() {} + /** * Make the system booted, so that {@link ActivityStack#resumeTopActivityInnerLocked} can really * be executed to update activity state and configuration when resuming the current top. 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 7634d9f601d4da1526662f0564eef03dd4b7b4fe..2597465337ecff3535f838e02371c441fa9c86e2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -134,11 +134,20 @@ public class SystemServicesTestRule implements TestRule { private WindowState.PowerManagerWrapper mPowerManagerWrapper; private InputManagerService mImService; private InputChannel mInputChannel; + private Runnable mOnBeforeServicesCreated; /** * Spied {@link SurfaceControl.Transaction} class than can be used to verify calls. */ SurfaceControl.Transaction mTransaction; + public SystemServicesTestRule(Runnable onBeforeServicesCreated) { + mOnBeforeServicesCreated = onBeforeServicesCreated; + } + + public SystemServicesTestRule() { + this(/* onBeforeServicesCreated= */ null); + } + @Override public Statement apply(Statement base, Description description) { return new Statement() { @@ -168,6 +177,10 @@ public class SystemServicesTestRule implements TestRule { } private void setUp() { + if (mOnBeforeServicesCreated != null) { + mOnBeforeServicesCreated.run(); + } + // Use stubOnly() to reduce memory usage if it doesn't need verification. final MockSettings spyStubOnly = withSettings().stubOnly() .defaultAnswer(CALLS_REAL_METHODS); diff --git a/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java new file mode 100644 index 0000000000000000000000000000000000000000..31418d65b8b6ebc6e91bb1a91ddaba80b4f961bb --- /dev/null +++ b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.usage; + +import android.os.Looper; +import android.os.Trace; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.ServiceThread; + +/** + * Shared singleton default priority thread for usage stats message handling. + */ +public class UsageStatsHandlerThread extends ServiceThread { + private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000; + private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000; + + private static final Object sLock = new Object(); + + @GuardedBy("sLock") + private static UsageStatsHandlerThread sInstance; + + private UsageStatsHandlerThread() { + super("android.usagestats", android.os.Process.THREAD_PRIORITY_DEFAULT, + /* allowIo= */ true); + } + + @GuardedBy("sLock") + private static void ensureThreadLocked() { + if (sInstance != null) { + return; + } + + sInstance = new UsageStatsHandlerThread(); + sInstance.start(); + final Looper looper = sInstance.getLooper(); + looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER); + looper.setSlowLogThresholdMs( + SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS); + } + + /** + * Obtain a singleton instance of the UsageStatsHandlerThread. + */ + public static UsageStatsHandlerThread get() { + synchronized (sLock) { + ensureThreadLocked(); + return sInstance; + } + } +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 55b5d11d938aef4f91eeafabea9de9791fbc4235..e413663160b59eaf631478a00735ad93e74e82a8 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -202,6 +202,7 @@ public class UsageStatsService extends SystemService implements static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8; static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9; static final int MSG_UID_REMOVED = 10; + static final int MSG_USER_STARTED = 11; private final Object mLock = new Object(); private Handler mHandler; @@ -334,7 +335,7 @@ public class UsageStatsService extends SystemService implements mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); mPackageManager = getContext().getPackageManager(); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - mHandler = new H(BackgroundThread.get().getLooper()); + mHandler = getUsageEventProcessingHandler(); mIoHandler = new Handler(IoThread.get().getLooper(), mIoHandlerCallback); mAppStandby = mInjector.getAppStandbyController(getContext()); @@ -380,10 +381,12 @@ public class UsageStatsService extends SystemService implements IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_STARTED); - getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, - filter, null, /* Handler scheduler */ null); + getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter, + null, /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null); + getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL, - new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null); + new IntentFilter(ACTION_UID_REMOVED), null, + /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null); mRealTimeSnapshot = SystemClock.elapsedRealtime(); mSystemTimeSnapshot = System.currentTimeMillis(); @@ -471,6 +474,14 @@ public class UsageStatsService extends SystemService implements } } + private Handler getUsageEventProcessingHandler() { + if (Flags.useDedicatedHandlerThread()) { + return new H(UsageStatsHandlerThread.get().getLooper()); + } else { + return new H(BackgroundThread.get().getLooper()); + } + } + private void onUserUnlocked(int userId) { // fetch the installed packages outside the lock so it doesn't block package manager. final HashMap<String, Long> installedPackages = getInstalledPackages(userId); @@ -618,7 +629,7 @@ public class UsageStatsService extends SystemService implements } } else if (Intent.ACTION_USER_STARTED.equals(action)) { if (userId >= 0) { - mAppStandby.postCheckIdleStates(userId); + mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget(); } } } @@ -1554,8 +1565,7 @@ public class UsageStatsService extends SystemService implements synchronized (mLaunchTimeAlarmQueues) { LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId); if (alarmQueue == null) { - alarmQueue = new LaunchTimeAlarmQueue( - userId, getContext(), BackgroundThread.get().getLooper()); + alarmQueue = new LaunchTimeAlarmQueue(userId, getContext(), mHandler.getLooper()); mLaunchTimeAlarmQueues.put(userId, alarmQueue); } @@ -2040,6 +2050,9 @@ public class UsageStatsService extends SystemService implements case MSG_UID_REMOVED: mResponseStatsTracker.onUidRemoved(msg.arg1); break; + case MSG_USER_STARTED: + mAppStandby.postCheckIdleStates(msg.arg1); + break; case MSG_PACKAGE_REMOVED: onPackageRemoved(msg.arg1, (String) msg.obj); break; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 7a0bf9038f7c5e655ae94473c5c9774137a17ab3..540cecfbe7c931ef822ad976135b1326be075607 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1055,6 +1055,14 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; + /** + * When {@code true}, this carrier will preferentially dial normal routed emergency calls over + * an in-service SIM if one is available. + * @hide + */ + public static final String KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL = + "prefer_in_service_sim_for_normal_routed_emergency_calls_bool"; + /** * When {@code true}, the determination of whether to place a call as an emergency call will be * based on the known {@link android.telephony.emergency.EmergencyNumber}s for the SIM on which @@ -9874,6 +9882,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true); + sDefaults.putBoolean(KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL, + false); sDefaults.putBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false); sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, ""); sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, ""); diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index e9af486219f7372cb2e46205af92fee3d517f53f..11cbcb1c149dcc0a9cdfde997dc65bb5832e3952 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -15,6 +15,7 @@ */ package android.telephony.data; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,6 +36,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -121,6 +123,9 @@ public class ApnSetting implements Parcelable { public static final int TYPE_BIP = ApnTypes.BIP; /** APN type for ENTERPRISE. */ public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE; + /** APN type for RCS (Rich Communication Services). */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final int TYPE_RCS = ApnTypes.RCS; /** @hide */ @IntDef(flag = true, prefix = {"TYPE_"}, value = { @@ -139,6 +144,7 @@ public class ApnSetting implements Parcelable { TYPE_BIP, TYPE_VSIM, TYPE_ENTERPRISE, + TYPE_RCS }) @Retention(RetentionPolicy.SOURCE) public @interface ApnType { @@ -356,6 +362,17 @@ public class ApnSetting implements Parcelable { @SystemApi public static final String TYPE_ENTERPRISE_STRING = "enterprise"; + /** + * APN type for RCS (Rich Communication Services) + * + * Note: String representations of APN types are intended for system apps to communicate with + * modem components or carriers. Non-system apps should use the integer variants instead. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @SystemApi + public static final String TYPE_RCS_STRING = "rcs"; + /** @hide */ @IntDef(prefix = { "AUTH_TYPE_" }, value = { @@ -424,6 +441,26 @@ public class ApnSetting implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface MvnoType {} + /** + * Indicating this APN can be used when the device is using terrestrial cellular networks. + * @hide + */ + public static final int INFRASTRUCTURE_CELLULAR = 1 << 0; + + /** + * Indicating this APN can be used when the device is attached to satellites. + * @hide + */ + public static final int INFRASTRUCTURE_SATELLITE = 1 << 1; + + /** @hide */ + @IntDef(flag = true, prefix = { "INFRASTRUCTURE_" }, value = { + INFRASTRUCTURE_CELLULAR, + INFRASTRUCTURE_SATELLITE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface InfrastructureBitmask {} + private static final Map<String, Integer> APN_TYPE_STRING_MAP; private static final Map<Integer, String> APN_TYPE_INT_MAP; private static final Map<String, Integer> PROTOCOL_STRING_MAP; @@ -449,6 +486,7 @@ public class ApnSetting implements Parcelable { APN_TYPE_STRING_MAP.put(TYPE_ENTERPRISE_STRING, TYPE_ENTERPRISE); APN_TYPE_STRING_MAP.put(TYPE_VSIM_STRING, TYPE_VSIM); APN_TYPE_STRING_MAP.put(TYPE_BIP_STRING, TYPE_BIP); + APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS); APN_TYPE_INT_MAP = new ArrayMap<>(); APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING); @@ -466,6 +504,7 @@ public class ApnSetting implements Parcelable { APN_TYPE_INT_MAP.put(TYPE_ENTERPRISE, TYPE_ENTERPRISE_STRING); APN_TYPE_INT_MAP.put(TYPE_VSIM, TYPE_VSIM_STRING); APN_TYPE_INT_MAP.put(TYPE_BIP, TYPE_BIP_STRING); + APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING); PROTOCOL_STRING_MAP = new ArrayMap<>(); PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP); @@ -528,6 +567,7 @@ public class ApnSetting implements Parcelable { private final int mCarrierId; private final int mSkip464Xlat; private final boolean mAlwaysOn; + private final @InfrastructureBitmask int mInfrastructureBitmask; /** * Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought @@ -916,6 +956,29 @@ public class ApnSetting implements Parcelable { return mAlwaysOn; } + /** + * Check if this APN can be used when the device is using certain infrastructure(s). + * + * @param infrastructures The infrastructure(s) the device is using. + * + * @return {@code true} if this APN can be used. + * @hide + */ + public boolean isForInfrastructure(@InfrastructureBitmask int infrastructures) { + return (mInfrastructureBitmask & infrastructures) != 0; + } + + /** + * @return The infrastructure bitmask of which the APN can be used on. For example, some APNs + * can only be used when the device is on cellular, on satellite, or both. + * + * @hide + */ + @InfrastructureBitmask + public int getInfrastructureBitmask() { + return mInfrastructureBitmask; + } + private ApnSetting(Builder builder) { this.mEntryName = builder.mEntryName; this.mApnName = builder.mApnName; @@ -952,6 +1015,7 @@ public class ApnSetting implements Parcelable { this.mCarrierId = builder.mCarrierId; this.mSkip464Xlat = builder.mSkip464Xlat; this.mAlwaysOn = builder.mAlwaysOn; + this.mInfrastructureBitmask = builder.mInfrastructureBitmask; } /** @@ -1031,6 +1095,8 @@ public class ApnSetting implements Parcelable { cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID))) .setSkip464Xlat(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT))) .setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1) + .setInfrastructureBitmask(cursor.getInt(cursor.getColumnIndexOrThrow( + Telephony.Carriers.INFRASTRUCTURE_BITMASK))) .buildWithoutCheck(); } @@ -1070,6 +1136,7 @@ public class ApnSetting implements Parcelable { .setCarrierId(apn.mCarrierId) .setSkip464Xlat(apn.mSkip464Xlat) .setAlwaysOn(apn.mAlwaysOn) + .setInfrastructureBitmask(apn.mInfrastructureBitmask) .buildWithoutCheck(); } @@ -1115,6 +1182,7 @@ public class ApnSetting implements Parcelable { sb.append(", ").append(mCarrierId); sb.append(", ").append(mSkip464Xlat); sb.append(", ").append(mAlwaysOn); + sb.append(", ").append(mInfrastructureBitmask); sb.append(", ").append(Objects.hash(mUser, mPassword)); return sb.toString(); } @@ -1179,7 +1247,7 @@ public class ApnSetting implements Parcelable { mProtocol, mRoamingProtocol, mMtuV4, mMtuV6, mCarrierEnabled, mNetworkTypeBitmask, mLingeringNetworkTypeBitmask, mProfileId, mPersistent, mMaxConns, mWaitTime, mMaxConnsTime, mMvnoType, mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat, - mAlwaysOn); + mAlwaysOn, mInfrastructureBitmask); } @Override @@ -1191,36 +1259,37 @@ public class ApnSetting implements Parcelable { ApnSetting other = (ApnSetting) o; return mEntryName.equals(other.mEntryName) - && Objects.equals(mId, other.mId) + && mId == other.mId && Objects.equals(mOperatorNumeric, other.mOperatorNumeric) && Objects.equals(mApnName, other.mApnName) && Objects.equals(mProxyAddress, other.mProxyAddress) && Objects.equals(mMmsc, other.mMmsc) && Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress) - && Objects.equals(mMmsProxyPort, other.mMmsProxyPort) - && Objects.equals(mProxyPort, other.mProxyPort) + && mMmsProxyPort == other.mMmsProxyPort + && mProxyPort == other.mProxyPort && Objects.equals(mUser, other.mUser) && Objects.equals(mPassword, other.mPassword) - && Objects.equals(mAuthType, other.mAuthType) - && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask) - && Objects.equals(mProtocol, other.mProtocol) - && Objects.equals(mRoamingProtocol, other.mRoamingProtocol) - && Objects.equals(mCarrierEnabled, other.mCarrierEnabled) - && Objects.equals(mProfileId, other.mProfileId) - && Objects.equals(mPersistent, other.mPersistent) - && Objects.equals(mMaxConns, other.mMaxConns) - && Objects.equals(mWaitTime, other.mWaitTime) - && Objects.equals(mMaxConnsTime, other.mMaxConnsTime) - && Objects.equals(mMtuV4, other.mMtuV4) - && Objects.equals(mMtuV6, other.mMtuV6) - && Objects.equals(mMvnoType, other.mMvnoType) + && mAuthType == other.mAuthType + && mApnTypeBitmask == other.mApnTypeBitmask + && mProtocol == other.mProtocol + && mRoamingProtocol == other.mRoamingProtocol + && mCarrierEnabled == other.mCarrierEnabled + && mProfileId == other.mProfileId + && mPersistent == other.mPersistent + && mMaxConns == other.mMaxConns + && mWaitTime == other.mWaitTime + && mMaxConnsTime == other.mMaxConnsTime + && mMtuV4 == other.mMtuV4 + && mMtuV6 == other.mMtuV6 + && mMvnoType == other.mMvnoType && Objects.equals(mMvnoMatchData, other.mMvnoMatchData) - && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask) - && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask) - && Objects.equals(mApnSetId, other.mApnSetId) - && Objects.equals(mCarrierId, other.mCarrierId) - && Objects.equals(mSkip464Xlat, other.mSkip464Xlat) - && Objects.equals(mAlwaysOn, other.mAlwaysOn); + && mNetworkTypeBitmask == other.mNetworkTypeBitmask + && mLingeringNetworkTypeBitmask == other.mLingeringNetworkTypeBitmask + && mApnSetId == other.mApnSetId + && mCarrierId == other.mCarrierId + && mSkip464Xlat == other.mSkip464Xlat + && mAlwaysOn == other.mAlwaysOn + && mInfrastructureBitmask == other.mInfrastructureBitmask; } /** @@ -1270,7 +1339,8 @@ public class ApnSetting implements Parcelable { && Objects.equals(mApnSetId, other.mApnSetId) && Objects.equals(mCarrierId, other.mCarrierId) && Objects.equals(mSkip464Xlat, other.mSkip464Xlat) - && Objects.equals(mAlwaysOn, other.mAlwaysOn); + && Objects.equals(mAlwaysOn, other.mAlwaysOn) + && Objects.equals(mInfrastructureBitmask, other.mInfrastructureBitmask); } /** @@ -1307,7 +1377,8 @@ public class ApnSetting implements Parcelable { && Objects.equals(this.mApnSetId, other.mApnSetId) && Objects.equals(this.mCarrierId, other.mCarrierId) && Objects.equals(this.mSkip464Xlat, other.mSkip464Xlat) - && Objects.equals(this.mAlwaysOn, other.mAlwaysOn); + && Objects.equals(this.mAlwaysOn, other.mAlwaysOn) + && Objects.equals(this.mInfrastructureBitmask, other.mInfrastructureBitmask); } // Equal or one is null. @@ -1379,6 +1450,7 @@ public class ApnSetting implements Parcelable { apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId); apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat); apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn); + apnValue.put(Telephony.Carriers.INFRASTRUCTURE_BITMASK, mInfrastructureBitmask); return apnValue; } @@ -1651,6 +1723,7 @@ public class ApnSetting implements Parcelable { dest.writeInt(mCarrierId); dest.writeInt(mSkip464Xlat); dest.writeBoolean(mAlwaysOn); + dest.writeInt(mInfrastructureBitmask); } private static ApnSetting readFromParcel(Parcel in) { @@ -1686,6 +1759,7 @@ public class ApnSetting implements Parcelable { .setCarrierId(in.readInt()) .setSkip464Xlat(in.readInt()) .setAlwaysOn(in.readBoolean()) + .setInfrastructureBitmask(in.readInt()) .buildWithoutCheck(); } @@ -1767,6 +1841,7 @@ public class ApnSetting implements Parcelable { private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT; private boolean mAlwaysOn; + private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR; /** * Default constructor for Builder. @@ -2188,6 +2263,22 @@ public class ApnSetting implements Parcelable { return this; } + /** + * Set the infrastructure bitmask. + * + * @param infrastructureBitmask The infrastructure bitmask of which the APN can be used on. + * For example, some APNs can only be used when the device is on cellular, on satellite, or + * both. + * + * @return The builder. + * @hide + */ + @NonNull + public Builder setInfrastructureBitmask(@InfrastructureBitmask int infrastructureBitmask) { + this.mInfrastructureBitmask = infrastructureBitmask; + return this; + } + /** * Builds {@link ApnSetting} from this builder. * @@ -2198,7 +2289,7 @@ public class ApnSetting implements Parcelable { public ApnSetting build() { if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI | TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX - | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE)) == 0 + | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0 || TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) { return null; } diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java index f346b9208bcdd855c0a62022068b2837ab658f4a..88a32d15a7a60bd749980fa45981131971195943 100644 --- a/telephony/java/android/telephony/data/DataProfile.java +++ b/telephony/java/android/telephony/data/DataProfile.java @@ -431,6 +431,8 @@ public final class DataProfile implements Parcelable { return ApnSetting.TYPE_VSIM; case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE: return ApnSetting.TYPE_ENTERPRISE; + case NetworkCapabilities.NET_CAPABILITY_RCS: + return ApnSetting.TYPE_RCS; default: return ApnSetting.TYPE_NONE; } diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 82aa85d55e4c6ca845097257cb67777c4a7179bb..f4f2be642e814044de986ac735712eaa84fb063f 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -53,7 +53,17 @@ filegroup { } filegroup { - name: "FlickerTestsAppLaunch-src", + name: "FlickerTestsAppLaunchCommon-src", + srcs: ["src/**/launch/common/*.kt"], +} + +filegroup { + name: "FlickerTestsAppLaunch1-src", + srcs: ["src/**/launch/OpenApp*.kt"], +} + +filegroup { + name: "FlickerTestsAppLaunch2-src", srcs: ["src/**/launch/*.kt"], } @@ -119,7 +129,8 @@ android_test { exclude_srcs: [ ":FlickerTestsAppClose-src", ":FlickerTestsIme-src", - ":FlickerTestsAppLaunch-src", + ":FlickerTestsAppLaunch1-src", + ":FlickerTestsAppLaunch2-src", ":FlickerTestsQuickswitch-src", ":FlickerTestsRotation-src", ":FlickerTestsNotification-src", @@ -162,10 +173,44 @@ android_test { instrumentation_target_package: "com.android.server.wm.flicker.launch", srcs: [ ":FlickerTestsBase-src", - ":FlickerTestsAppLaunch-src", + ":FlickerTestsAppLaunchCommon-src", + ":FlickerTestsAppLaunch2-src", + ], + exclude_srcs: [ + ":FlickerTestsActivityEmbedding-src", + ], +} + +android_test { + name: "FlickerTestsAppLaunch1", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"], + package_name: "com.android.server.wm.flicker.launch", + instrumentation_target_package: "com.android.server.wm.flicker.launch", + srcs: [ + ":FlickerTestsBase-src", + ":FlickerTestsAppLaunchCommon-src", + ":FlickerTestsAppLaunch1-src", + ], + exclude_srcs: [ + ":FlickerTestsActivityEmbedding-src", + ], +} + +android_test { + name: "FlickerTestsAppLaunch2", + defaults: ["FlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"], + package_name: "com.android.server.wm.flicker.launch", + instrumentation_target_package: "com.android.server.wm.flicker.launch", + srcs: [ + ":FlickerTestsBase-src", + ":FlickerTestsAppLaunchCommon-src", + ":FlickerTestsAppLaunch2-src", ], exclude_srcs: [ ":FlickerTestsActivityEmbedding-src", + ":FlickerTestsAppLaunch1-src", ], } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt index 48d504141116f35f0058fb3f7a3bcddecb8e563b..f788efae4c59441f7a0bc7bcaa3d1e81eb24056a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt @@ -16,13 +16,11 @@ package com.android.server.wm.flicker.launch -import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory -import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import androidx.test.filters.FlakyTest +import com.android.server.wm.flicker.launch.common.OpenAppFromIconTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -54,28 +52,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) : - OpenAppFromLauncherTransition(flicker) { - /** {@inheritDoc} */ - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - setup { - if (flicker.scenario.isTablet) { - tapl.setExpectedRotation(flicker.scenario.startRotation.value) - } else { - tapl.setExpectedRotation(Rotation.ROTATION_0.value) - } - RemoveAllTasksButHomeRule.removeAllTasksButHome() - } - transitions { - tapl - .goHome() - .switchToAllApps() - .getAppIcon(testApp.appName) - .launch(testApp.packageName) - } - teardown { testApp.exit(wmHelper) } - } + OpenAppFromIconTransition(flicker) { @FlakyTest(bugId = 240916028) @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt index f575fcc0e94586f0a8d878be00548788c4dedd99..d86dc50b3a5cb2d9f8ce5f3e844767f4ffa681ec 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt @@ -21,6 +21,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt index 93d0520d87bc3c0d1cf3c15a4a0211efd88da7c6..be07053f3039a109ea82766207727f439b81dc4a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt @@ -25,6 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt index 78b58f4da065b78dab18a7a2fca6c601969028cd..f66eff9b4cb05ff5da297ae46adfe2f5c6a63b56 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt @@ -24,6 +24,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt index 3f931c48ddbde50546bfdf8aee9b90349d93d940..65214764f0ac552e1095648d2b0438f7894491a4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt @@ -27,6 +27,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.launch.common.OpenAppFromLockscreenTransition import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Ignore diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index b85362a075386857cccbcd0f9add9c85065eb17e..4d31c28b1231a0a15cae27388168f11845aae519 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -25,6 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt index 1bdb6e717b12598fb2d2c1cbf1e12dcf66d11be1..42e34b313d8ef8f165809b960a7602679521985a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt @@ -30,6 +30,7 @@ import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import android.view.KeyEvent import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition import org.junit.FixMethodOrder import org.junit.Ignore import org.junit.Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt deleted file mode 100644 index 3d9c0679945df7058ff6bd2c7669939634bd4db1..0000000000000000000000000000000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt +++ /dev/null @@ -1,87 +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.server.wm.flicker.launch - -import android.platform.test.annotations.Presubmit -import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.LegacyFlickerTest -import android.tools.device.flicker.legacy.LegacyFlickerTestFactory -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test cold launching an app from launcher - * - * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition` - * - * Actions: - * ``` - * Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen - * by clicking it's icon on all apps, and wait for transfer splash screen complete - * ``` - * - * Notes: - * ``` - * 1. Some default assertions (e.g., nav bar, status bar and screen covered) - * are inherited [OpenAppTransition] - * 2. Verify no flickering when transfer splash screen to app window. - * ``` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) : - OpenAppFromIconColdTest(flicker) { - override val testApp = TransferSplashscreenAppHelper(instrumentation) - - /** - * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the - * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains - * visible until the end - */ - @Presubmit - @Test - fun appWindowAfterSplash() { - flicker.assertWm { - this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) - .then() - .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN) - .then() - .isAppWindowOnTop(testApp) - .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN) - } - } - - companion object { - /** - * Creates the test configurations. - * - * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and - * navigation modes. - */ - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams() = LegacyFlickerTestFactory.nonRotationTests() - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt new file mode 100644 index 0000000000000000000000000000000000000000..c8547015b92bc7315d46450975b89ba4ac291257 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.launch.common + +import android.tools.common.Rotation +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule + +abstract class OpenAppFromIconTransition(flicker: LegacyFlickerTest) : + OpenAppFromLauncherTransition(flicker) { + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + if (flicker.scenario.isTablet) { + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + } else { + tapl.setExpectedRotation(Rotation.ROTATION_0.value) + } + RemoveAllTasksButHomeRule.removeAllTasksButHome() + } + transitions { + tapl + .goHome() + .switchToAllApps() + .getAppIcon(testApp.appName) + .launch(testApp.packageName) + } + teardown { testApp.exit(wmHelper) } + } +} \ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt similarity index 96% rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt index 4fc9bcb309c5f71d2cc06d691dd15d3fbb908ddb..9d7096ea0e73c1183ea10b9974b46ebcfba6c5c6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.flicker.launch +package com.android.server.wm.flicker.launch.common import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt similarity index 97% rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt index cc501e6c43081ac44d1a649d0b8a278f7c86fbc1..7b088431b0bb3c0bd27fd499e2a38857809fc80f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.flicker.launch +package com.android.server.wm.flicker.launch.common import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt similarity index 98% rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt index bb11be5bb520d32f508428f361860492662c3e8c..989619e08d98681c0b8b563c4416d6e9f001f759 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.wm.flicker.launch +package com.android.server.wm.flicker.launch.common import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e9620bb13c5a329bb2eec0a6a6f25a602b52fd9 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.launch.common + +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launching an app from launcher + * + * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition` + * + * Actions: + * ``` + * Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen + * by clicking it's icon on all apps, and wait for transfer splash screen complete + * ``` + * + * Notes: + * ``` + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [OpenAppTransition] + * 2. Verify no flickering when transfer splash screen to app window. + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) : + OpenAppFromIconTransition(flicker) { + override val testApp = TransferSplashscreenAppHelper(instrumentation) + + /** + * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the + * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains + * visible until the end + */ + @Presubmit + @Test + fun appWindowAfterSplash() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN) + .then() + .isAppWindowOnTop(testApp) + .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN) + } + } + + @FlakyTest(bugId = 240916028) + @Test + override fun focusChanges() { + super.focusChanges() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowReplacesLauncherAsTopWindow() { + super.appWindowReplacesLauncherAsTopWindow() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowAsTopWindowAtEnd() { + super.appWindowAsTopWindowAtEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowBecomesTopWindow() { + super.appWindowBecomesTopWindow() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowBecomesVisible() { + super.appWindowBecomesVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowIsTopWindowAtEnd() { + super.appWindowIsTopWindowAtEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appLayerBecomesVisible() { + super.appLayerBecomesVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appLayerReplacesLauncher() { + super.appLayerReplacesLauncher() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun cujCompleted() { + super.cujCompleted() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun entireScreenCovered() { + super.entireScreenCovered() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() { + super.navBarLayerIsVisibleAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun navBarLayerPositionAtStartAndEnd() { + super.navBarLayerPositionAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun navBarWindowIsAlwaysVisible() { + super.navBarWindowIsAlwaysVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun navBarWindowIsVisibleAtStartAndEnd() { + super.navBarWindowIsVisibleAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() { + super.statusBarLayerIsVisibleAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun statusBarLayerPositionAtStartAndEnd() { + super.statusBarLayerPositionAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun statusBarWindowIsAlwaysVisible() { + super.statusBarWindowIsAlwaysVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() { + super.taskBarLayerIsVisibleAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun taskBarWindowIsAlwaysVisible() { + super.taskBarWindowIsAlwaysVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + } +} diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml index 49cbde0d4611a29c2f42b7efd1272f4acf3e7b92..d2537f6410e8c5661c75bc13712746a7d10fad91 100644 --- a/tests/FsVerityTest/AndroidTest.xml +++ b/tests/FsVerityTest/AndroidTest.xml @@ -24,7 +24,7 @@ <!-- This test requires root to write against block device. --> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> - <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="test-file-name" value="FsVerityTestApp.apk"/> <option name="cleanup-apks" value="true"/> </target_preparer> diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java index 01f15916f244fd5193fadc4507bd1bebcc9c5014..3d5a0f7a239f4cbcae5ee388594e3f19540d05cc 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java @@ -16,6 +16,7 @@ package android.net.wifi.sharedconnectivity.app; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -169,6 +170,7 @@ public final class NetworkProviderInfo implements Parcelable { * @return Returns the Builder object. */ @NonNull + @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public Builder setBatteryCharging(boolean isBatteryCharging) { mIsBatteryCharging = isBatteryCharging; return this; @@ -283,6 +285,7 @@ public final class NetworkProviderInfo implements Parcelable { * * @return Returns true if the battery of the remote device is charging. */ + @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging() { return mIsBatteryCharging; } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index 71ac94ba2ee433dba781d8f744f3bb372c9fac4a..b0f68f7870eee705841b292a3ff11c641aaf656b 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -17,6 +17,7 @@ package android.net.wifi.sharedconnectivity.app; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -296,6 +297,7 @@ public class SharedConnectivityManager { */ @TestApi @NonNull + @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") public BroadcastReceiver getBroadcastReceiver() { return mBroadcastReceiver; }