diff options
377 files changed, 10576 insertions, 5504 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index b924ac802812..7ccbffb83563 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -18,6 +18,7 @@ aconfig_srcjars = [ ":android.content.pm.flags-aconfig-java{.generated_srcjars}", ":android.content.res.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", + ":android.location.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", @@ -142,6 +143,21 @@ cc_aconfig_library { aconfig_declarations: "com.android.text.flags-aconfig", } +// Location +aconfig_declarations { + name: "android.location.flags-aconfig", + package: "android.location.flags", + srcs: [ + "location/java/android/location/flags/*.aconfig", + ], +} + +java_aconfig_library { + name: "android.location.flags-aconfig-java", + aconfig_declarations: "android.location.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // NFC aconfig_declarations { name: "android.nfc.flags-aconfig", @@ -152,6 +168,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"], } @@ -289,6 +310,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", diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index 45bb16184069..e7adf203334e 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 a143d6fd4250..bbe1485ddcd4 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 d48d84ba6980..1fdf906fd4ea 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 e1621008cc33..e086bfe5cbb2 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 7e41660cf1a2..7e78185b2659 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/cmds/svc/src/com/android/commands/svc/NfcCommand.java b/cmds/svc/src/com/android/commands/svc/NfcCommand.java index 020ca3387555..870e0078450f 100644 --- a/cmds/svc/src/com/android/commands/svc/NfcCommand.java +++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java @@ -16,10 +16,10 @@ package com.android.commands.svc; +import android.app.ActivityThread; import android.content.Context; -import android.nfc.INfcAdapter; -import android.os.RemoteException; -import android.os.ServiceManager; +import android.nfc.NfcAdapter; +import android.nfc.NfcManager; public class NfcCommand extends Svc.Command { @@ -42,27 +42,24 @@ public class NfcCommand extends Svc.Command { @Override public void run(String[] args) { - INfcAdapter adapter = INfcAdapter.Stub.asInterface( - ServiceManager.getService(Context.NFC_SERVICE)); - + Context context = ActivityThread.systemMain().getSystemContext(); + NfcManager nfcManager = context.getSystemService(NfcManager.class); + if (nfcManager == null) { + System.err.println("Got a null NfcManager, is the system running?"); + return; + } + NfcAdapter adapter = nfcManager.getDefaultAdapter(); if (adapter == null) { System.err.println("Got a null NfcAdapter, is the system running?"); return; } - - try { - if (args.length == 2 && "enable".equals(args[1])) { - adapter.enable(); - return; - } else if (args.length == 2 && "disable".equals(args[1])) { - adapter.disable(true); - return; - } - } catch (RemoteException e) { - System.err.println("NFC operation failed: " + e); + if (args.length == 2 && "enable".equals(args[1])) { + adapter.enable(); + return; + } else if (args.length == 2 && "disable".equals(args[1])) { + adapter.disable(true); return; } - System.err.println(longHelp()); } diff --git a/core/api/current.txt b/core/api/current.txt index 8c2ba082d821..b73de4fba062 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9533,6 +9533,7 @@ package android.companion { method public int getId(); method public int getSystemDataSyncFlags(); 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; } @@ -17576,7 +17577,7 @@ package android.graphics.fonts { ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font); method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font); method @NonNull public android.graphics.fonts.FontFamily build(); - method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily(); + method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily(); } public final class FontStyle { @@ -17764,18 +17765,18 @@ package android.graphics.text { method public float getAdvance(); method public float getAscent(); method public float getDescent(); - method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeBold(@IntRange(from=0) int); - method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeItalic(@IntRange(from=0) int); + method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeBold(@IntRange(from=0) int); + method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeItalic(@IntRange(from=0) int); method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int); method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int); method public float getGlyphX(@IntRange(from=0) int); method public float getGlyphY(@IntRange(from=0) int); - method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getItalicOverride(@IntRange(from=0) int); + method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getItalicOverride(@IntRange(from=0) int); method public float getOffsetX(); method public float getOffsetY(); - method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getWeightOverride(@IntRange(from=0) int); + method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getWeightOverride(@IntRange(from=0) int); method @IntRange(from=0) public int glyphCount(); - field @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public static final float NO_OVERRIDE = 1.4E-45f; + field @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public static final float NO_OVERRIDE = 1.4E-45f; } public class TextRunShaper { @@ -18551,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(); } } @@ -23779,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 @@ -45476,7 +45479,6 @@ package android.telephony { field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2 field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9 field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6 - field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10 field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2 field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3 field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4 @@ -45706,6 +45708,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 70f5e2f231c9..ac61107dd752 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 { @@ -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/java/Android.bp b/core/java/Android.bp index 0293f66061c1..ddb221f422d9 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/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index a3b82e935673..d7d654672abc 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. * @@ -253,13 +247,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. * @hide diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index e7a5b72eafc5..d660078a9ae7 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) { @@ -2988,25 +2950,6 @@ public class WallpaperManager { } /** - * 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? * * Only the OS itself may use this method. diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java index 113a6dd0ca48..7320cea16451 100644 --- a/core/java/android/app/admin/SystemUpdatePolicy.java +++ b/core/java/android/app/admin/SystemUpdatePolicy.java @@ -51,7 +51,7 @@ import java.util.stream.Collectors; /** * Determines when over-the-air system updates are installed on a device. Only a device policy * controller (DPC) running in device owner mode or in profile owner mode for an organization-owned - * device can set an update policy for the device—by calling the {@code DevicePolicyManager} method + * device can set an update policy for the device by calling the {@code DevicePolicyManager} method * {@link DevicePolicyManager#setSystemUpdatePolicy setSystemUpdatePolicy()}. An update * policy affects the pending system update (if there is one) and any future updates for the device. * diff --git a/core/java/android/app/admin/flags/FlagUtils.java b/core/java/android/app/admin/flags/FlagUtils.java deleted file mode 100644 index 7c3c3d5260b4..000000000000 --- a/core/java/android/app/admin/flags/FlagUtils.java +++ /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 android.app.admin.flags; - -import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; -import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled; -import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; - -import android.os.Binder; - -/** - * - * @hide - */ -public final class FlagUtils { - private FlagUtils() {} - - public static boolean isPolicyEngineMigrationV2Enabled() { - return Binder.withCleanCallingIdentity(() -> { - return policyEngineMigrationV2Enabled(); - }); - } - - public static boolean isDevicePolicySizeTrackingEnabled() { - return Binder.withCleanCallingIdentity(() -> { - return devicePolicySizeTrackingEnabled(); - }); - } - - public static boolean isOnboardingBugreportV2Enabled() { - return Binder.withCleanCallingIdentity(() -> { - return onboardingBugreportV2Enabled(); - }); - } -} diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index c145c0255b0a..f99615fd2ef4 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/contentsuggestions/OWNERS b/core/java/android/app/contentsuggestions/OWNERS index cf54c2a6fcbc..5f8de77c9dda 100644 --- a/core/java/android/app/contentsuggestions/OWNERS +++ b/core/java/android/app/contentsuggestions/OWNERS @@ -1,7 +1,4 @@ # Bug component: 643919 -augale@google.com -joannechung@google.com -markpun@google.com -lpeter@google.com -tymtsai@google.com +hackz@google.com +volnov@google.com diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index f401a7607364..4f1c65b1e395 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 8b09bdf2ab17..161fa799f2f5 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -206,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; } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index bb9cc0bef671..7b6bad31539a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4292,6 +4292,14 @@ public class Intent implements Parcelable, Cloneable { "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/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 563ed7dd6e7a..e9f419e9a8ce 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.IntentSender; import android.content.LocusId; +import android.content.pm.LauncherUserInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IOnAppsChangedListener; import android.content.pm.LauncherActivityInfoInternal; @@ -62,6 +63,7 @@ interface ILauncherApps { in Bundle opts, in UserHandle user); PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component, in UserHandle user); + LauncherUserInfo getLauncherUserInfo(in UserHandle user); void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage, String callingFeatureId, in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index dbaa4c93d71c..0cd4358b2c91 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -20,6 +20,7 @@ import static android.Manifest.permission; import static android.Manifest.permission.READ_FRAME_BUFFER; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -55,6 +56,7 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Flags; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -776,6 +778,28 @@ public class LauncherApps { } /** + * Returns information related to a user which is useful for displaying UI elements + * to distinguish it from other users (eg, badges). Only system launchers should + * call this API. + * + * @param userHandle user handle of the user for which LauncherUserInfo is requested + * @return the LauncherUserInfo object related to the user specified. + * @hide + */ + @Nullable + @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) { + if (DEBUG) { + Log.i(TAG, "getLauncherUserInfo " + userHandle); + } + try { + return mService.getLauncherUserInfo(userHandle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it * returns null. * diff --git a/core/java/android/content/pm/LauncherUserInfo.aidl b/core/java/android/content/pm/LauncherUserInfo.aidl new file mode 100644 index 000000000000..f875f1ef0c6d --- /dev/null +++ b/core/java/android/content/pm/LauncherUserInfo.aidl @@ -0,0 +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 android.content.pm; + +parcelable LauncherUserInfo; diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java new file mode 100644 index 000000000000..214c3e48db71 --- /dev/null +++ b/core/java/android/content/pm/LauncherUserInfo.java @@ -0,0 +1,126 @@ +/* + * 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.content.pm; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.os.Flags; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; +import android.os.UserManager; + +/** + * The LauncherUserInfo object holds information about an Android user that is required to display + * the Launcher related UI elements specific to the user (like badges). + * + * @hide + */ +@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) +public final class LauncherUserInfo implements Parcelable { + + private final String mUserType; + + // Serial number for the user, should be same as in the {@link UserInfo} object. + private final int mUserSerialNumber; + + /** + * Returns type of the user as defined in {@link UserManager}. e.g., + * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE} + * TODO(b/303812736): Make the return type public and update javadoc here once the linked bug + * is resolved. + * + * @return the userType for the user whose LauncherUserInfo this is + * @hide + */ + @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + @NonNull + public String getUserType() { + return mUserType; + } + + /** + * Returns serial number of user as returned by + * {@link UserManager#getSerialNumberForUser(UserHandle)} + * + * @return the serial number associated with the user + * @hide + */ + @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + public int getUserSerialNumber() { + return mUserSerialNumber; + } + + private LauncherUserInfo(@NonNull Parcel in) { + mUserType = in.readString16NoHelper(); + mUserSerialNumber = in.readInt(); + } + + @Override + @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString16NoHelper(mUserType); + dest.writeInt(mUserSerialNumber); + } + + @Override + @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + public int describeContents() { + return 0; + } + + @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + public static final @android.annotation.NonNull Creator<LauncherUserInfo> CREATOR = + new Creator<LauncherUserInfo>() { + @Override + public LauncherUserInfo createFromParcel(Parcel in) { + return new LauncherUserInfo(in); + } + + @Override + public LauncherUserInfo[] newArray(int size) { + return new LauncherUserInfo[size]; + } + }; + + /** + * @hide + */ + public static final class Builder { + private final String mUserType; + + private final int mUserSerialNumber; + + public Builder(@NonNull String userType, int userSerialNumber) { + this.mUserType = userType; + this.mUserSerialNumber = userSerialNumber; + } + + /** + * Builds the LauncherUserInfo object + */ + @NonNull public LauncherUserInfo build() { + return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber); + } + + } // End builder + + private LauncherUserInfo(@NonNull String userType, int userSerialNumber) { + this.mUserType = userType; + this.mUserSerialNumber = userSerialNumber; + } +} diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b37da8488db6..b15c9e4fa15b 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1480,7 +1480,6 @@ public abstract class PackageManager { INSTALL_ALLOW_DOWNGRADE, INSTALL_STAGED, INSTALL_REQUEST_UPDATE_OWNERSHIP, - INSTALL_IGNORE_DEXOPT_PROFILE, }) @Retention(RetentionPolicy.SOURCE) public @interface InstallFlags {} @@ -1713,18 +1712,6 @@ public abstract class PackageManager { public static final int INSTALL_ARCHIVED = 1 << 27; /** - * If set, all dexopt profiles are ignored by dexopt during the installation, including the - * profile in the DM file and the profile embedded in the APK file. If an invalid profile is - * provided during installation, no warning will be reported by {@code adb install}. - * - * This option does not affect later dexopt operations (e.g., background dexopt and manual `pm - * compile` invocations). - * - * @hide - */ - public static final int INSTALL_IGNORE_DEXOPT_PROFILE = 1 << 28; - - /** * Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is * a development-only feature and should not be used on end user devices. * diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index db12728cfb98..96609ad241bc 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -15,9 +15,9 @@ flag { } flag { - name: "prevent_sdk_lib_app" + name: "disallow_sdk_libs_to_be_apps" namespace: "package_manager_service" - description: "Feature flag to enable the prevent sdk-library be an application." + description: "Feature flag to disallow a <sdk-library> to be an <application>." bug: "295843617" is_fixed_read_only: true } diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 20771af7d26d..524afe975d73 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -153,7 +153,7 @@ public final class CredentialManager { mService.getCandidateCredentials( request, new GetCandidateCredentialsTransport(executor, callback), - mContext.getOpPackageName()); + callingPackage); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl index 1333f0da725f..a9742cb6c084 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 490ff640885e..7a43286692c4 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 6ac1efb49839..39fbe83b6abb 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/nfc/Constants.java b/core/java/android/nfc/Constants.java new file mode 100644 index 000000000000..f76833063605 --- /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 46586308e3cf..4a7bd3f29458 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/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java index 958669ee5852..ae3e333051d7 100644 --- a/core/java/android/nfc/cardemulation/AidGroup.java +++ b/core/java/android/nfc/cardemulation/AidGroup.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.regex.Pattern; /********************************************************************** * This file is not a part of the NFC mainline module * @@ -79,7 +80,7 @@ public final class AidGroup implements Parcelable { throw new IllegalArgumentException("Too many AIDs in AID group."); } for (String aid : aids) { - if (!CardEmulation.isValidAid(aid)) { + if (!isValidAid(aid)) { throw new IllegalArgumentException("AID " + aid + " is not a valid AID."); } } @@ -264,4 +265,34 @@ public final class AidGroup implements Parcelable { return CardEmulation.CATEGORY_PAYMENT.equals(category) || CardEmulation.CATEGORY_OTHER.equals(category); } + + private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?"); + /** + * Copied over from {@link CardEmulation#isValidAid(String)} + * @hide + */ + private static boolean isValidAid(String aid) { + if (aid == null) + return false; + + // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*') + if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) { + Log.e(TAG, "AID " + aid + " is not a valid AID."); + return false; + } + + // If not a prefix/subset AID, the total length must be even (even # of AID chars) + if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) { + Log.e(TAG, "AID " + aid + " is not a valid AID."); + return false; + } + + // Verify hex characters + if (!AID_PATTERN.matcher(aid).matches()) { + Log.e(TAG, "AID " + aid + " is not a valid AID."); + return false; + } + + return true; + } } diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index 18ec914860fb..665b7531d3ce 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -52,6 +52,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * Class holding APDU service info. @@ -307,7 +308,7 @@ public final class ApduServiceInfo implements Parcelable { com.android.internal.R.styleable.AidFilter); String aid = a.getString(com.android.internal.R.styleable.AidFilter_name). toUpperCase(); - if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) { + if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) { currentGroup.getAids().add(aid); } else { Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); @@ -321,7 +322,7 @@ public final class ApduServiceInfo implements Parcelable { toUpperCase(); // Add wildcard char to indicate prefix aid = aid.concat("*"); - if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) { + if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) { currentGroup.getAids().add(aid); } else { Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); @@ -335,7 +336,7 @@ public final class ApduServiceInfo implements Parcelable { toUpperCase(); // Add wildcard char to indicate suffix aid = aid.concat("#"); - if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) { + if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) { currentGroup.getAids().add(aid); } else { Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); @@ -806,7 +807,7 @@ public final class ApduServiceInfo implements Parcelable { */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull ProtoOutputStream proto) { - Utils.dumpDebugComponentName(getComponent(), proto, ApduServiceInfoProto.COMPONENT_NAME); + getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME); proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription()); proto.write(ApduServiceInfoProto.ON_HOST, mOnHost); if (!mOnHost) { @@ -825,4 +826,34 @@ public final class ApduServiceInfo implements Parcelable { } proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName); } + + private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?"); + /** + * Copied over from {@link CardEmulation#isValidAid(String)} + * @hide + */ + private static boolean isValidAid(String aid) { + if (aid == null) + return false; + + // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*') + if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) { + Log.e(TAG, "AID " + aid + " is not a valid AID."); + return false; + } + + // If not a prefix/subset AID, the total length must be even (even # of AID chars) + if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) { + Log.e(TAG, "AID " + aid + " is not a valid AID."); + return false; + } + + // Verify hex characters + if (!AID_PATTERN.matcher(aid).matches()) { + Log.e(TAG, "AID " + aid + " is not a valid AID."); + return false; + } + + return true; + } } diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index 4909b0830eeb..32c2a1b40530 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/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java index ec919e4d66bc..33bc16978721 100644 --- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java +++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java @@ -173,7 +173,7 @@ public final class NfcFServiceInfo implements Parcelable { com.android.internal.R.styleable.SystemCodeFilter); systemCode = a.getString( com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase(); - if (!NfcFCardEmulation.isValidSystemCode(systemCode) && + if (!isValidSystemCode(systemCode) && !systemCode.equalsIgnoreCase("NULL")) { Log.e(TAG, "Invalid System Code: " + systemCode); systemCode = null; @@ -187,7 +187,7 @@ public final class NfcFServiceInfo implements Parcelable { com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase(); if (!nfcid2.equalsIgnoreCase("RANDOM") && !nfcid2.equalsIgnoreCase("NULL") && - !NfcFCardEmulation.isValidNfcid2(nfcid2)) { + !isValidNfcid2(nfcid2)) { Log.e(TAG, "Invalid NFCID2: " + nfcid2); nfcid2 = null; } @@ -436,10 +436,62 @@ public final class NfcFServiceInfo implements Parcelable { */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull ProtoOutputStream proto) { - Utils.dumpDebugComponentName(getComponent(), proto, NfcFServiceInfoProto.COMPONENT_NAME); + getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME); proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription()); proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode()); proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2()); proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm()); } + + /** + * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)} + * @hide + */ + private static boolean isValidSystemCode(String systemCode) { + if (systemCode == null) { + return false; + } + if (systemCode.length() != 4) { + Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); + return false; + } + // check if the value is between "4000" and "4FFF" (excluding "4*FF") + if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) { + Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); + return false; + } + try { + Integer.parseInt(systemCode, 16); + } catch (NumberFormatException e) { + Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); + return false; + } + return true; + } + + /** + * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)} + * @hide + */ + private static boolean isValidNfcid2(String nfcid2) { + if (nfcid2 == null) { + return false; + } + if (nfcid2.length() != 16) { + Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); + return false; + } + // check if the the value starts with "02FE" + if (!nfcid2.toUpperCase().startsWith("02FE")) { + Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); + return false; + } + try { + Long.parseLong(nfcid2, 16); + } catch (NumberFormatException e) { + Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); + return false; + } + return true; + } } diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java index 151a65fdfaf5..ca1d49a93def 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 4c8ef97a7437..9034ff10286b 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/provider/Telephony.java b/core/java/android/provider/Telephony.java index a39157103f71..27ad45de69e6 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -3196,6 +3196,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)}. * <P>Type: TEXT</P> diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index a29bf7a06334..1afe8d95185b 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -1401,6 +1401,7 @@ public final class Dataset implements Parcelable { parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); parcel.writeInt(mEligibleReason); + parcel.writeTypedObject(mAuthenticationExtras, flags); } public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() { @@ -1436,6 +1437,7 @@ public final class Dataset implements Parcelable { android.content.IntentSender.class); final String datasetId = parcel.readString(); final int eligibleReason = parcel.readInt(); + final Bundle authenticationExtras = parcel.readTypedObject(Bundle.CREATOR); // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks @@ -1480,6 +1482,7 @@ public final class Dataset implements Parcelable { fieldDialogPresentation); } builder.setAuthentication(authentication); + builder.setAuthenticationExtras(authenticationExtras); builder.setId(datasetId); Dataset dataset = builder.build(); dataset.mEligibleReason = eligibleReason; diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java index 46fa5017106b..e17a955121b0 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -27,14 +27,6 @@ import com.android.text.flags.Flags; * @hide */ public class ClientFlags { - - /** - * @see Flags#deprecateFontsXml() - */ - public static boolean deprecateFontsXml() { - return TextFlags.isFeatureEnabled(Flags.FLAG_DEPRECATE_FONTS_XML); - } - /** * @see Flags#noBreakNoHyphenationSpan() */ diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 9edf298b1fd2..b8b30c230e5e 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -55,7 +55,6 @@ public final class TextFlags { * List of text flags to be transferred to the application process. */ public static final String[] TEXT_ACONFIGS_FLAGS = { - Flags.FLAG_DEPRECATE_FONTS_XML, Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN, Flags.FLAG_PHRASE_STRICT_FALLBACK, Flags.FLAG_USE_BOUNDS_FOR_WIDTH, @@ -67,7 +66,6 @@ public final class TextFlags { * The order must be the same to the TEXT_ACONFIG_FLAGS. */ public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { - Flags.deprecateFontsXml(), Flags.noBreakNoHyphenationSpan(), Flags.phraseStrictFallback(), Flags.useBoundsForWidth(), diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig deleted file mode 100644 index 52fe8834f234..000000000000 --- a/core/java/android/text/flags/custom_locale_fallback.aconfig +++ /dev/null @@ -1,9 +0,0 @@ -package: "com.android.text.flags" - -flag { - name: "custom_locale_fallback" - namespace: "text" - description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font." - is_fixed_read_only: true - bug: "278768958" -} diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig deleted file mode 100644 index 53621385dd4b..000000000000 --- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig +++ /dev/null @@ -1,10 +0,0 @@ -package: "com.android.text.flags" - -flag { - name: "deprecate_fonts_xml" - namespace: "text" - description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml" - # Make read only, as it could be used before the Settings provider is initialized. - is_fixed_read_only: true - bug: "281769620" -} diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig deleted file mode 100644 index b0aa72a765cc..000000000000 --- a/core/java/android/text/flags/fix_double_underline.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "com.android.text.flags" - -flag { - name: "fix_double_underline" - namespace: "text" - description: "Feature flag for fixing double underline because of the multiple font used in the single line." - bug: "297336724" -} diff --git a/core/java/android/text/flags/fix_line_height_for_locale.aconfig b/core/java/android/text/flags/fix_line_height_for_locale.aconfig deleted file mode 100644 index 8696bfa61e5c..000000000000 --- a/core/java/android/text/flags/fix_line_height_for_locale.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "com.android.text.flags" - -flag { - name: "fix_line_height_for_locale" - namespace: "text" - description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin" - bug: "303326708" -} diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig new file mode 100644 index 000000000000..201f680860f5 --- /dev/null +++ b/core/java/android/text/flags/flags.aconfig @@ -0,0 +1,63 @@ +package: "com.android.text.flags" + +flag { + name: "vendor_custom_locale_fallback" + namespace: "text" + description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font." + is_fixed_read_only: true + bug: "278768958" +} + +flag { + name: "new_fonts_fallback_xml" + namespace: "text" + description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml" + # Make read only, as it could be used before the Settings provider is initialized. + is_fixed_read_only: true + bug: "281769620" +} + +flag { + name: "fix_double_underline" + namespace: "text" + description: "Feature flag for fixing double underline because of the multiple font used in the single line." + bug: "297336724" +} + +flag { + name: "fix_line_height_for_locale" + namespace: "text" + description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin" + bug: "303326708" +} + +flag { + name: "no_break_no_hyphenation_span" + namespace: "text" + description: "A feature flag that adding new spans that prevents line breaking and hyphenation." + bug: "283193586" +} + +flag { + name: "use_optimized_boottime_font_loading" + namespace: "text" + description: "Feature flag ensuring that font is loaded once and asynchronously." + # Make read only, as font loading is in the critical boot path which happens before the read-write + # flags propagate to the device. + is_fixed_read_only: true + bug: "304406888" +} + +flag { + name: "phrase_strict_fallback" + namespace: "text" + description: "Feature flag for automatic fallback from phrase based line break to strict line break." + bug: "281970875" +} + +flag { + name: "use_bounds_for_width" + namespace: "text" + description: "Feature flag for preventing horizontal clipping." + bug: "63938206" +} diff --git a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig deleted file mode 100644 index 60f1e8802ea1..000000000000 --- a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "com.android.text.flags" - -flag { - name: "no_break_no_hyphenation_span" - namespace: "text" - description: "A feature flag that adding new spans that prevents line breaking and hyphenation." - bug: "283193586" -} diff --git a/core/java/android/text/flags/optimized_font_loading.aconfig b/core/java/android/text/flags/optimized_font_loading.aconfig deleted file mode 100644 index 0e4a69f90375..000000000000 --- a/core/java/android/text/flags/optimized_font_loading.aconfig +++ /dev/null @@ -1,11 +0,0 @@ -package: "com.android.text.flags" - -flag { - name: "use_optimized_boottime_font_loading" - namespace: "text" - description: "Feature flag ensuring that font is loaded once and asynchronously." - # Make read only, as font loading is in the critical boot path which happens before the read-write - # flags propagate to the device. - is_fixed_read_only: true - bug: "304406888" -} diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig deleted file mode 100644 index c67a21bca0b4..000000000000 --- a/core/java/android/text/flags/phrase_strict_fallback.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "com.android.text.flags" - -flag { - name: "phrase_strict_fallback" - namespace: "text" - description: "Feature flag for automatic fallback from phrase based line break to strict line break." - bug: "281970875" -} diff --git a/core/java/android/text/flags/use_bounds_for_width.aconfig b/core/java/android/text/flags/use_bounds_for_width.aconfig deleted file mode 100644 index d89d5f4c7216..000000000000 --- a/core/java/android/text/flags/use_bounds_for_width.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "com.android.text.flags" - -flag { - name: "use_bounds_for_width" - namespace: "text" - description: "Feature flag for preventing horizontal clipping." - bug: "63938206" -} diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 2f3d73a36425..42a0c9a9cfe9 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 0f35048cac67..52be8f6a76fd 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; } } @@ -248,6 +273,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/View.java b/core/java/android/view/View.java index 49b16c7697c9..e9d0e4c557c6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -19,8 +19,6 @@ package android.view; import static android.content.res.Resources.ID_NULL; import static android.os.Trace.TRACE_TAG_APP; import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; -import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; -import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; @@ -29,7 +27,6 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH; import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE; import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; -import static android.view.flags.Flags.toolkitSetFrameRate; import static android.view.flags.Flags.viewVelocityApi; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; @@ -117,7 +114,6 @@ import android.sysprop.DisplayProperties; import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.DisplayMetrics; import android.util.FloatProperty; import android.util.LayoutDirection; import android.util.Log; @@ -5513,11 +5509,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private ViewTranslationResponse mViewTranslationResponse; /** - * A threshold value to determine the frame rate category of the View based on the size. - */ - private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f; - - /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -20192,9 +20183,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return; } - // For VRR to vote the preferred frame rate - votePreferredFrameRate(); - // Reset content capture caches mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK; mContentCaptureSessionCached = false; @@ -20297,8 +20285,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ protected void damageInParent() { if (mParent != null && mAttachInfo != null) { - // For VRR to vote the preferred frame rate - votePreferredFrameRate(); mParent.onDescendantInvalidated(this, this); } } @@ -32995,40 +32981,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return null; } - private float getSizePercentage() { - if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) { - return 0; - } - - DisplayMetrics displayMetrics = mResources.getDisplayMetrics(); - int screenSize = displayMetrics.widthPixels - * displayMetrics.heightPixels; - int viewSize = getWidth() * getHeight(); - - if (screenSize == 0 || viewSize == 0) { - return 0f; - } - return (float) viewSize / screenSize; - } - - private int calculateFrameRateCategory() { - float sizePercentage = getSizePercentage(); - - if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) { - return FRAME_RATE_CATEGORY_LOW; - } else { - return FRAME_RATE_CATEGORY_NORMAL; - } - } - - private void votePreferredFrameRate() { - // use toolkitSetFrameRate flag to gate the change - ViewRootImpl viewRootImpl = getViewRootImpl(); - if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) { - viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory()); - } - } - /** * Set the current velocity of the View, we only track positive value. * We will use the velocity information to adjust the frame rate when applicable. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 50eeed81e16b..dfada58771a6 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -24,8 +24,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; -import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; -import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -76,10 +74,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_E import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; @@ -92,7 +87,6 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; -import static android.view.flags.Flags.toolkitSetFrameRate; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -661,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; @@ -738,7 +736,6 @@ public final class ViewRootImpl implements ViewParent, private SurfaceControl mBoundsLayer; private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final Transaction mTransaction = new Transaction(); - private final Transaction mFrameRateTransaction = new Transaction(); @UnsupportedAppUsage boolean mAdded; @@ -962,34 +959,6 @@ public final class ViewRootImpl implements ViewParent, private AccessibilityWindowAttributes mAccessibilityWindowAttributes; - /* - * for Variable Refresh Rate project - */ - - // The preferred frame rate category of the view that - // could be updated on a frame-by-frame basis. - private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; - // The preferred frame rate category of the last frame that - // could be used to lower frame rate after touch boost - private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; - // The preferred frame rate of the view that is mainly used for - // touch boosting, view velocity handling, and TextureView. - private float mPreferredFrameRate = 0; - // Used to check if there were any view invalidations in - // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). - private boolean mHasInvalidation = false; - // Used to check if it is in the touch boosting period. - private boolean mIsFrameRateBoosting = false; - // Used to check if there is a message in the message queue - // for idleness handling. - private boolean mHasIdledMessage = false; - // time for touch boost period. - private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500; - // time for checking idle status periodically. - private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500; - // time for revaluating the idle status before lowering the frame rate. - private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500; - /** * A temporary object used so relayoutWindow can return the latest SyncSeqId * system. The SyncSeqId system was designed to work without synchronous relayout @@ -1926,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(); } @@ -3292,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)); } @@ -3882,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. @@ -3910,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) { @@ -3921,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(); @@ -3938,6 +3924,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransaction, mLastPerformDrawSkippedReason); } } + mWasLastDrawCanceled = cancelAndRedraw; + mLastTraversalWasVisible = isViewVisible; if (mAttachInfo.mContentCaptureEvents != null) { notifyContentCaptureEvents(); @@ -3958,12 +3946,6 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroupState = WMS_SYNC_NONE; } } - - // For the variable refresh rate project. - setPreferredFrameRate(mPreferredFrameRate); - setPreferredFrameRateCategory(mPreferredFrameRateCategory); - mLastPreferredFrameRateCategory = mPreferredFrameRateCategory; - mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; } private void createSyncIfNeeded() { @@ -4733,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); } }; @@ -4753,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, "draw-" + mTag); addFrameCommitCallbackIfNeeded(); @@ -6016,8 +6004,6 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36; private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37; private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38; - private static final int MSG_TOUCH_BOOST_TIMEOUT = 39; - private static final int MSG_CHECK_INVALIDATION_IDLE = 40; final class ViewRootHandler extends Handler { @Override @@ -6313,32 +6299,6 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; - case MSG_TOUCH_BOOST_TIMEOUT: - /** - * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). - */ - mIsFrameRateBoosting = false; - setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, - mLastPreferredFrameRateCategory)); - break; - case MSG_CHECK_INVALIDATION_IDLE: - if (!mHasInvalidation && !mIsFrameRateBoosting) { - mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; - setPreferredFrameRateCategory(mPreferredFrameRateCategory); - mHasIdledMessage = false; - } else { - /** - * If there is no invalidation within a certain period, - * we consider the display is idled. - * We then set the frame rate catetogry to NO_PREFERENCE. - * Note that SurfaceFlinger also has a mechanism to lower the refresh rate - * if there is no updates of the buffer. - */ - mHasInvalidation = false; - mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, - FRAME_RATE_IDLENESS_REEVALUATE_TIME); - } - break; } } } @@ -7281,7 +7241,6 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; - final int action = event.getAction(); boolean handled = mHandwritingInitiator.onTouchEvent(event); if (handled) { // If handwriting is started, toolkit doesn't receive ACTION_UP. @@ -7302,22 +7261,6 @@ public final class ViewRootImpl implements ViewParent, scheduleConsumeBatchedInputImmediately(); } } - - // For the variable refresh rate project - if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { - // set the frame rate to the maximum value. - mIsFrameRateBoosting = true; - setPreferredFrameRateCategory(mPreferredFrameRateCategory); - } - /** - * We want to lower the refresh rate when MotionEvent.ACTION_UP, - * MotionEvent.ACTION_CANCEL is detected. - * Not using ACTION_MOVE to avoid checking and sending messages too frequently. - */ - if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP - || action == MotionEvent.ACTION_CANCEL)) { - sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); - } return handled ? FINISH_HANDLED : FORWARD; } @@ -11517,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); @@ -11906,93 +11848,10 @@ public final class ViewRootImpl implements ViewParent, t.clearTrustedPresentationCallback(getSurfaceControl()); } - private void setPreferredFrameRateCategory(int preferredFrameRateCategory) { - if (!shouldSetFrameRateCategory()) { - return; - } - - int frameRateCategory = mIsFrameRateBoosting - ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; - - try { - mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, - frameRateCategory, false).apply(); - } catch (Exception e) { - Log.e(mTag, "Unable to set frame rate category", e); - } - - if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) { - // Check where the display is idled periodically. - // If so, set the frame rate category to NO_PREFERENCE - mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, - FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS); - mHasIdledMessage = true; - } - } - - private void setPreferredFrameRate(float preferredFrameRate) { - if (!shouldSetFrameRate()) { - return; - } - - try { - mFrameRateTransaction.setFrameRate(mSurfaceControl, - preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply(); - } catch (Exception e) { - Log.e(mTag, "Unable to set frame rate", e); + private void logAndTrace(String msg) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg); } - } - - private void sendDelayedEmptyMessage(int message, int delayedTime) { - mHandler.removeMessages(message); - - mHandler.sendEmptyMessageDelayed(message, delayedTime); - } - - private boolean shouldSetFrameRateCategory() { - // use toolkitSetFrameRate flag to gate the change - return mSurface.isValid() && toolkitSetFrameRate(); - } - - private boolean shouldSetFrameRate() { - // use toolkitSetFrameRate flag to gate the change - return mPreferredFrameRate > 0 && toolkitSetFrameRate(); - } - - private boolean shouldTouchBoost(int motionEventAction, int windowType) { - boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN - || motionEventAction == MotionEvent.ACTION_MOVE - || motionEventAction == MotionEvent.ACTION_UP; - boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION - || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION; - // use toolkitSetFrameRate flag to gate the change - return desiredAction && desiredType && toolkitSetFrameRate(); - } - - /** - * Allow Views to vote for the preferred frame rate category - * - * @param frameRateCategory the preferred frame rate category of a View - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) - public void votePreferredFrameRateCategory(int frameRateCategory) { - mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory); - mHasInvalidation = true; - } - - /** - * Get the value of mPreferredFrameRateCategory - */ - @VisibleForTesting - public int getPreferredFrameRateCategory() { - return mPreferredFrameRateCategory; - } - - /** - * Get the value of mPreferredFrameRate - */ - @VisibleForTesting - public float getPreferredFrameRate() { - return mPreferredFrameRate; + Log.d(mTag, msg); } } diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index cc612ed93b2f..6888b50bb744 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/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index a40ff643379a..96574f5c959e 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -3392,7 +3392,7 @@ public final class AutofillManager { return false; } for (String hint : hints) { - if (Objects.equals(hint, View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) { + if (hint != null && hint.startsWith(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) { return true; } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 17c82b63c443..a0628c4b9580 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 7cfd35b07547..79b3b4f686b4 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -8,6 +8,14 @@ flag { } 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" description: "On close to square display, when necessary, configuration includes status bar" diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index b1d22e069d9d..77e150239803 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 6a62c917f240..000000000000 --- 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/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 445ddf52bf1c..2993a0e63228 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -65,7 +65,6 @@ android_test { "device-time-shell-utils", "testables", "com.android.text.flags-aconfig-java", - "flag-junit", ], libs: [ @@ -76,7 +75,6 @@ android_test { "framework", "ext", "framework-res", - "android.view.flags-aconfig-java", ], jni_libs: [ "libpowermanagertest_jni", diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 3f78396e3a70..0d687b24a4e5 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -16,7 +16,7 @@ package android.graphics; -import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK; +import static com.android.text.flags.Flags.FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -931,7 +931,7 @@ public class TypefaceSystemFallbackTest { return String.format(xml, op, lang, font); } - @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_prepend() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -947,7 +947,7 @@ public class TypefaceSystemFallbackTest { assertB3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_replace() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -963,7 +963,7 @@ public class TypefaceSystemFallbackTest { assertB3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_append() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -979,7 +979,7 @@ public class TypefaceSystemFallbackTest { assertA3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_ScriptMismatch() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -995,7 +995,7 @@ public class TypefaceSystemFallbackTest { assertA3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK) + @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_SubscriptMatch() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java index e117051ba9de..71bdce4ecb0e 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/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 1a38decae604..6a9fc04230f8 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -17,11 +17,6 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; -import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE; -import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; -import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; -import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; -import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; @@ -53,12 +48,8 @@ import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; -import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; @@ -106,10 +97,6 @@ public class ViewRootImplTest { // state after the test completes. private static boolean sOriginalTouchMode; - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @BeforeClass public static void setUpClass() { sContext = sInstrumentation.getTargetContext(); @@ -440,129 +427,6 @@ public class ViewRootImplTest { assertThat(result).isFalse(); } - /** - * Test the default values are properly set - */ - @UiThreadTest - @Test - @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) - public void votePreferredFrameRate_getDefaultValues() { - ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, - sContext.getDisplayNoVerify()); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), - FRAME_RATE_CATEGORY_NO_PREFERENCE); - assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1); - } - - /** - * Test the value of the frame rate cateogry based on the visibility of a view - * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE - * Visible: FRAME_RATE_CATEGORY_NORMAL - */ - @Test - @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) - public void votePreferredFrameRate_voteFrameRateCategory_visibility() { - View view = new View(sContext); - attachViewToWindow(view); - ViewRootImpl viewRootImpl = view.getViewRootImpl(); - sInstrumentation.runOnMainSync(() -> { - view.setVisibility(View.INVISIBLE); - view.invalidate(); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), - FRAME_RATE_CATEGORY_NO_PREFERENCE); - }); - - sInstrumentation.runOnMainSync(() -> { - view.setVisibility(View.VISIBLE); - view.invalidate(); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), - FRAME_RATE_CATEGORY_NORMAL); - }); - } - - /** - * Test the value of the frame rate cateogry based on the size of a view. - * The current threshold value is 7% of the screen size - * <7%: FRAME_RATE_CATEGORY_LOW - */ - @Test - @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) - public void votePreferredFrameRate_voteFrameRateCategory_smallSize() { - View view = new View(sContext); - WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); - wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check - wmlp.width = 1; - wmlp.height = 1; - - sInstrumentation.runOnMainSync(() -> { - WindowManager wm = sContext.getSystemService(WindowManager.class); - wm.addView(view, wmlp); - }); - sInstrumentation.waitForIdleSync(); - - ViewRootImpl viewRootImpl = view.getViewRootImpl(); - sInstrumentation.runOnMainSync(() -> { - view.invalidate(); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); - }); - } - - /** - * Test the value of the frame rate cateogry based on the size of a view. - * The current threshold value is 7% of the screen size - * >=7% : FRAME_RATE_CATEGORY_NORMAL - */ - @Test - @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) - public void votePreferredFrameRate_voteFrameRateCategory_normalSize() { - View view = new View(sContext); - WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); - wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check - - sInstrumentation.runOnMainSync(() -> { - WindowManager wm = sContext.getSystemService(WindowManager.class); - Display display = wm.getDefaultDisplay(); - DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - wmlp.width = (int) (metrics.widthPixels * 0.9); - wmlp.height = (int) (metrics.heightPixels * 0.9); - wm.addView(view, wmlp); - }); - sInstrumentation.waitForIdleSync(); - - ViewRootImpl viewRootImpl = view.getViewRootImpl(); - sInstrumentation.runOnMainSync(() -> { - view.invalidate(); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); - }); - } - - /** - * Test how values of the frame rate cateogry are aggregated. - * It should take the max value among all of the voted categories per frame. - */ - @Test - @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) - public void votePreferredFrameRate_voteFrameRateCategory_aggregate() { - View view = new View(sContext); - attachViewToWindow(view); - sInstrumentation.runOnMainSync(() -> { - ViewRootImpl viewRootImpl = view.getViewRootImpl(); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), - FRAME_RATE_CATEGORY_NO_PREFERENCE); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); - viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); - assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); - }); - } - @Test public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() { mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index b71aaf3fc2e6..cc73ecee2146 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/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index 6e04a2f5e405..ba5628cd2bc1 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -182,7 +182,7 @@ public class FontCustomizationParser { // For ignoring the customization, consume the new-locale-family element but don't // register any customizations. - if (com.android.text.flags.Flags.customLocaleFallback()) { + if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) { outCustomization.add(new FontConfig.Customization.LocaleFallback( Locale.forLanguageTag(lang), intOp, family)); } diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index 4c753565eb5b..685fd825d43e 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -16,7 +16,7 @@ package android.graphics.fonts; -import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML; +import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -145,7 +145,7 @@ public final class FontFamily { * @return A variable font family. null if a variable font cannot be built from the given * fonts. */ - @FlaggedApi(FLAG_DEPRECATE_FONTS_XML) + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) public @Nullable FontFamily buildVariableFamily() { int variableFamilyType = analyzeAndResolveVariableType(mFonts); if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) { diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 618aa5b5019c..3ef714ed8bdf 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -241,7 +241,7 @@ public final class SystemFonts { int configVersion ) { final String fontsXml; - if (com.android.text.flags.Flags.deprecateFontsXml()) { + if (com.android.text.flags.Flags.newFontsFallbackXml()) { fontsXml = FONTS_XML; } else { fontsXml = LEGACY_FONTS_XML; @@ -272,7 +272,7 @@ public final class SystemFonts { */ public static @NonNull FontConfig getSystemPreinstalledFontConfig() { final String fontsXml; - if (com.android.text.flags.Flags.deprecateFontsXml()) { + if (com.android.text.flags.Flags.newFontsFallbackXml()) { fontsXml = FONTS_XML; } else { fontsXml = LEGACY_FONTS_XML; diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index 569f9b6fe945..7932e3334063 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -16,7 +16,7 @@ package android.graphics.text; -import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML; +import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML; import android.annotation.FlaggedApi; import android.annotation.IntRange; @@ -173,7 +173,7 @@ public final class PositionedGlyphs { * @param index the glyph index * @return true if the fake bold option is on, otherwise off. */ - @FlaggedApi(FLAG_DEPRECATE_FONTS_XML) + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) public boolean getFakeBold(@IntRange(from = 0) int index) { Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); return nGetFakeBold(mLayoutPtr, index); @@ -185,7 +185,7 @@ public final class PositionedGlyphs { * @param index the glyph index * @return true if the fake italic option is on, otherwise off. */ - @FlaggedApi(FLAG_DEPRECATE_FONTS_XML) + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) public boolean getFakeItalic(@IntRange(from = 0) int index) { Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); return nGetFakeItalic(mLayoutPtr, index); @@ -195,7 +195,7 @@ public final class PositionedGlyphs { * A special value returned by {@link #getWeightOverride(int)} and * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden. */ - @FlaggedApi(FLAG_DEPRECATE_FONTS_XML) + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) public static final float NO_OVERRIDE = Float.MIN_VALUE; /** @@ -205,7 +205,7 @@ public final class PositionedGlyphs { * @param index the glyph index * @return overridden weight value or {@link #NO_OVERRIDE}. */ - @FlaggedApi(FLAG_DEPRECATE_FONTS_XML) + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) public float getWeightOverride(@IntRange(from = 0) int index) { Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); float value = nGetWeightOverride(mLayoutPtr, index); @@ -223,7 +223,7 @@ public final class PositionedGlyphs { * @param index the glyph index * @return overridden weight value or {@link #NO_OVERRIDE}. */ - @FlaggedApi(FLAG_DEPRECATE_FONTS_XML) + @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) public float getItalicOverride(@IntRange(from = 0) int index) { Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); float value = nGetItalicOverride(mLayoutPtr, index); 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 000000000000..ff49cdcab349 --- /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 cdfc4c87d271..2f1eec15e415 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 d894487fafb6..66e76c595652 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 9a0769a82d99..f4427aaba182 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 0a694b5c3b64..2ba5c9b320f1 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; @@ -158,14 +161,28 @@ 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 ed2ff2de245b..4ddbd13978d5 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 000000000000..af8017ae9544 --- /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 d440a3eb95de..6c0b3cf7971d 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 738c94e82a95..79f306ece283 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 000000000000..2a44f04f1358 --- /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 aad268394305..e48732801094 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/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java new file mode 100644 index 000000000000..f561aa5568be --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.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 com.android.wm.shell.transition; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; + +import static com.android.wm.shell.transition.Transitions.TransitionObserver; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.content.Context; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import com.android.wm.shell.common.RemoteCallable; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SingleInstanceRemoteListener; +import com.android.wm.shell.util.TransitionUtil; + +/** + * The {@link TransitionObserver} that observes for transitions involving the home + * activity. It reports transitions to the caller via {@link IHomeTransitionListener}. + */ +public class HomeTransitionObserver implements TransitionObserver, + RemoteCallable<HomeTransitionObserver> { + private final SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener> + mListener; + + private @NonNull final Context mContext; + private @NonNull final ShellExecutor mMainExecutor; + private @NonNull final Transitions mTransitions; + + public HomeTransitionObserver(@NonNull Context context, + @NonNull ShellExecutor mainExecutor, + @NonNull Transitions transitions) { + mContext = context; + mMainExecutor = mainExecutor; + mTransitions = transitions; + + mListener = new SingleInstanceRemoteListener<>(this, + c -> mTransitions.registerObserver(this), + c -> mTransitions.unregisterObserver(this)); + + } + + @Override + public void onTransitionReady(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + for (TransitionInfo.Change change : info.getChanges()) { + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null || taskInfo.taskId == -1) { + continue; + } + + final int mode = change.getMode(); + if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME + && TransitionUtil.isOpenOrCloseMode(mode)) { + mListener.call(l -> l.onHomeVisibilityChanged(TransitionUtil.isOpeningType(mode))); + } + } + } + + @Override + public void onTransitionStarting(@NonNull IBinder transition) {} + + @Override + public void onTransitionMerged(@NonNull IBinder merged, + @NonNull IBinder playing) {} + + @Override + public void onTransitionFinished(@NonNull IBinder transition, + boolean aborted) {} + + /** + * Sets the home transition listener that receives any transitions resulting in a change of + * + */ + public void setHomeTransitionListener(IHomeTransitionListener listener) { + if (listener != null) { + mListener.register(listener); + } else { + mListener.unregister(); + } + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + + /** + * Invalidates this controller, preventing future calls to send updates. + */ + public void invalidate() { + mTransitions.unregisterObserver(this); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl index ffe38ddacfda..18716c68da27 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl @@ -14,14 +14,19 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.base.interactor +package com.android.wm.shell.transition; -import com.android.systemui.qs.tiles.viewmodel.QSTileState -import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import android.window.RemoteTransition; +import android.window.TransitionFilter; -sealed interface StateUpdateTrigger { - class UserAction<T>(val action: QSTileUserAction, val tileState: QSTileState, val tileData: T) : - StateUpdateTrigger - data object ForceUpdate : StateUpdateTrigger - data object InitialRequest : StateUpdateTrigger +/** + * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks. + */ +interface IHomeTransitionListener { + + /** + * Called when a transition changes the visibility of the home activity. + */ + void onHomeVisibilityChanged(in boolean isVisible); } + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl index cc4d268a0000..644a6a5114a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl @@ -19,6 +19,8 @@ package com.android.wm.shell.transition; import android.window.RemoteTransition; import android.window.TransitionFilter; +import com.android.wm.shell.transition.IHomeTransitionListener; + /** * Interface that is exposed to remote callers to manipulate the transitions feature. */ @@ -39,4 +41,7 @@ interface IShellTransitions { * Retrieves the apply-token used by transactions in Shell */ IBinder getShellApplyToken() = 3; + + /** Set listener that will receive callbacks about transitions involving home activity */ + oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 576bba96044c..baa9acaafa4b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -356,7 +356,7 @@ public class Transitions implements RemoteCallable<Transitions>, } private ExternalInterfaceBinder createExternalInterface() { - return new IShellTransitionsImpl(this); + return new IShellTransitionsImpl(mContext, getMainExecutor(), this); } @Override @@ -1400,9 +1400,12 @@ public class Transitions implements RemoteCallable<Transitions>, private static class IShellTransitionsImpl extends IShellTransitions.Stub implements ExternalInterfaceBinder { private Transitions mTransitions; + private final HomeTransitionObserver mHomeTransitionObserver; - IShellTransitionsImpl(Transitions transitions) { + IShellTransitionsImpl(Context context, ShellExecutor executor, Transitions transitions) { mTransitions = transitions; + mHomeTransitionObserver = new HomeTransitionObserver(context, executor, + mTransitions); } /** @@ -1410,6 +1413,7 @@ public class Transitions implements RemoteCallable<Transitions>, */ @Override public void invalidate() { + mHomeTransitionObserver.invalidate(); mTransitions = null; } @@ -1434,6 +1438,14 @@ public class Transitions implements RemoteCallable<Transitions>, public IBinder getShellApplyToken() { return SurfaceControl.Transaction.getDefaultApplyToken(); } + + @Override + public void setHomeTransitionListener(IHomeTransitionListener listener) { + executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener", + (transitions) -> { + mHomeTransitionObserver.setHomeTransitionListener(listener); + }); + } } private class SettingsObserver extends ContentObserver { diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index e111edc96580..acfb259d7359 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -220,19 +220,6 @@ android_test { } android_test { - name: "WMShellFlickerTestsPip", - 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", - ], -} - -android_test { name: "WMShellFlickerTestsPip1", defaults: ["WMShellFlickerTestsDefault"], additional_manifests: ["manifests/AndroidManifestPip.xml"], diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index e672b983d509..e986c38a845a 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -27,3 +27,10 @@ flag { description: "APIs to create a new gainmap with a bitmap for metadata." bug: "304478551" } + +flag { + name: "clip_surfaceviews" + namespace: "core_graphics" + description: "Clip z-above surfaceviews to global clip rect" + bug: "298621623" +} diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp index 10427039c35a..814b682a2b98 100644 --- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp +++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp @@ -32,13 +32,13 @@ SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType) , mTotalSize("bytes", 0) , mPurgeableSize("bytes", 0) {} -const char* SkiaMemoryTracer::mapName(const char* resourceName) { +std::optional<std::string> SkiaMemoryTracer::mapName(const std::string& resourceName) { for (auto& resource : mResourceMap) { - if (SkStrContains(resourceName, resource.first)) { + if (resourceName.find(resource.first) != std::string::npos) { return resource.second; } } - return nullptr; + return std::nullopt; } void SkiaMemoryTracer::processElement() { @@ -62,7 +62,7 @@ void SkiaMemoryTracer::processElement() { } // find the type if one exists - const char* type; + std::string type; auto typeResult = mCurrentValues.find("type"); if (typeResult != mCurrentValues.end()) { type = typeResult->second.units; @@ -71,14 +71,13 @@ void SkiaMemoryTracer::processElement() { } // compute the type if we are itemizing or use the default "size" if we are not - const char* key = (mItemizeType) ? type : sizeResult->first; - SkASSERT(key != nullptr); + std::string key = (mItemizeType) ? type : sizeResult->first; // compute the top level element name using either the map or category key - const char* resourceName = mapName(mCurrentElement.c_str()); - if (mCategoryKey != nullptr) { + std::optional<std::string> resourceName = mapName(mCurrentElement); + if (mCategoryKey) { // find the category if one exists - auto categoryResult = mCurrentValues.find(mCategoryKey); + auto categoryResult = mCurrentValues.find(*mCategoryKey); if (categoryResult != mCurrentValues.end()) { resourceName = categoryResult->second.units; } else if (mItemizeType) { @@ -87,11 +86,11 @@ void SkiaMemoryTracer::processElement() { } // if we don't have a pretty name then use the dumpName - if (resourceName == nullptr) { - resourceName = mCurrentElement.c_str(); + if (!resourceName) { + resourceName = mCurrentElement; } - auto result = mResults.find(resourceName); + auto result = mResults.find(*resourceName); if (result != mResults.end()) { auto& resourceValues = result->second; typeResult = resourceValues.find(key); @@ -106,7 +105,7 @@ void SkiaMemoryTracer::processElement() { TraceValue sizeValue = sizeResult->second; mCurrentValues.clear(); mCurrentValues.insert({key, sizeValue}); - mResults.insert({resourceName, mCurrentValues}); + mResults.insert({*resourceName, mCurrentValues}); } } @@ -139,8 +138,9 @@ void SkiaMemoryTracer::logOutput(String8& log) { for (const auto& typedValue : namedItem.second) { TraceValue traceValue = convertUnits(typedValue.second); const char* entry = (traceValue.count > 1) ? "entries" : "entry"; - log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value, - traceValue.units, traceValue.count, entry); + log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first.c_str(), + traceValue.value, traceValue.units.c_str(), traceValue.count, + entry); } } else { auto result = namedItem.second.find("size"); @@ -148,7 +148,8 @@ void SkiaMemoryTracer::logOutput(String8& log) { TraceValue traceValue = convertUnits(result->second); const char* entry = (traceValue.count > 1) ? "entries" : "entry"; log.appendFormat(" %s: %.2f %s (%d %s)\n", namedItem.first.c_str(), - traceValue.value, traceValue.units, traceValue.count, entry); + traceValue.value, traceValue.units.c_str(), traceValue.count, + entry); } } } @@ -156,7 +157,7 @@ void SkiaMemoryTracer::logOutput(String8& log) { size_t SkiaMemoryTracer::total() { processElement(); - if (!strcmp("bytes", mTotalSize.units)) { + if ("bytes" == mTotalSize.units) { return mTotalSize.value; } return 0; @@ -166,16 +167,16 @@ void SkiaMemoryTracer::logTotals(String8& log) { TraceValue total = convertUnits(mTotalSize); TraceValue purgeable = convertUnits(mPurgeableSize); log.appendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value, - total.value, total.units, purgeable.value, purgeable.units); + total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str()); } SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) { TraceValue output(value); - if (SkString("bytes") == SkString(output.units) && output.value >= 1024) { + if ("bytes" == output.units && output.value >= 1024) { output.value = output.value / 1024.0f; output.units = "KB"; } - if (SkString("KB") == SkString(output.units) && output.value >= 1024) { + if ("KB" == output.units && output.value >= 1024) { output.value = output.value / 1024.0f; output.units = "MB"; } diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h index cba3b0422c6f..dbfc86bc033c 100644 --- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h +++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h @@ -16,8 +16,9 @@ #pragma once -#include <SkString.h> #include <SkTraceMemoryDump.h> +#include <optional> +#include <string> #include <utils/String8.h> #include <unordered_map> #include <vector> @@ -60,17 +61,17 @@ private: TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {} TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {} - const char* units; + std::string units; float value; int count; }; - const char* mapName(const char* resourceName); + std::optional<std::string> mapName(const std::string& resourceName); void processElement(); TraceValue convertUnits(const TraceValue& value); const std::vector<ResourcePair> mResourceMap; - const char* mCategoryKey = nullptr; + std::optional<std::string> mCategoryKey; const bool mItemizeType; // variables storing the size of all elements being dumped @@ -79,12 +80,12 @@ private: // variables storing information on the current node being dumped std::string mCurrentElement; - std::unordered_map<const char*, TraceValue> mCurrentValues; + std::unordered_map<std::string, TraceValue> mCurrentValues; // variable that stores the final format of the data after the individual elements are processed - std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults; + std::unordered_map<std::string, std::unordered_map<std::string, TraceValue>> mResults; }; } /* namespace skiapipeline */ } /* namespace uirenderer */ -} /* namespace android */
\ No newline at end of file +} /* namespace android */ diff --git a/location/api/current.txt b/location/api/current.txt index 33effdd6cd6c..0c23d8cd77e0 100644 --- a/location/api/current.txt +++ b/location/api/current.txt @@ -412,7 +412,9 @@ package android.location { field public static final int TYPE_GPS_L1CA = 257; // 0x101 field public static final int TYPE_GPS_L2CNAV = 258; // 0x102 field public static final int TYPE_GPS_L5CNAV = 259; // 0x103 - field public static final int TYPE_IRN_L5CA = 1793; // 0x701 + field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703 + field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702 + field @Deprecated public static final int TYPE_IRN_L5CA = 1793; // 0x701 field public static final int TYPE_QZS_L1CA = 1025; // 0x401 field public static final int TYPE_SBS = 513; // 0x201 field public static final int TYPE_UNKNOWN = 0; // 0x0 diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java index 637f90536125..32e636f8658b 100644 --- a/location/java/android/location/GnssNavigationMessage.java +++ b/location/java/android/location/GnssNavigationMessage.java @@ -16,10 +16,12 @@ package android.location; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.TestApi; +import android.location.flags.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -41,7 +43,7 @@ public final class GnssNavigationMessage implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef({TYPE_UNKNOWN, TYPE_GPS_L1CA, TYPE_GPS_L2CNAV, TYPE_GPS_L5CNAV, TYPE_GPS_CNAV2, TYPE_SBS, TYPE_GLO_L1CA, TYPE_QZS_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_BDS_CNAV1, - TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA}) + TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA, TYPE_IRN_L5, TYPE_IRN_L1}) public @interface GnssNavigationMessageType {} // The following enumerations must be in sync with the values declared in gps.h @@ -74,8 +76,18 @@ public final class GnssNavigationMessage implements Parcelable { public static final int TYPE_GAL_I = 0x0601; /** Galileo F/NAV message contained in the structure. */ public static final int TYPE_GAL_F = 0x0602; - /** IRNSS L5 C/A message contained in the structure. */ + /** + * NavIC L5 C/A message contained in the structure. + * @deprecated Use {@link #TYPE_IRN_L5} instead. + */ + @Deprecated public static final int TYPE_IRN_L5CA = 0x0701; + /** NavIC L5 message contained in the structure. */ + @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) + public static final int TYPE_IRN_L5 = 0x0702; + /** NavIC L1 message contained in the structure. */ + @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) + public static final int TYPE_IRN_L1 = 0x0703; /** * The status of the GNSS Navigation Message @@ -254,8 +266,15 @@ public final class GnssNavigationMessage implements Parcelable { case TYPE_GAL_F: return "Galileo F"; case TYPE_IRN_L5CA: - return "IRNSS L5 C/A"; + return "NavIC L5 C/A"; default: + if (Flags.gnssApiNavicL1()) { + if (mType == TYPE_IRN_L5) { + return "NavIC L5"; + } else if (mType == TYPE_IRN_L1) { + return "NavIC L1"; + } + } return "<Invalid:" + mType + ">"; } } @@ -303,9 +322,12 @@ public final class GnssNavigationMessage implements Parcelable { * navigation message, in the range of 1-25 (Subframe 1, 2, 3 does not contain a 'frame id' and * this value can be set to -1.)</li> * <li> For Beidou CNAV1 this refers to the page type number in the range of 1-63.</li> - * <li> For IRNSS L5 C/A subframe 3 and 4, this value corresponds to the Message Id of the + * <li> For NavIC L5 subframe 3 and 4, this value corresponds to the Message Id of the * navigation message, in the range of 1-63. (Subframe 1 and 2 does not contain a message type * id and this value can be set to -1.)</li> + * <li> For NavIC L1 subframe 3, this value corresponds to the Message Id of the navigation + * message, in the range of 1-63. (Subframe 1 and 2 does not contain a message type id and this + * value can be set to -1.)</li> * </ul> */ @IntRange(from = -1, to = 120) @@ -339,8 +361,10 @@ public final class GnssNavigationMessage implements Parcelable { * navigation message, in the range of 1-3.</li> * <li> For Beidou CNAV2, the submessage id corresponds to the message type, in the range * 1-63.</li> - * <li> For IRNSS L5 C/A, the submessage id corresponds to the subframe number of the - * navigation message, in the range of 1-4.</li> + * <li> For NavIC L5, the submessage id corresponds to the subframe number of the navigation + * message, in the range of 1-4.</li> + * <li> For NavIC L1, the submessage id corresponds to the subframe number of the navigation + * message, in the range of 1-3.</li> * </ul> */ @IntRange(from = 1) @@ -363,7 +387,7 @@ public final class GnssNavigationMessage implements Parcelable { * <p>The bytes (or words) specified using big endian format (MSB first). * * <ul> - * <li>For GPS L1 C/A, IRNSS L5 C/A, Beidou D1 & Beidou D2, each subframe contains 10 + * <li>For GPS L1 C/A, NavIC L5, Beidou D1 & Beidou D2, each subframe contains 10 * 30-bit words. Each word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip * B31 and B32), with MSB first, for a total of 40 bytes, covering a time period of 6, 6, and * 0.6 seconds, respectively.</li> @@ -383,6 +407,9 @@ public final class GnssNavigationMessage implements Parcelable { * 75 bytes. subframe #3 consists of 264 data bits that should be fit into 33 bytes.</li> * <li>For Beidou CNAV2, each subframe consists of 288 data bits, that should be fit into 36 * bytes.</li> + * <li> For NavIC L1, subframe #1 consists of 9 data bits that should be fit into 2 bytes (skip + * B10-B16). subframe #2 consists of 600 bits that should be fit into 75 bytes. subframe #3 + * consists of 274 data bits that should be fit into 35 bytes (skip B275-B280).</li> * </ul> */ @NonNull diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig new file mode 100644 index 000000000000..c471a2749617 --- /dev/null +++ b/location/java/android/location/flags/gnss.aconfig @@ -0,0 +1,8 @@ +package: "android.location.flags" + +flag { + name: "gnss_api_navic_l1" + namespace: "location" + description: "Flag for GNSS API for NavIC L1" + bug: "302199306" +}
\ No newline at end of file diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 91fa873078fc..cccf6f1caca7 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, @@ -206,6 +211,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. * * @see #getType @@ -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 159427bc2796..76a00acfa1c4 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); } /** @@ -2428,6 +2425,23 @@ public final class MediaRouter2 { } /** + * 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 31e65eb13926..10c880d8ab06 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -176,4 +176,47 @@ 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); + + /** + * Notifies system server that the permission request was displayed. + * + * <p>Only used for emitting atoms. + * + * @param hostUid The uid of the process requesting consent to capture, may be an app or + * SystemUI. + */ + @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + oneway void notifyPermissionRequestDisplayed(int hostUid); + + /** + * Notifies system server that the app selector was displayed. + * + * <p>Only used for emitting atoms. + * + * @param hostUid The uid of the process requesting consent to capture, may be an app or + * SystemUI. + */ + @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + + ".permission.MANAGE_MEDIA_PROJECTION)") + oneway void notifyAppSelectorDisplayed(int hostUid); } diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java index ffed80479662..a7c6c690f852 100644 --- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java +++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java @@ -16,6 +16,8 @@ package com.android.nfc_extras; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.HashMap; import android.content.Context; @@ -30,6 +32,8 @@ import android.util.Log; * * There is a 1-1 relationship between an {@link NfcAdapterExtras} object and * a {@link NfcAdapter} object. + * + * TODO(b/303286040): Deprecate this API surface since this is no longer supported (see ag/443092) */ public final class NfcAdapterExtras { private static final String TAG = "NfcAdapterExtras"; @@ -81,6 +85,18 @@ public final class NfcAdapterExtras { } } + private static Context getContextFromNfcAdapter(NfcAdapter adapter) { + try { + Method method = NfcAdapter.class.getDeclaredMethod("getContext"); + method.setAccessible(true); + return (Context) method.invoke(adapter); + } catch (SecurityException | NoSuchMethodException | IllegalArgumentException + | IllegalAccessException | IllegalAccessError | InvocationTargetException e) { + Log.e(TAG, "Unable to get context from NfcAdapter"); + } + return null; + } + /** * Get the {@link NfcAdapterExtras} for the given {@link NfcAdapter}. * @@ -91,7 +107,7 @@ public final class NfcAdapterExtras { * @return the {@link NfcAdapterExtras} object for the given {@link NfcAdapter} */ public static NfcAdapterExtras get(NfcAdapter adapter) { - Context context = adapter.getContext(); + Context context = getContextFromNfcAdapter(adapter); if (context == null) { throw new UnsupportedOperationException( "You must pass a context to your NfcAdapter to use the NFC extras APIs"); @@ -112,7 +128,7 @@ public final class NfcAdapterExtras { private NfcAdapterExtras(NfcAdapter adapter) { mAdapter = adapter; - mPackageName = adapter.getContext().getPackageName(); + mPackageName = getContextFromNfcAdapter(adapter).getPackageName(); mEmbeddedEe = new NfcExecutionEnvironment(this); mRouteOnWhenScreenOn = new CardEmulationRoute(CardEmulationRoute.ROUTE_ON_WHEN_SCREEN_ON, mEmbeddedEe); @@ -156,12 +172,24 @@ public final class NfcAdapterExtras { } } + private static void attemptDeadServiceRecoveryOnNfcAdapter(NfcAdapter adapter, Exception e) { + try { + Method method = NfcAdapter.class.getDeclaredMethod( + "attemptDeadServiceRecovery", Exception.class); + method.setAccessible(true); + method.invoke(adapter, e); + } catch (SecurityException | NoSuchMethodException | IllegalArgumentException + | IllegalAccessException | IllegalAccessError | InvocationTargetException ex) { + Log.e(TAG, "Unable to attempt dead service recovery on NfcAdapter"); + } + } + /** * NFC service dead - attempt best effort recovery */ void attemptDeadServiceRecovery(Exception e) { Log.e(TAG, "NFC Adapter Extras dead - attempting to recover"); - mAdapter.attemptDeadServiceRecovery(e); + attemptDeadServiceRecoveryOnNfcAdapter(mAdapter, e); initService(mAdapter); } diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml index 897585729596..47ce58735048 100644 --- a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml +++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml @@ -83,7 +83,7 @@ android:id="@android:id/progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="4dp" android:layout_marginTop="4dp" android:max="100" android:visibility="gone"/> diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp index eaeda3c67545..009407a91812 100644 --- a/packages/SettingsLib/SpaPrivileged/Android.bp +++ b/packages/SettingsLib/SpaPrivileged/Android.bp @@ -38,6 +38,7 @@ java_defaults { static_libs: [ "androidx.compose.runtime_runtime", "SpaPrivilegedLib", + "android.content.pm.flags-aconfig-java", ], kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt index a428142f4cc8..d95dd8c9ab13 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt @@ -17,6 +17,8 @@ package com.android.settingslib.spaprivileged.model.app import android.content.Context +import android.content.pm.FeatureFlags +import android.content.pm.FeatureFlagsImpl import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager @@ -65,10 +67,15 @@ object AppListRepositoryUtil { AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId) } -class AppListRepositoryImpl(private val context: Context) : AppListRepository { +class AppListRepositoryImpl( + private val context: Context, + private val featureFlags: FeatureFlags +) : AppListRepository { private val packageManager = context.packageManager private val userManager = context.userManager + constructor(context: Context) : this(context, FeatureFlagsImpl()) + override suspend fun loadApps( userId: Int, loadInstantApps: Boolean, @@ -98,9 +105,13 @@ class AppListRepositoryImpl(private val context: Context) : AppListRepository { userId: Int, matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> { + val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() + val archivedPackagesFlag: Long = if (featureFlags.archiving()) + PackageManager.MATCH_ARCHIVED_PACKAGES else 0L val regularFlags = ApplicationInfoFlags.of( - (PackageManager.MATCH_DISABLED_COMPONENTS or - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() + disabledComponentsFlag or + archivedPackagesFlag ) return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { packageManager.getInstalledApplicationsAsUser(regularFlags, userId) diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt index 517f67e3c44b..840bca8b14ec 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt @@ -47,6 +47,8 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import android.content.pm.FakeFeatureFlagsImpl +import android.content.pm.Flags @RunWith(AndroidJUnit4::class) class AppListRepositoryTest { @@ -268,6 +270,40 @@ class AppListRepositoryTest { } @Test + fun loadApps_archivedAppsEnabled() = runTest { + val fakeFlags = FakeFeatureFlagsImpl() + fakeFlags.setFlag(Flags.FLAG_ARCHIVING, true) + mockInstalledApplications(listOf(NORMAL_APP, ARCHIVED_APP), ADMIN_USER_ID) + val repository = AppListRepositoryImpl(context, fakeFlags) + val appList = repository.loadApps(userId = ADMIN_USER_ID) + + assertThat(appList).containsExactly(NORMAL_APP, ARCHIVED_APP) + argumentCaptor<ApplicationInfoFlags> { + verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID)) + assertThat(firstValue.value).isEqualTo( + (PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() or + PackageManager.MATCH_ARCHIVED_PACKAGES + ) + } + } + + @Test + fun loadApps_archivedAppsDisabled() = runTest { + mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) + val appList = repository.loadApps(userId = ADMIN_USER_ID) + + assertThat(appList).containsExactly(NORMAL_APP) + argumentCaptor<ApplicationInfoFlags> { + verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID)) + assertThat(firstValue.value).isEqualTo( + PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + ) + } + } + + @Test fun showSystemPredicate_showSystem() = runTest { val app = SYSTEM_APP @@ -391,6 +427,12 @@ class AppListRepositoryTest { flags = ApplicationInfo.FLAG_SYSTEM } + val ARCHIVED_APP = ApplicationInfo().apply { + packageName = "archived.app" + flags = ApplicationInfo.FLAG_SYSTEM + isArchived = true + } + fun resolveInfoOf(packageName: String) = ResolveInfo().apply { activityInfo = ActivityInfo().apply { this.packageName = packageName diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java index 57867be53bb9..f83e37b2fd5c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java @@ -19,6 +19,9 @@ package com.android.settingslib.bluetooth; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; +import android.annotation.CallbackExecutor; +import android.annotation.IntRange; +import android.annotation.NonNull; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -28,10 +31,11 @@ import android.content.Context; import android.os.Build; import android.util.Log; +import androidx.annotation.RequiresApi; + import java.util.ArrayList; import java.util.List; - -import androidx.annotation.RequiresApi; +import java.util.concurrent.Executor; /** * VolumeControlProfile handles Bluetooth Volume Control Controller role @@ -102,6 +106,88 @@ public class VolumeControlProfile implements LocalBluetoothProfile { BluetoothProfile.VOLUME_CONTROL); } + + /** + * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the + * operation of this profile. + * + * Repeated registration of the same <var>callback</var> object will have no effect after + * the first call to this method, even when the <var>executor</var> is different. API caller + * would have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with + * the same callback object before registering it again. + * + * @param executor an {@link Executor} to execute given callback + * @param callback user implementation of the {@link BluetoothVolumeControl.Callback} + * @throws IllegalArgumentException if a null executor or callback is given + */ + public void registerCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull BluetoothVolumeControl.Callback callback) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot register callback."); + return; + } + mService.registerCallback(executor, callback); + } + + /** + * Unregisters the specified {@link BluetoothVolumeControl.Callback}. + * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling + * {@link #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used. + * + * <p>Callbacks are automatically unregistered when application process goes away + * + * @param callback user implementation of the {@link BluetoothVolumeControl.Callback} + * @throws IllegalArgumentException when callback is null or when no callback is registered + */ + public void unregisterCallback(@NonNull BluetoothVolumeControl.Callback callback) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot unregister callback."); + return; + } + mService.unregisterCallback(callback); + } + + /** + * Tells the remote device to set a volume offset to the absolute volume. + * + * @param device {@link BluetoothDevice} representing the remote device + * @param volumeOffset volume offset to be set on the remote device + */ + public void setVolumeOffset(BluetoothDevice device, + @IntRange(from = -255, to = 255) int volumeOffset) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot set volume offset."); + return; + } + if (device == null) { + Log.w(TAG, "Device is null. Cannot set volume offset."); + return; + } + mService.setVolumeOffset(device, volumeOffset); + } + + /** + * Provides information about the possibility to set volume offset on the remote device. + * If the remote device supports Volume Offset Control Service, it is automatically + * connected. + * + * @param device {@link BluetoothDevice} representing the remote device + * @return {@code true} if volume offset function is supported and available to use on the + * remote device. When Bluetooth is off, the return value should always be + * {@code false}. + */ + public boolean isVolumeOffsetAvailable(BluetoothDevice device) { + if (mService == null) { + Log.w(TAG, "Proxy not attached to service. Cannot get is volume offset available."); + return false; + } + if (device == null) { + Log.w(TAG, "Device is null. Cannot get is volume offset available."); + return false; + } + return mService.isVolumeOffsetAvailable(device); + } + @Override public boolean accessProfileEnabled() { return false; @@ -113,12 +199,12 @@ public class VolumeControlProfile implements LocalBluetoothProfile { } /** - * Get VolumeControlProfile devices matching connection states{ + * Gets VolumeControlProfile devices matching connection states{ + * {@code BluetoothProfile.STATE_CONNECTED}, + * {@code BluetoothProfile.STATE_CONNECTING}, + * {@code BluetoothProfile.STATE_DISCONNECTING}} * * @return Matching device list - * @code BluetoothProfile.STATE_CONNECTED, - * @code BluetoothProfile.STATE_CONNECTING, - * @code BluetoothProfile.STATE_DISCONNECTING} */ public List<BluetoothDevice> getConnectedDevices() { if (mService == null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java index e38e041a87dc..2a2841745fa0 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 bf63f5d80450..5dacba5357cd 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 147412d08d2c..8085c998abea 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 a63bbdf36fa8..c44f66e99d00 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/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java new file mode 100644 index 000000000000..c56062739735 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java @@ -0,0 +1,245 @@ +/* + * 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.settingslib.bluetooth; + +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothVolumeControl; +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Executor; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) +public class VolumeControlProfileTest { + + private static final int TEST_VOLUME_OFFSET = 10; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private CachedBluetoothDeviceManager mDeviceManager; + @Mock + private LocalBluetoothProfileManager mProfileManager; + @Mock + private BluetoothDevice mBluetoothDevice; + @Mock + private BluetoothVolumeControl mService; + + private final Context mContext = ApplicationProvider.getApplicationContext(); + private BluetoothProfile.ServiceListener mServiceListener; + private VolumeControlProfile mProfile; + + @Before + public void setUp() { + mProfile = new VolumeControlProfile(mContext, mDeviceManager, mProfileManager); + final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class); + final ShadowBluetoothAdapter shadowBluetoothAdapter = + Shadow.extract(bluetoothManager.getAdapter()); + mServiceListener = shadowBluetoothAdapter.getServiceListener(); + } + + @Test + public void onServiceConnected_isProfileReady() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + + assertThat(mProfile.isProfileReady()).isTrue(); + verify(mProfileManager).callServiceConnectedListeners(); + } + + @Test + public void onServiceDisconnected_isProfileNotReady() { + mServiceListener.onServiceDisconnected(BluetoothProfile.VOLUME_CONTROL); + + assertThat(mProfile.isProfileReady()).isFalse(); + verify(mProfileManager).callServiceDisconnectedListeners(); + } + + @Test + public void getConnectionStatus_returnCorrectConnectionState() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.getConnectionState(mBluetoothDevice)) + .thenReturn(BluetoothProfile.STATE_CONNECTED); + + assertThat(mProfile.getConnectionStatus(mBluetoothDevice)) + .isEqualTo(BluetoothProfile.STATE_CONNECTED); + } + + @Test + public void isEnabled_connectionPolicyAllowed_returnTrue() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED); + + assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue(); + } + + @Test + public void isEnabled_connectionPolicyForbidden_returnFalse() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.getConnectionPolicy(mBluetoothDevice)) + .thenReturn(CONNECTION_POLICY_FORBIDDEN); + + assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse(); + } + + @Test + public void getConnectionPolicy_returnCorrectConnectionPolicy() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED); + + assertThat(mProfile.getConnectionPolicy(mBluetoothDevice)) + .isEqualTo(CONNECTION_POLICY_ALLOWED); + } + + @Test + public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED); + when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED)) + .thenReturn(true); + + assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse(); + } + + @Test + public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.getConnectionPolicy(mBluetoothDevice)) + .thenReturn(CONNECTION_POLICY_FORBIDDEN); + when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED)) + .thenReturn(true); + + assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue(); + } + + @Test + public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED); + when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN)) + .thenReturn(true); + + assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue(); + } + + @Test + public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.getConnectionPolicy(mBluetoothDevice)) + .thenReturn(CONNECTION_POLICY_FORBIDDEN); + when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN)) + .thenReturn(true); + + assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue(); + } + + @Test + public void getConnectedDevices_returnCorrectList() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + int[] connectedStates = new int[] { + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}; + List<BluetoothDevice> connectedList = Arrays.asList( + mBluetoothDevice, + mBluetoothDevice, + mBluetoothDevice); + when(mService.getDevicesMatchingConnectionStates(connectedStates)) + .thenReturn(connectedList); + + assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size()); + } + + @Test + public void registerCallback_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + + final Executor executor = (command -> new Thread(command).start()); + final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {}; + mProfile.registerCallback(executor, callback); + + verify(mService).registerCallback(executor, callback); + } + + @Test + public void unregisterCallback_verifyIsCalled() { + final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {}; + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + + mProfile.unregisterCallback(callback); + + verify(mService).unregisterCallback(callback); + } + + @Test + public void setVolumeOffset_verifyIsCalled() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + + mProfile.setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET); + + verify(mService).setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET); + } + + @Test + public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true); + + final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice); + + verify(mService).isVolumeOffsetAvailable(mBluetoothDevice); + assertThat(available).isTrue(); + } + + @Test + public void isVolumeOffsetAvailable_verifyIsCalledAndReturnFalse() { + mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); + when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(false); + + final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice); + + verify(mService).isVolumeOffsetAvailable(mBluetoothDevice); + assertThat(available).isFalse(); + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 4e2fad0bece2..95d7039859b5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -40,7 +40,6 @@ import static com.android.providers.settings.SettingsState.getUserIdFromKey; import static com.android.providers.settings.SettingsState.isConfigSettingsKey; import static com.android.providers.settings.SettingsState.isGlobalSettingsKey; import static com.android.providers.settings.SettingsState.isSecureSettingsKey; -import static com.android.providers.settings.SettingsState.isSsaidSettingsKey; import static com.android.providers.settings.SettingsState.isSystemSettingsKey; import static com.android.providers.settings.SettingsState.makeKey; @@ -412,6 +411,9 @@ public class SettingsProvider extends ContentProvider { SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext()); synchronized (mLock) { mSettingsRegistry.migrateAllLegacySettingsIfNeededLocked(); + for (UserInfo user : mUserManager.getAliveUsers()) { + mSettingsRegistry.ensureSettingsForUserLocked(user.id); + } mSettingsRegistry.syncSsaidTableOnStartLocked(); } mHandler.post(() -> { @@ -427,65 +429,53 @@ public class SettingsProvider extends ContentProvider { public Bundle call(String method, String name, Bundle args) { final int requestingUserId = getRequestingUserId(args); switch (method) { - case Settings.CALL_METHOD_GET_CONFIG: { + case Settings.CALL_METHOD_GET_CONFIG -> { Setting setting = getConfigSetting(name); return packageValueForCallResult(SETTINGS_TYPE_CONFIG, name, requestingUserId, setting, isTrackingGeneration(args)); } - - case Settings.CALL_METHOD_GET_GLOBAL: { + case Settings.CALL_METHOD_GET_GLOBAL -> { Setting setting = getGlobalSetting(name); return packageValueForCallResult(SETTINGS_TYPE_GLOBAL, name, requestingUserId, setting, isTrackingGeneration(args)); } - - case Settings.CALL_METHOD_GET_SECURE: { + case Settings.CALL_METHOD_GET_SECURE -> { Setting setting = getSecureSetting(name, requestingUserId); return packageValueForCallResult(SETTINGS_TYPE_SECURE, name, requestingUserId, setting, isTrackingGeneration(args)); } - - case Settings.CALL_METHOD_GET_SYSTEM: { + case Settings.CALL_METHOD_GET_SYSTEM -> { Setting setting = getSystemSetting(name, requestingUserId); return packageValueForCallResult(SETTINGS_TYPE_SYSTEM, name, requestingUserId, setting, isTrackingGeneration(args)); } - - case Settings.CALL_METHOD_PUT_CONFIG: { + case Settings.CALL_METHOD_PUT_CONFIG -> { String value = getSettingValue(args); final boolean makeDefault = getSettingMakeDefault(args); insertConfigSetting(name, value, makeDefault); - break; } - - case Settings.CALL_METHOD_PUT_GLOBAL: { + case Settings.CALL_METHOD_PUT_GLOBAL -> { String value = getSettingValue(args); String tag = getSettingTag(args); final boolean makeDefault = getSettingMakeDefault(args); final boolean overrideableByRestore = getSettingOverrideableByRestore(args); insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false, overrideableByRestore); - break; } - - case Settings.CALL_METHOD_PUT_SECURE: { + case Settings.CALL_METHOD_PUT_SECURE -> { String value = getSettingValue(args); String tag = getSettingTag(args); final boolean makeDefault = getSettingMakeDefault(args); final boolean overrideableByRestore = getSettingOverrideableByRestore(args); insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false, overrideableByRestore); - break; } - - case Settings.CALL_METHOD_PUT_SYSTEM: { + case Settings.CALL_METHOD_PUT_SYSTEM -> { String value = getSettingValue(args); boolean overrideableByRestore = getSettingOverrideableByRestore(args); insertSystemSetting(name, value, requestingUserId, overrideableByRestore); - break; } - - case Settings.CALL_METHOD_SET_ALL_CONFIG: { + case Settings.CALL_METHOD_SET_ALL_CONFIG -> { String prefix = getSettingPrefix(args); Map<String, String> flags = getSettingFlags(args); Bundle result = new Bundle(); @@ -493,120 +483,96 @@ public class SettingsProvider extends ContentProvider { setAllConfigSettings(prefix, flags)); return result; } - - case Settings.CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG: { + case Settings.CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG -> { final int mode = getSyncDisabledMode(args); setSyncDisabledModeConfig(mode); - break; } - - case Settings.CALL_METHOD_GET_SYNC_DISABLED_MODE_CONFIG: { + case Settings.CALL_METHOD_GET_SYNC_DISABLED_MODE_CONFIG -> { Bundle result = new Bundle(); result.putInt(Settings.KEY_CONFIG_GET_SYNC_DISABLED_MODE_RETURN, getSyncDisabledModeConfig()); return result; } - - case Settings.CALL_METHOD_RESET_CONFIG: { + case Settings.CALL_METHOD_RESET_CONFIG -> { final int mode = getResetModeEnforcingPermission(args); String prefix = getSettingPrefix(args); resetConfigSetting(mode, prefix); - break; } - - case Settings.CALL_METHOD_RESET_GLOBAL: { + case Settings.CALL_METHOD_RESET_GLOBAL -> { final int mode = getResetModeEnforcingPermission(args); String tag = getSettingTag(args); resetGlobalSetting(requestingUserId, mode, tag); - break; } - - case Settings.CALL_METHOD_RESET_SECURE: { + case Settings.CALL_METHOD_RESET_SECURE -> { final int mode = getResetModeEnforcingPermission(args); String tag = getSettingTag(args); resetSecureSetting(requestingUserId, mode, tag); - break; } - - case Settings.CALL_METHOD_RESET_SYSTEM: { + case Settings.CALL_METHOD_RESET_SYSTEM -> { final int mode = getResetModeEnforcingPermission(args); String tag = getSettingTag(args); resetSystemSetting(requestingUserId, mode, tag); - break; } - - case Settings.CALL_METHOD_DELETE_CONFIG: { - int rows = deleteConfigSetting(name) ? 1 : 0; + case Settings.CALL_METHOD_DELETE_CONFIG -> { + int rows = deleteConfigSetting(name) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - - case Settings.CALL_METHOD_DELETE_GLOBAL: { + case Settings.CALL_METHOD_DELETE_GLOBAL -> { int rows = deleteGlobalSetting(name, requestingUserId, false) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - - case Settings.CALL_METHOD_DELETE_SECURE: { + case Settings.CALL_METHOD_DELETE_SECURE -> { int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - - case Settings.CALL_METHOD_DELETE_SYSTEM: { + case Settings.CALL_METHOD_DELETE_SYSTEM -> { int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0; Bundle result = new Bundle(); result.putInt(RESULT_ROWS_DELETED, rows); return result; } - - case Settings.CALL_METHOD_LIST_CONFIG: { + case Settings.CALL_METHOD_LIST_CONFIG -> { String prefix = getSettingPrefix(args); Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix), isTrackingGeneration(args)); reportDeviceConfigAccess(prefix); return result; } - - case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG: { + case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> { RemoteCallback callback = args.getParcelable( Settings.CALL_METHOD_MONITOR_CALLBACK_KEY); setMonitorCallback(callback); - break; } - - case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG: { + case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG -> { clearMonitorCallback(); - break; } - - case Settings.CALL_METHOD_LIST_GLOBAL: { + case Settings.CALL_METHOD_LIST_GLOBAL -> { Bundle result = new Bundle(); result.putStringArrayList(RESULT_SETTINGS_LIST, buildSettingsList(getAllGlobalSettings(null))); return result; } - - case Settings.CALL_METHOD_LIST_SECURE: { + case Settings.CALL_METHOD_LIST_SECURE -> { Bundle result = new Bundle(); result.putStringArrayList(RESULT_SETTINGS_LIST, buildSettingsList(getAllSecureSettings(requestingUserId, null))); return result; } - - case Settings.CALL_METHOD_LIST_SYSTEM: { + case Settings.CALL_METHOD_LIST_SYSTEM -> { Bundle result = new Bundle(); result.putStringArrayList(RESULT_SETTINGS_LIST, buildSettingsList(getAllSystemSettings(requestingUserId, null))); return result; } - - default: { + default -> { Slog.w(LOG_TAG, "call() with invalid method: " + method); - } break; + } } return null; @@ -638,7 +604,7 @@ public class SettingsProvider extends ContentProvider { } switch (args.table) { - case TABLE_GLOBAL: { + case TABLE_GLOBAL -> { if (args.name != null) { Setting setting = getGlobalSetting(args.name); return packageSettingForQuery(setting, normalizedProjection); @@ -646,8 +612,7 @@ public class SettingsProvider extends ContentProvider { return getAllGlobalSettings(projection); } } - - case TABLE_SECURE: { + case TABLE_SECURE -> { final int userId = UserHandle.getCallingUserId(); if (args.name != null) { Setting setting = getSecureSetting(args.name, userId); @@ -656,8 +621,7 @@ public class SettingsProvider extends ContentProvider { return getAllSecureSettings(userId, projection); } } - - case TABLE_SYSTEM: { + case TABLE_SYSTEM -> { final int userId = UserHandle.getCallingUserId(); if (args.name != null) { Setting setting = getSystemSetting(args.name, userId); @@ -666,8 +630,7 @@ public class SettingsProvider extends ContentProvider { return getAllSystemSettings(userId, projection); } } - - default: { + default -> { throw new IllegalArgumentException("Invalid Uri path:" + uri); } } @@ -708,30 +671,27 @@ public class SettingsProvider extends ContentProvider { String value = values.getAsString(Settings.Secure.VALUE); switch (table) { - case TABLE_GLOBAL: { + case TABLE_GLOBAL -> { if (insertGlobalSetting(name, value, null, false, UserHandle.getCallingUserId(), false, /* overrideableByRestore */ false)) { - return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name); + return Uri.withAppendedPath(Global.CONTENT_URI, name); } - } break; - - case TABLE_SECURE: { + } + case TABLE_SECURE -> { if (insertSecureSetting(name, value, null, false, UserHandle.getCallingUserId(), false, /* overrideableByRestore */ false)) { - return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name); + return Uri.withAppendedPath(Secure.CONTENT_URI, name); } - } break; - - case TABLE_SYSTEM: { + } + case TABLE_SYSTEM -> { if (insertSystemSetting(name, value, UserHandle.getCallingUserId(), /* overridableByRestore */ false)) { return Uri.withAppendedPath(Settings.System.CONTENT_URI, name); } - } break; - - default: { + } + default -> { throw new IllegalArgumentException("Bad Uri path:" + uri); } } @@ -775,22 +735,19 @@ public class SettingsProvider extends ContentProvider { } switch (args.table) { - case TABLE_GLOBAL: { + case TABLE_GLOBAL -> { final int userId = UserHandle.getCallingUserId(); return deleteGlobalSetting(args.name, userId, false) ? 1 : 0; } - - case TABLE_SECURE: { + case TABLE_SECURE -> { final int userId = UserHandle.getCallingUserId(); return deleteSecureSetting(args.name, userId, false) ? 1 : 0; } - - case TABLE_SYSTEM: { + case TABLE_SYSTEM -> { final int userId = UserHandle.getCallingUserId(); return deleteSystemSetting(args.name, userId) ? 1 : 0; } - - default: { + default -> { throw new IllegalArgumentException("Bad Uri path:" + uri); } } @@ -816,24 +773,21 @@ public class SettingsProvider extends ContentProvider { String value = values.getAsString(Settings.Secure.VALUE); switch (args.table) { - case TABLE_GLOBAL: { + case TABLE_GLOBAL -> { final int userId = UserHandle.getCallingUserId(); return updateGlobalSetting(args.name, value, null, false, userId, false) ? 1 : 0; } - - case TABLE_SECURE: { + case TABLE_SECURE -> { final int userId = UserHandle.getCallingUserId(); return updateSecureSetting(args.name, value, null, false, userId, false) ? 1 : 0; } - - case TABLE_SYSTEM: { + case TABLE_SYSTEM -> { final int userId = UserHandle.getCallingUserId(); return updateSystemSetting(args.name, value, userId) ? 1 : 0; } - - default: { + default -> { throw new IllegalArgumentException("Invalid Uri path:" + uri); } } @@ -1034,27 +988,38 @@ public class SettingsProvider extends ContentProvider { private void registerBroadcastReceivers() { IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_ADDED); userFilter.addAction(Intent.ACTION_USER_REMOVED); userFilter.addAction(Intent.ACTION_USER_STOPPED); getContext().registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (intent.getAction() == null) { + return; + } final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_SYSTEM); + UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) { + return; + } switch (intent.getAction()) { - case Intent.ACTION_USER_REMOVED: { + case Intent.ACTION_USER_ADDED -> { + synchronized (mLock) { + mSettingsRegistry.ensureSettingsForUserLocked(userId); + } + } + case Intent.ACTION_USER_REMOVED -> { synchronized (mLock) { mSettingsRegistry.removeUserStateLocked(userId, true); } - } break; - - case Intent.ACTION_USER_STOPPED: { + } + case Intent.ACTION_USER_STOPPED -> { synchronized (mLock) { mSettingsRegistry.removeUserStateLocked(userId, false); } - } break; + } } } }, userFilter); @@ -1350,26 +1315,24 @@ public class SettingsProvider extends ContentProvider { // Perform the mutation. synchronized (mLock) { switch (operation) { - case MUTATION_OPERATION_INSERT: { + case MUTATION_OPERATION_INSERT -> { enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name)); return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, name, value, null, makeDefault, true, callingPackage, false, null, /* overrideableByRestore */ false); } - - case MUTATION_OPERATION_DELETE: { + case MUTATION_OPERATION_DELETE -> { enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name)); return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, name, false, null); } - - case MUTATION_OPERATION_RESET: { + case MUTATION_OPERATION_RESET -> { enforceDeviceConfigWritePermission(getContext(), getAllConfigFlags(prefix).keySet()); - mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG, + return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM, callingPackage, mode, null, prefix); - } return true; + } } } @@ -1523,7 +1486,7 @@ public class SettingsProvider extends ContentProvider { enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS); // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); // If this is a setting that is currently restricted for this user, do not allow // unrestricting changes. @@ -1536,28 +1499,25 @@ public class SettingsProvider extends ContentProvider { // Perform the mutation. synchronized (mLock) { switch (operation) { - case MUTATION_OPERATION_INSERT: { + case MUTATION_OPERATION_INSERT -> { return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, name, value, tag, makeDefault, callingPackage, forceNotify, CRITICAL_GLOBAL_SETTINGS, overrideableByRestore); } - - case MUTATION_OPERATION_DELETE: { + case MUTATION_OPERATION_DELETE -> { return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, name, forceNotify, CRITICAL_GLOBAL_SETTINGS); } - - case MUTATION_OPERATION_UPDATE: { + case MUTATION_OPERATION_UPDATE -> { return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, name, value, tag, makeDefault, callingPackage, forceNotify, CRITICAL_GLOBAL_SETTINGS); } - - case MUTATION_OPERATION_RESET: { - mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL, + case MUTATION_OPERATION_RESET -> { + return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, callingPackage, mode, tag); - } return true; + } } } @@ -1580,12 +1540,12 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(userId); // The relevant "calling package" userId will be the owning userId for some // profiles, and we can't do the lookup inside our [lock held] loop, so work out // up front who the effective "new SSAID" user ID for that settings name will be. - final int ssaidUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, + final int ssaidUserId = resolveOwningUserIdForSecureSetting(callingUserId, Settings.Secure.ANDROID_ID); final PackageInfo ssaidCallingPkg = getCallingPackageInfo(ssaidUserId); @@ -1600,7 +1560,7 @@ public class SettingsProvider extends ContentProvider { for (int i = 0; i < nameCount; i++) { String name = names.get(i); // Determine the owning user as some profile settings are cloned from the parent. - final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, + final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name); if (!isSecureSettingAccessible(name)) { @@ -1638,13 +1598,13 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); // Ensure the caller can access the setting. enforceSettingReadable(name, SETTINGS_TYPE_SECURE, UserHandle.getCallingUserId()); // Determine the owning user as some profile settings are cloned from the parent. - final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name); + final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name); if (!isSecureSettingAccessible(name)) { // This caller is not permitted to access this setting. Pretend the setting doesn't @@ -1811,7 +1771,7 @@ public class SettingsProvider extends ContentProvider { enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS); // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); // If this is a setting that is currently restricted for this user, do not allow // unrestricting changes. @@ -1820,7 +1780,7 @@ public class SettingsProvider extends ContentProvider { } // Determine the owning user as some profile settings are cloned from the parent. - final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name); + final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name); // Only the owning user can change the setting. if (owningUserId != callingUserId) { @@ -1832,28 +1792,25 @@ public class SettingsProvider extends ContentProvider { // Mutate the value. synchronized (mLock) { switch (operation) { - case MUTATION_OPERATION_INSERT: { + case MUTATION_OPERATION_INSERT -> { return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name, value, tag, makeDefault, callingPackage, forceNotify, CRITICAL_SECURE_SETTINGS, overrideableByRestore); } - - case MUTATION_OPERATION_DELETE: { + case MUTATION_OPERATION_DELETE -> { return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name, forceNotify, CRITICAL_SECURE_SETTINGS); } - - case MUTATION_OPERATION_UPDATE: { + case MUTATION_OPERATION_UPDATE -> { return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SECURE, owningUserId, name, value, tag, makeDefault, callingPackage, forceNotify, CRITICAL_SECURE_SETTINGS); } - - case MUTATION_OPERATION_RESET: { - mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE, + case MUTATION_OPERATION_RESET -> { + return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE, UserHandle.USER_SYSTEM, callingPackage, mode, tag); - } return true; + } } } @@ -1866,7 +1823,7 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(userId); synchronized (mLock) { List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SYSTEM, callingUserId); @@ -1903,7 +1860,7 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId); // Ensure the caller can access the setting. enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, UserHandle.getCallingUserId()); @@ -1978,7 +1935,7 @@ public class SettingsProvider extends ContentProvider { } // Resolve the userId on whose behalf the call is made. - final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId); + final int callingUserId = resolveCallingUserIdEnforcingPermissions(runAsUserId); if (isSettingRestrictedForUser(name, callingUserId, value, Binder.getCallingUid())) { Slog.e(LOG_TAG, "UserId: " + callingUserId + " is disallowed to change system " @@ -2012,37 +1969,30 @@ public class SettingsProvider extends ContentProvider { // Mutate the value. synchronized (mLock) { switch (operation) { - case MUTATION_OPERATION_INSERT: { + case MUTATION_OPERATION_INSERT -> { validateSystemSettingValue(name, value); success = mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, value, null, false, callingPackage, false, null, overrideableByRestore); - break; } - - case MUTATION_OPERATION_DELETE: { + case MUTATION_OPERATION_DELETE -> { success = mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, false, null); - break; } - - case MUTATION_OPERATION_UPDATE: { + case MUTATION_OPERATION_UPDATE -> { validateSystemSettingValue(name, value); success = mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM, owningUserId, name, value, null, false, callingPackage, false, null); - break; } - - case MUTATION_OPERATION_RESET: { + case MUTATION_OPERATION_RESET -> { success = mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SYSTEM, runAsUserId, callingPackage, mode, tag); - break; } - - default: + default -> { success = false; Slog.e(LOG_TAG, "Unknown operation code: " + operation); + } } } @@ -2113,8 +2063,8 @@ public class SettingsProvider extends ContentProvider { * Returns {@code true} if the specified secure setting should be accessible to the caller. */ private boolean isSecureSettingAccessible(String name) { - switch (name) { - case "bluetooth_address": + return switch (name) { + case "bluetooth_address" -> // BluetoothManagerService for some reason stores the Android's Bluetooth MAC // address in this secure setting. Secure settings can normally be read by any app, // which thus enables them to bypass the recently introduced restrictions on access @@ -2122,22 +2072,23 @@ public class SettingsProvider extends ContentProvider { // To mitigate this we make this setting available only to callers privileged to see // this device's MAC addresses, same as through public API // BluetoothAdapter.getAddress() (see BluetoothManagerService for details). - return getContext().checkCallingOrSelfPermission( - Manifest.permission.LOCAL_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED; - default: - return true; - } + getContext().checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS) + == PackageManager.PERMISSION_GRANTED; + default -> true; + }; } - private int resolveOwningUserIdForSecureSettingLocked(int userId, String setting) { - return resolveOwningUserIdLocked(userId, sSecureCloneToManagedSettings, setting); + private int resolveOwningUserIdForSecureSetting(int userId, String setting) { + // no need to lock because sSecureCloneToManagedSettings is never modified + return resolveOwningUserId(userId, sSecureCloneToManagedSettings, setting); } + @GuardedBy("mLock") private int resolveOwningUserIdForSystemSettingLocked(int userId, String setting) { final int parentId; // Resolves dependency if setting has a dependency and the calling user has a parent if (sSystemCloneFromParentOnDependency.containsKey(setting) - && (parentId = getGroupParentLocked(userId)) != userId) { + && (parentId = getGroupParent(userId)) != userId) { // The setting has a dependency and the profile has a parent String dependency = sSystemCloneFromParentOnDependency.get(setting); // Lookup the dependency setting as ourselves, some callers may not have access to it. @@ -2151,11 +2102,11 @@ public class SettingsProvider extends ContentProvider { Binder.restoreCallingIdentity(token); } } - return resolveOwningUserIdLocked(userId, sSystemCloneToManagedSettings, setting); + return resolveOwningUserId(userId, sSystemCloneToManagedSettings, setting); } - private int resolveOwningUserIdLocked(int userId, Set<String> keys, String name) { - final int parentId = getGroupParentLocked(userId); + private int resolveOwningUserId(int userId, Set<String> keys, String name) { + final int parentId = getGroupParent(userId); if (parentId != userId && keys.contains(name)) { return parentId; } @@ -2174,9 +2125,8 @@ public class SettingsProvider extends ContentProvider { } switch (operation) { - case MUTATION_OPERATION_INSERT: - // Insert updates. - case MUTATION_OPERATION_UPDATE: { + // Insert updates. + case MUTATION_OPERATION_INSERT, MUTATION_OPERATION_UPDATE -> { if (Settings.System.PUBLIC_SETTINGS.contains(name)) { return; } @@ -2192,9 +2142,8 @@ public class SettingsProvider extends ContentProvider { warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk( packageInfo.applicationInfo.targetSdkVersion, name); - } break; - - case MUTATION_OPERATION_DELETE: { + } + case MUTATION_OPERATION_DELETE -> { if (Settings.System.PUBLIC_SETTINGS.contains(name) || Settings.System.PRIVATE_SETTINGS.contains(name)) { throw new IllegalArgumentException("You cannot delete system defined" @@ -2212,34 +2161,26 @@ public class SettingsProvider extends ContentProvider { warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk( packageInfo.applicationInfo.targetSdkVersion, name); - } break; + } } } - private Set<String> getInstantAppAccessibleSettings(int settingsType) { - switch (settingsType) { - case SETTINGS_TYPE_GLOBAL: - return Settings.Global.INSTANT_APP_SETTINGS; - case SETTINGS_TYPE_SECURE: - return Settings.Secure.INSTANT_APP_SETTINGS; - case SETTINGS_TYPE_SYSTEM: - return Settings.System.INSTANT_APP_SETTINGS; - default: - throw new IllegalArgumentException("Invalid settings type: " + settingsType); - } + private static Set<String> getInstantAppAccessibleSettings(int settingsType) { + return switch (settingsType) { + case SETTINGS_TYPE_GLOBAL -> Global.INSTANT_APP_SETTINGS; + case SETTINGS_TYPE_SECURE -> Secure.INSTANT_APP_SETTINGS; + case SETTINGS_TYPE_SYSTEM -> Settings.System.INSTANT_APP_SETTINGS; + default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType); + }; } - private Set<String> getOverlayInstantAppAccessibleSettings(int settingsType) { - switch (settingsType) { - case SETTINGS_TYPE_GLOBAL: - return OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS; - case SETTINGS_TYPE_SYSTEM: - return OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS; - case SETTINGS_TYPE_SECURE: - return OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS; - default: - throw new IllegalArgumentException("Invalid settings type: " + settingsType); - } + private static Set<String> getOverlayInstantAppAccessibleSettings(int settingsType) { + return switch (settingsType) { + case SETTINGS_TYPE_GLOBAL -> OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS; + case SETTINGS_TYPE_SYSTEM -> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS; + case SETTINGS_TYPE_SECURE -> OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS; + default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType); + }; } @GuardedBy("mLock") @@ -2270,7 +2211,7 @@ public class SettingsProvider extends ContentProvider { switch (settingName) { // missing READ_PRIVILEGED_PHONE_STATE permission protection // see alternative API {@link SubscriptionManager#getPreferredDataSubscriptionId() - case Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION: + case Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION -> { // app-compat handling, not break apps targeting on previous SDKs. if (CompatChanges.isChangeEnabled( ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL)) { @@ -2278,7 +2219,7 @@ public class SettingsProvider extends ContentProvider { Manifest.permission.READ_PRIVILEGED_PHONE_STATE, "access global settings MULTI_SIM_DATA_CALL_SUBSCRIPTION"); } - break; + } } if (!ai.isInstantApp()) { return; @@ -2306,23 +2247,22 @@ public class SettingsProvider extends ContentProvider { final Set<String> readableFields; final ArrayMap<String, Integer> readableFieldsWithMaxTargetSdk; switch (settingsType) { - case SETTINGS_TYPE_GLOBAL: + case SETTINGS_TYPE_GLOBAL -> { allFields = sAllGlobalSettings; readableFields = sReadableGlobalSettings; readableFieldsWithMaxTargetSdk = sReadableGlobalSettingsWithMaxTargetSdk; - break; - case SETTINGS_TYPE_SYSTEM: + } + case SETTINGS_TYPE_SYSTEM -> { allFields = sAllSystemSettings; readableFields = sReadableSystemSettings; readableFieldsWithMaxTargetSdk = sReadableSystemSettingsWithMaxTargetSdk; - break; - case SETTINGS_TYPE_SECURE: + } + case SETTINGS_TYPE_SECURE -> { allFields = sAllSecureSettings; readableFields = sReadableSecureSettings; readableFieldsWithMaxTargetSdk = sReadableSecureSettingsWithMaxTargetSdk; - break; - default: - throw new IllegalArgumentException("Invalid settings type: " + settingsType); + } + default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType); } if (allFields.contains(settingName)) { @@ -2380,7 +2320,7 @@ public class SettingsProvider extends ContentProvider { throw new IllegalStateException("Calling package doesn't exist"); } - private int getGroupParentLocked(int userId) { + private int getGroupParent(int userId) { // Most frequent use case. if (userId == UserHandle.USER_SYSTEM) { return userId; @@ -2480,7 +2420,7 @@ public class SettingsProvider extends ContentProvider { } } - private static int resolveCallingUserIdEnforcingPermissionsLocked(int requestingUserId) { + private static int resolveCallingUserIdEnforcingPermissions(int requestingUserId) { if (requestingUserId == UserHandle.getCallingUserId()) { return requestingUserId; } @@ -2654,28 +2594,28 @@ public class SettingsProvider extends ContentProvider { private static int getResetModeEnforcingPermission(Bundle args) { final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0; switch (mode) { - case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: { + case Settings.RESET_MODE_UNTRUSTED_DEFAULTS -> { if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) { throw new SecurityException("Only system, shell/root on a " + "debuggable build can reset to untrusted defaults"); } return mode; } - case Settings.RESET_MODE_UNTRUSTED_CHANGES: { + case Settings.RESET_MODE_UNTRUSTED_CHANGES -> { if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) { throw new SecurityException("Only system, shell/root on a " + "debuggable build can reset untrusted changes"); } return mode; } - case Settings.RESET_MODE_TRUSTED_DEFAULTS: { + case Settings.RESET_MODE_TRUSTED_DEFAULTS -> { if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) { throw new SecurityException("Only system, shell/root on a " + "debuggable build can reset to trusted defaults"); } return mode; } - case Settings.RESET_MODE_PACKAGE_DEFAULTS: { + case Settings.RESET_MODE_PACKAGE_DEFAULTS -> { return mode; } } @@ -2736,21 +2676,18 @@ public class SettingsProvider extends ContentProvider { String column = cursor.getColumnName(i); switch (column) { - case Settings.NameValueTable._ID: { + case Settings.NameValueTable._ID -> { values[i] = setting.getId(); - } break; - - case Settings.NameValueTable.NAME: { + } + case Settings.NameValueTable.NAME -> { values[i] = setting.getName(); - } break; - - case Settings.NameValueTable.VALUE: { + } + case Settings.NameValueTable.VALUE -> { values[i] = setting.getValue(); - } break; - - case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE: { + } + case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE -> { values[i] = String.valueOf(setting.isValuePreservedInRestore()); - } break; + } } } @@ -2762,19 +2699,11 @@ public class SettingsProvider extends ContentProvider { } private String resolveCallingPackage() { - switch (Binder.getCallingUid()) { - case Process.ROOT_UID: { - return "root"; - } - - case Process.SHELL_UID: { - return "com.android.shell"; - } - - default: { - return getCallingPackage(); - } - } + return switch (Binder.getCallingUid()) { + case Process.ROOT_UID -> "root"; + case Process.SHELL_UID -> "com.android.shell"; + default -> getCallingPackage(); + }; } private static final class Arguments { @@ -2796,17 +2725,17 @@ public class SettingsProvider extends ContentProvider { public Arguments(Uri uri, String where, String[] whereArgs, boolean supportAll) { final int segmentSize = uri.getPathSegments().size(); switch (segmentSize) { - case 1: { + case 1 -> { if (where != null && (WHERE_PATTERN_WITH_PARAM_NO_BRACKETS.matcher(where).matches() - || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches()) + || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches()) && whereArgs.length == 1) { name = whereArgs[0]; table = computeTableForSetting(uri, name); return; } else if (where != null && (WHERE_PATTERN_NO_PARAM_NO_BRACKETS.matcher(where).matches() - || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) { + || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) { final int startIndex = Math.max(where.indexOf("'"), where.indexOf("\"")) + 1; final int endIndex = Math.max(where.lastIndexOf("'"), @@ -2819,15 +2748,14 @@ public class SettingsProvider extends ContentProvider { table = computeTableForSetting(uri, null); return; } - } break; - - case 2: { + } + case 2 -> { if (where == null && whereArgs == null) { name = uri.getPathSegments().get(1); table = computeTableForSetting(uri, name); return; } - } break; + } } EventLogTags.writeUnsupportedSettingsQuery( @@ -2960,6 +2888,7 @@ public class SettingsProvider extends ContentProvider { mBackupManager = new BackupManager(getContext()); } + @GuardedBy("mLock") private void generateUserKeyLocked(int userId) { // Generate a random key for each user used for creating a new ssaid. final byte[] keyBytes = new byte[32]; @@ -2983,6 +2912,7 @@ public class SettingsProvider extends ContentProvider { return ByteBuffer.allocate(4).putInt(data.length).array(); } + @GuardedBy("mLock") public Setting generateSsaidLocked(PackageInfo callingPkg, int userId) { // Read the user's key from the ssaid table. Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY); @@ -3044,6 +2974,7 @@ public class SettingsProvider extends ContentProvider { return getSettingLocked(SETTINGS_TYPE_SSAID, userId, uid); } + @GuardedBy("mLock") private void syncSsaidTableOnStartLocked() { // Verify that each user's packages and ssaid's are in sync. for (UserInfo user : mUserManager.getAliveUsers()) { @@ -3078,15 +3009,17 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") public List<String> getSettingsNamesLocked(int type, int userId) { final int key = makeKey(type, userId); - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState == null) { return new ArrayList<>(); } return settingsState.getSettingNamesLocked(); } + @GuardedBy("mLock") public SparseBooleanArray getKnownUsersLocked() { SparseBooleanArray users = new SparseBooleanArray(); for (int i = mSettingsStates.size()-1; i >= 0; i--) { @@ -3095,17 +3028,19 @@ public class SettingsProvider extends ContentProvider { return users; } + @GuardedBy("mLock") @Nullable public SettingsState getSettingsLocked(int type, int userId) { final int key = makeKey(type, userId); - return peekSettingsStateLocked(key); + return mSettingsStates.get(key); } - public boolean ensureSettingsForUserLocked(int userId) { + @GuardedBy("mLock") + public void ensureSettingsForUserLocked(int userId) { // First make sure this user actually exists. if (mUserManager.getUserInfo(userId) == null) { Slog.wtf(LOG_TAG, "Requested user " + userId + " does not exist"); - return false; + return; } // Migrate the setting for this user if needed. @@ -3143,9 +3078,9 @@ public class SettingsProvider extends ContentProvider { // Upgrade the settings to the latest version. UpgradeController upgrader = new UpgradeController(userId); upgrader.upgradeIfNeededLocked(); - return true; } + @GuardedBy("mLock") private void ensureSettingsStateLocked(int key) { if (mSettingsStates.get(key) == null) { final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key)); @@ -3155,6 +3090,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") public void removeUserStateLocked(int userId, boolean permanently) { // We always keep the global settings in memory. @@ -3166,12 +3102,7 @@ public class SettingsProvider extends ContentProvider { mSettingsStates.remove(systemKey); systemSettingsState.destroyLocked(null); } else { - systemSettingsState.destroyLocked(new Runnable() { - @Override - public void run() { - mSettingsStates.remove(systemKey); - } - }); + systemSettingsState.destroyLocked(() -> mSettingsStates.remove(systemKey)); } } @@ -3183,12 +3114,7 @@ public class SettingsProvider extends ContentProvider { mSettingsStates.remove(secureKey); secureSettingsState.destroyLocked(null); } else { - secureSettingsState.destroyLocked(new Runnable() { - @Override - public void run() { - mSettingsStates.remove(secureKey); - } - }); + secureSettingsState.destroyLocked(() -> mSettingsStates.remove(secureKey)); } } @@ -3200,12 +3126,7 @@ public class SettingsProvider extends ContentProvider { mSettingsStates.remove(ssaidKey); ssaidSettingsState.destroyLocked(null); } else { - ssaidSettingsState.destroyLocked(new Runnable() { - @Override - public void run() { - mSettingsStates.remove(ssaidKey); - } - }); + ssaidSettingsState.destroyLocked(() -> mSettingsStates.remove(ssaidKey)); } } @@ -3213,6 +3134,7 @@ public class SettingsProvider extends ContentProvider { mGenerationRegistry.onUserRemoved(userId); } + @GuardedBy("mLock") public boolean insertSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, String packageName, boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) { @@ -3220,6 +3142,7 @@ public class SettingsProvider extends ContentProvider { packageName, forceNotify, criticalSettings, overrideableByRestore); } + @GuardedBy("mLock") public boolean insertSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName, boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) { @@ -3232,7 +3155,7 @@ public class SettingsProvider extends ContentProvider { boolean success = false; boolean wasUnsetNonPredefinedSetting = false; - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) { wasUnsetNonPredefinedSetting = true; @@ -3264,9 +3187,10 @@ public class SettingsProvider extends ContentProvider { * Set Config Settings using consumed keyValues, returns true if the keyValues can be set, * false otherwise. */ + @GuardedBy("mLock") public boolean setConfigSettingsLocked(int key, String prefix, Map<String, String> keyValues, String packageName) { - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) { return false; @@ -3283,12 +3207,13 @@ public class SettingsProvider extends ContentProvider { return true; } + @GuardedBy("mLock") public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify, Set<String> criticalSettings) { final int key = makeKey(type, userId); boolean success = false; - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { success = settingsState.deleteSettingLocked(name); } @@ -3306,13 +3231,14 @@ public class SettingsProvider extends ContentProvider { return success; } + @GuardedBy("mLock") public boolean updateSettingLocked(int type, int userId, String name, String value, String tag, boolean makeDefault, String packageName, boolean forceNotify, Set<String> criticalSettings) { final int key = makeKey(type, userId); boolean success = false; - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState != null) { success = settingsState.updateSettingLocked(name, value, tag, makeDefault, packageName); @@ -3331,10 +3257,11 @@ public class SettingsProvider extends ContentProvider { return success; } + @GuardedBy("mLock") public Setting getSettingLocked(int type, int userId, String name) { final int key = makeKey(type, userId); - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState == null) { return null; } @@ -3352,16 +3279,18 @@ public class SettingsProvider extends ContentProvider { return Global.SECURE_FRP_MODE.equals(setting.getName()); } + @GuardedBy("mLock") public boolean resetSettingsLocked(int type, int userId, String packageName, int mode, String tag) { return resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/ null); } + @GuardedBy("mLock") public boolean resetSettingsLocked(int type, int userId, String packageName, int mode, String tag, @Nullable String prefix) { final int key = makeKey(type, userId); - SettingsState settingsState = peekSettingsStateLocked(key); + SettingsState settingsState = mSettingsStates.get(key); if (settingsState == null) { return false; } @@ -3369,7 +3298,7 @@ public class SettingsProvider extends ContentProvider { boolean success = false; banConfigurationIfNecessary(type, prefix, settingsState); switch (mode) { - case Settings.RESET_MODE_PACKAGE_DEFAULTS: { + case Settings.RESET_MODE_PACKAGE_DEFAULTS -> { for (String name : settingsState.getSettingNamesLocked()) { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); @@ -3389,9 +3318,8 @@ public class SettingsProvider extends ContentProvider { success = true; } } - } break; - - case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: { + } + case Settings.RESET_MODE_UNTRUSTED_DEFAULTS -> { for (String name : settingsState.getSettingNamesLocked()) { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); @@ -3411,9 +3339,8 @@ public class SettingsProvider extends ContentProvider { success = true; } } - } break; - - case Settings.RESET_MODE_UNTRUSTED_CHANGES: { + } + case Settings.RESET_MODE_UNTRUSTED_CHANGES -> { for (String name : settingsState.getSettingNamesLocked()) { boolean someSettingChanged = false; Setting setting = settingsState.getSettingLocked(name); @@ -3439,9 +3366,8 @@ public class SettingsProvider extends ContentProvider { success = true; } } - } break; - - case Settings.RESET_MODE_TRUSTED_DEFAULTS: { + } + case Settings.RESET_MODE_TRUSTED_DEFAULTS -> { for (String name : settingsState.getSettingNamesLocked()) { Setting setting = settingsState.getSettingLocked(name); boolean someSettingChanged = false; @@ -3464,11 +3390,12 @@ public class SettingsProvider extends ContentProvider { success = true; } } - } break; + } } return success; } + @GuardedBy("mLock") public void removeSettingsForPackageLocked(String packageName, int userId) { // Global and secure settings are signature protected. Apps signed // by the platform certificate are generally not uninstalled and @@ -3482,6 +3409,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") public void onUidRemovedLocked(int uid) { final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, UserHandle.getUserId(uid)); @@ -3490,19 +3418,7 @@ public class SettingsProvider extends ContentProvider { } } - @Nullable - private SettingsState peekSettingsStateLocked(int key) { - SettingsState settingsState = mSettingsStates.get(key); - if (settingsState != null) { - return settingsState; - } - - if (!ensureSettingsForUserLocked(getUserIdFromKey(key))) { - return null; - } - return mSettingsStates.get(key); - } - + @GuardedBy("mLock") private void migrateAllLegacySettingsIfNeededLocked() { final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM); File globalFile = getSettingsFile(key); @@ -3538,6 +3454,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") private void migrateLegacySettingsForUserIfNeededLocked(int userId) { // Every user has secure settings and if no file we need to migrate. final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId); @@ -3552,6 +3469,7 @@ public class SettingsProvider extends ContentProvider { migrateLegacySettingsForUserLocked(dbHelper, database, userId); } + @GuardedBy("mLock") private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper, SQLiteDatabase database, int userId) { // Move over the system settings. @@ -3596,6 +3514,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") private void migrateLegacySettingsLocked(SettingsState settingsState, SQLiteDatabase database, String table) { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); @@ -3630,7 +3549,7 @@ public class SettingsProvider extends ContentProvider { } } - @GuardedBy("secureSettings.mLock") + @GuardedBy("mLock") private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) { Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID); @@ -3706,6 +3625,7 @@ public class SettingsProvider extends ContentProvider { name, type, changeType); } + @GuardedBy("mLock") private void notifyForConfigSettingsChangeLocked(int key, String prefix, List<String> changedSettings) { @@ -3787,30 +3707,18 @@ public class SettingsProvider extends ContentProvider { } } - private File getSettingsFile(int key) { - if (isConfigSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_CONFIG); - } else if (isGlobalSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_GLOBAL); - } else if (isSystemSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_SYSTEM); - } else if (isSecureSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_SECURE); - } else if (isSsaidSettingsKey(key)) { - final int userId = getUserIdFromKey(key); - return new File(Environment.getUserSystemDirectory(userId), - SETTINGS_FILE_SSAID); - } else { - throw new IllegalArgumentException("Invalid settings key:" + key); - } + private static File getSettingsFile(int key) { + final int userId = getUserIdFromKey(key); + final int type = getTypeFromKey(key); + final File userSystemDirectory = Environment.getUserSystemDirectory(userId); + return switch (type) { + case SETTINGS_TYPE_CONFIG -> new File(userSystemDirectory, SETTINGS_FILE_CONFIG); + case SETTINGS_TYPE_GLOBAL -> new File(userSystemDirectory, SETTINGS_FILE_GLOBAL); + case SETTINGS_TYPE_SYSTEM -> new File(userSystemDirectory, SETTINGS_FILE_SYSTEM); + case SETTINGS_TYPE_SECURE -> new File(userSystemDirectory, SETTINGS_FILE_SECURE); + case SETTINGS_TYPE_SSAID -> new File(userSystemDirectory, SETTINGS_FILE_SSAID); + default -> throw new IllegalArgumentException("Invalid settings key:" + key); + }; } private Uri getNotificationUriFor(int key, String name) { @@ -3833,14 +3741,11 @@ public class SettingsProvider extends ContentProvider { private int getMaxBytesPerPackageForType(int type) { switch (type) { - case SETTINGS_TYPE_CONFIG: - case SETTINGS_TYPE_GLOBAL: - case SETTINGS_TYPE_SECURE: - case SETTINGS_TYPE_SSAID: { + case SETTINGS_TYPE_CONFIG, SETTINGS_TYPE_GLOBAL, SETTINGS_TYPE_SECURE, + SETTINGS_TYPE_SSAID -> { return SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED; } - - default: { + default -> { return SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED; } } @@ -3857,7 +3762,7 @@ public class SettingsProvider extends ContentProvider { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_NOTIFY_URI_CHANGED: { + case MSG_NOTIFY_URI_CHANGED -> { final int userId = msg.arg1; Uri uri = (Uri) msg.obj; try { @@ -3868,12 +3773,11 @@ public class SettingsProvider extends ContentProvider { if (DEBUG) { Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri); } - } break; - - case MSG_NOTIFY_DATA_CHANGED: { + } + case MSG_NOTIFY_DATA_CHANGED -> { mBackupManager.dataChanged(); scheduleWriteFallbackFilesJob(); - } break; + } } } } @@ -3887,6 +3791,7 @@ public class SettingsProvider extends ContentProvider { mUserId = userId; } + @GuardedBy("mLock") public void upgradeIfNeededLocked() { // The version of all settings for a user is the same (all users have secure). SettingsState secureSettings = getSettingsLocked( @@ -3944,18 +3849,22 @@ public class SettingsProvider extends ContentProvider { systemSettings.setVersionLocked(newVersion); } + @GuardedBy("mLock") private SettingsState getGlobalSettingsLocked() { return getSettingsLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM); } + @GuardedBy("mLock") private SettingsState getSecureSettingsLocked(int userId) { return getSettingsLocked(SETTINGS_TYPE_SECURE, userId); } + @GuardedBy("mLock") private SettingsState getSsaidSettingsLocked(int userId) { return getSettingsLocked(SETTINGS_TYPE_SSAID, userId); } + @GuardedBy("mLock") private SettingsState getSystemSettingsLocked(int userId) { return getSettingsLocked(SETTINGS_TYPE_SYSTEM, userId); } @@ -5399,7 +5308,7 @@ public class SettingsProvider extends ContentProvider { // next version step. // If this is a new profile, check if a secure setting exists for the // owner of the profile and use that value for the work profile. - int owningId = resolveOwningUserIdForSecureSettingLocked(userId, + int owningId = resolveOwningUserIdForSecureSetting(userId, NOTIFICATION_BUBBLES); Setting previous = getGlobalSettingsLocked() .getSettingLocked("notification_bubbles"); @@ -6068,18 +5977,22 @@ public class SettingsProvider extends ContentProvider { return currentVersion; } + @GuardedBy("mLock") private void initGlobalSettingsDefaultValLocked(String key, boolean val) { initGlobalSettingsDefaultValLocked(key, val ? "1" : "0"); } + @GuardedBy("mLock") private void initGlobalSettingsDefaultValLocked(String key, int val) { initGlobalSettingsDefaultValLocked(key, String.valueOf(val)); } + @GuardedBy("mLock") private void initGlobalSettingsDefaultValLocked(String key, long val) { initGlobalSettingsDefaultValLocked(key, String.valueOf(val)); } + @GuardedBy("mLock") private void initGlobalSettingsDefaultValLocked(String key, String val) { final SettingsState globalSettings = getGlobalSettingsLocked(); Setting currentSetting = globalSettings.getSettingLocked(key); @@ -6198,6 +6111,7 @@ public class SettingsProvider extends ContentProvider { } } + @GuardedBy("mLock") private void ensureLegacyDefaultValueAndSystemSetUpdatedLocked(SettingsState settings, int userId) { List<String> names = settings.getSettingNamesLocked(); diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig index 0f55f35adc4e..eadcd7c27a18 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 8841967b1535..bcf1535b94fa 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 211af908a877..05675289db64 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -31,6 +31,13 @@ flag { } 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" description: "Enables the scene container framework go/flexiglass." 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 6c4b695ed709..af35ea44322f 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/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt new file mode 100644 index 000000000000..d005413fcbcf --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt @@ -0,0 +1,20 @@ +package com.android.compose.animation.scene + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import kotlinx.coroutines.CoroutineScope + +interface GestureHandler { + val draggable: DraggableHandler + val nestedScroll: NestedScrollHandler +} + +interface DraggableHandler { + suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) + fun onDelta(pixels: Float) + suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) +} + +interface NestedScrollHandler { + val connection: NestedScrollConnection +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 3fd6828fca6b..9c799b282571 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.State @@ -100,3 +101,19 @@ private class SceneScopeImpl( MovableElement(layoutImpl, scene, key, modifier, content) } } + +/** The destination scene when swiping up or left from [upOrLeft]. */ +internal fun Scene.upOrLeft(orientation: Orientation): SceneKey? { + return when (orientation) { + Orientation.Vertical -> userActions[Swipe.Up] + Orientation.Horizontal -> userActions[Swipe.Left] + } +} + +/** The destination scene when swiping down or right from [downOrRight]. */ +internal fun Scene.downOrRight(orientation: Orientation): SceneKey? { + return when (orientation) { + Orientation.Vertical -> userActions[Swipe.Down] + Orientation.Horizontal -> userActions[Swipe.Right] + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 4952270cb5f2..a40b29991877 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -17,6 +17,7 @@ package com.android.compose.animation.scene import androidx.activity.compose.BackHandler +import androidx.annotation.VisibleForTesting import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable @@ -39,7 +40,8 @@ import androidx.compose.ui.unit.IntSize import com.android.compose.ui.util.fastForEach import kotlinx.coroutines.channels.Channel -internal class SceneTransitionLayoutImpl( +@VisibleForTesting +class SceneTransitionLayoutImpl( onChangeScene: (SceneKey) -> Unit, builder: SceneTransitionLayoutScope.() -> Unit, transitions: SceneTransitions, @@ -60,7 +62,7 @@ internal class SceneTransitionLayoutImpl( * The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have * any scene configured or right before the first measure pass of the layout. */ - internal var size by mutableStateOf(IntSize.Zero) + @VisibleForTesting var size by mutableStateOf(IntSize.Zero) init { setScenes(builder) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index 1cbfe3057ff0..6496507218a5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -16,10 +16,10 @@ package com.android.compose.animation.scene +import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring -import androidx.compose.foundation.gestures.DraggableState import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable import androidx.compose.foundation.gestures.rememberDraggableState @@ -34,7 +34,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection @@ -51,612 +50,575 @@ internal fun Modifier.swipeToScene( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, ): Modifier { - val state = layoutImpl.state.transitionState - val currentScene = layoutImpl.scene(state.currentScene) - val transition = remember { - // Note that the currentScene here does not matter, it's only used for initializing the - // transition and will be replaced when a drag event starts. - SwipeTransition(initialScene = currentScene) - } - - val enabled = state == transition || currentScene.shouldEnableSwipes(orientation) - - // Immediately start the drag if this our [transition] is currently animating to a scene (i.e. - // the user released their input pointer after swiping in this orientation) and the user can't - // swipe in the other direction. - val startDragImmediately = - state == transition && - transition.isAnimatingOffset && - !currentScene.shouldEnableSwipes(orientation.opposite()) - - // The velocity threshold at which the intent of the user is to swipe up or down. It is the same - // as SwipeableV2Defaults.VelocityThreshold. - val velocityThreshold = with(LocalDensity.current) { 125.dp.toPx() } - - // The positional threshold at which the intent of the user is to swipe to the next scene. It is - // the same as SwipeableV2Defaults.PositionalThreshold. - val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() } - - val draggableState = rememberDraggableState { delta -> - onDrag(layoutImpl, transition, orientation, delta) - } - - return nestedScroll( - connection = - rememberSwipeToSceneNestedScrollConnection( - orientation = orientation, - coroutineScope = rememberCoroutineScope(), - draggableState = draggableState, - transition = transition, - layoutImpl = layoutImpl, - velocityThreshold = velocityThreshold, - positionalThreshold = positionalThreshold - ), + val gestureHandler = rememberSceneGestureHandler(layoutImpl, orientation) + + /** Whether swipe should be enabled in the given [orientation]. */ + fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean = + upOrLeft(orientation) != null || downOrRight(orientation) != null + + val currentScene = gestureHandler.currentScene + val canSwipe = currentScene.shouldEnableSwipes(orientation) + val canOppositeSwipe = + currentScene.shouldEnableSwipes( + when (orientation) { + Orientation.Vertical -> Orientation.Horizontal + Orientation.Horizontal -> Orientation.Vertical + } ) + + return nestedScroll(connection = gestureHandler.nestedScroll.connection) .draggable( - state = draggableState, + state = rememberDraggableState(onDelta = gestureHandler.draggable::onDelta), orientation = orientation, - enabled = enabled, - startDragImmediately = startDragImmediately, - onDragStarted = { onDragStarted(layoutImpl, transition, orientation) }, - onDragStopped = { velocity -> - onDragStopped( - layoutImpl = layoutImpl, - transition = transition, - velocity = velocity, - velocityThreshold = velocityThreshold, - positionalThreshold = positionalThreshold, - ) - }, + enabled = gestureHandler.isDrivingTransition || canSwipe, + // Immediately start the drag if this our [transition] is currently animating to a scene + // (i.e. the user released their input pointer after swiping in this orientation) and + // the user can't swipe in the other direction. + startDragImmediately = + gestureHandler.isDrivingTransition && + gestureHandler.isAnimatingOffset && + !canOppositeSwipe, + onDragStarted = gestureHandler.draggable::onDragStarted, + onDragStopped = gestureHandler.draggable::onDragStopped, ) } -private class SwipeTransition(initialScene: Scene) : TransitionState.Transition { - var _currentScene by mutableStateOf(initialScene) - override val currentScene: SceneKey - get() = _currentScene.key - - var _fromScene by mutableStateOf(initialScene) - override val fromScene: SceneKey - get() = _fromScene.key - - var _toScene by mutableStateOf(initialScene) - override val toScene: SceneKey - get() = _toScene.key - - override val progress: Float - get() { - val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset - if (distance == 0f) { - // This can happen only if fromScene == toScene. - error( - "Transition.progress should be called only when Transition.fromScene != " + - "Transition.toScene" - ) - } - return offset / distance +@Composable +private fun rememberSceneGestureHandler( + layoutImpl: SceneTransitionLayoutImpl, + orientation: Orientation, +): SceneGestureHandler { + val coroutineScope = rememberCoroutineScope() + + val gestureHandler = + remember(layoutImpl, orientation, coroutineScope) { + SceneGestureHandler(layoutImpl, orientation, coroutineScope) } - override val isUserInputDriven = true + // Make sure we reset the scroll connection when this handler is removed from composition + val connection = gestureHandler.nestedScroll.connection + DisposableEffect(connection) { onDispose { connection.reset() } } - /** The current offset caused by the drag gesture. */ - var dragOffset by mutableFloatStateOf(0f) + return gestureHandler +} + +@VisibleForTesting +class SceneGestureHandler( + private val layoutImpl: SceneTransitionLayoutImpl, + internal val orientation: Orientation, + private val coroutineScope: CoroutineScope, +) : GestureHandler { + override val draggable: DraggableHandler = SceneDraggableHandler(this) + + override val nestedScroll: SceneNestedScrollHandler = SceneNestedScrollHandler(this) + + private var transitionState + get() = layoutImpl.state.transitionState + set(value) { + layoutImpl.state.transitionState = value + } /** - * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture. + * The transition controlled by this gesture handler. It will be set as the [transitionState] in + * the [SceneTransitionLayoutImpl] whenever this handler is driving the current transition. + * + * Note: the initialScene here does not matter, it's only used for initializing the transition + * and will be replaced when a drag event starts. */ - var isAnimatingOffset by mutableStateOf(false) + private val swipeTransition = SwipeTransition(initialScene = currentScene) - /** The animatable used to animate the offset once the user lifted its finger. */ - val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold) + internal val currentScene: Scene + get() = layoutImpl.scene(transitionState.currentScene) - /** Job to check that there is at most one offset animation in progress. */ - private var offsetAnimationJob: Job? = null + internal val isDrivingTransition + get() = transitionState == swipeTransition - /** Ends any previous [offsetAnimationJob] and runs the new [job]. */ - fun startOffsetAnimation(job: () -> Job) { - stopOffsetAnimation() - offsetAnimationJob = job() - } + internal var isAnimatingOffset + get() = swipeTransition.isAnimatingOffset + private set(value) { + swipeTransition.isAnimatingOffset = value + } - /** Stops any ongoing offset animation. */ - fun stopOffsetAnimation() { - offsetAnimationJob?.cancel() - } + internal val swipeTransitionToScene + get() = swipeTransition._toScene - /** The absolute distance between [fromScene] and [toScene]. */ - var absoluteDistance = 0f + /** + * The velocity threshold at which the intent of the user is to swipe up or down. It is the same + * as SwipeableV2Defaults.VelocityThreshold. + */ + @VisibleForTesting val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() } /** - * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above - * or to the left of [toScene]. + * The positional threshold at which the intent of the user is to swipe to the next scene. It is + * the same as SwipeableV2Defaults.PositionalThreshold. */ - var _distance by mutableFloatStateOf(0f) - val distance: Float - get() = _distance -} + private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() } + + internal fun onDragStarted() { + if (isDrivingTransition) { + // This [transition] was already driving the animation: simply take over it. + if (isAnimatingOffset) { + // Stop animating and start from where the current offset. Setting the animation job + // to `null` will effectively cancel the animation. + swipeTransition.stopOffsetAnimation() + swipeTransition.dragOffset = swipeTransition.offsetAnimatable.value + } -/** The destination scene when swiping up or left from [this@upOrLeft]. */ -private fun Scene.upOrLeft(orientation: Orientation): SceneKey? { - return when (orientation) { - Orientation.Vertical -> userActions[Swipe.Up] - Orientation.Horizontal -> userActions[Swipe.Left] - } -} + return + } -/** The destination scene when swiping down or right from [this@downOrRight]. */ -private fun Scene.downOrRight(orientation: Orientation): SceneKey? { - return when (orientation) { - Orientation.Vertical -> userActions[Swipe.Down] - Orientation.Horizontal -> userActions[Swipe.Right] - } -} + // TODO(b/290184746): Better handle interruptions here if state != idle. -/** Whether swipe should be enabled in the given [orientation]. */ -private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean { - return upOrLeft(orientation) != null || downOrRight(orientation) != null -} + val fromScene = currentScene -private fun Orientation.opposite(): Orientation { - return when (this) { - Orientation.Vertical -> Orientation.Horizontal - Orientation.Horizontal -> Orientation.Vertical - } -} + swipeTransition._currentScene = fromScene + swipeTransition._fromScene = fromScene -private fun onDragStarted( - layoutImpl: SceneTransitionLayoutImpl, - transition: SwipeTransition, - orientation: Orientation, -) { - if (layoutImpl.state.transitionState == transition) { - // This [transition] was already driving the animation: simply take over it. - if (transition.isAnimatingOffset) { - // Stop animating and start from where the current offset. Setting the animation job to - // `null` will effectively cancel the animation. - transition.stopOffsetAnimation() - transition.dragOffset = transition.offsetAnimatable.value - } + // We don't know where we are transitioning to yet given that the drag just started, so set + // it to fromScene, which will effectively be treated the same as Idle(fromScene). + swipeTransition._toScene = fromScene - return - } + swipeTransition.stopOffsetAnimation() + swipeTransition.dragOffset = 0f - // TODO(b/290184746): Better handle interruptions here if state != idle. + // Use the layout size in the swipe orientation for swipe distance. + // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, + // we will also have to make sure that we correctly handle overscroll. + swipeTransition.absoluteDistance = + when (orientation) { + Orientation.Horizontal -> layoutImpl.size.width + Orientation.Vertical -> layoutImpl.size.height + }.toFloat() - val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene) + if (swipeTransition.absoluteDistance > 0f) { + transitionState = swipeTransition + } + } - transition._currentScene = fromScene - transition._fromScene = fromScene + internal fun onDrag(delta: Float) { + swipeTransition.dragOffset += delta - // We don't know where we are transitioning to yet given that the drag just started, so set it - // to fromScene, which will effectively be treated the same as Idle(fromScene). - transition._toScene = fromScene + // First check transition.fromScene should be changed for the case where the user quickly + // swiped twice in a row to accelerate the transition and go from A => B then B => C really + // fast. + maybeHandleAcceleratedSwipe() - transition.stopOffsetAnimation() - transition.dragOffset = 0f + val offset = swipeTransition.dragOffset + val fromScene = swipeTransition._fromScene - // Use the layout size in the swipe orientation for swipe distance. - // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we - // will also have to make sure that we correctly handle overscroll. - transition.absoluteDistance = - when (orientation) { - Orientation.Horizontal -> layoutImpl.size.width - Orientation.Vertical -> layoutImpl.size.height - }.toFloat() + // Compute the target scene depending on the current offset. + val target = fromScene.findTargetSceneAndDistance(offset) - if (transition.absoluteDistance > 0f) { - layoutImpl.state.transitionState = transition - } -} + if (swipeTransition._toScene.key != target.sceneKey) { + swipeTransition._toScene = layoutImpl.scenes.getValue(target.sceneKey) + } -private fun onDrag( - layoutImpl: SceneTransitionLayoutImpl, - transition: SwipeTransition, - orientation: Orientation, - delta: Float, -) { - transition.dragOffset += delta + if (swipeTransition._distance != target.distance) { + swipeTransition._distance = target.distance + } + } - // First check transition.fromScene should be changed for the case where the user quickly swiped - // twice in a row to accelerate the transition and go from A => B then B => C really fast. - maybeHandleAcceleratedSwipe(transition, orientation) + /** + * Change fromScene in the case where the user quickly swiped multiple times in the same + * direction to accelerate the transition from A => B then B => C. + */ + private fun maybeHandleAcceleratedSwipe() { + val toScene = swipeTransition._toScene + val fromScene = swipeTransition._fromScene - val offset = transition.dragOffset - val fromScene = transition._fromScene + // If the swipe was not committed, don't do anything. + if (fromScene == toScene || swipeTransition._currentScene != toScene) { + return + } - // Compute the target scene depending on the current offset. - val target = fromScene.findTargetSceneAndDistance(orientation, offset, layoutImpl) + // If the offset is past the distance then let's change fromScene so that the user can swipe + // to the next screen or go back to the previous one. + val offset = swipeTransition.dragOffset + val absoluteDistance = swipeTransition.absoluteDistance + if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) { + swipeTransition.dragOffset += absoluteDistance + swipeTransition._fromScene = toScene + } else if ( + offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key + ) { + swipeTransition.dragOffset -= absoluteDistance + swipeTransition._fromScene = toScene + } - if (transition._toScene.key != target.sceneKey) { - transition._toScene = layoutImpl.scenes.getValue(target.sceneKey) + // Important note: toScene and distance will be updated right after this function is called, + // using fromScene and dragOffset. } - if (transition._distance != target.distance) { - transition._distance = target.distance + private class TargetScene( + val sceneKey: SceneKey, + val distance: Float, + ) + + private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene { + val maxDistance = + when (orientation) { + Orientation.Horizontal -> layoutImpl.size.width + Orientation.Vertical -> layoutImpl.size.height + }.toFloat() + + val upOrLeft = upOrLeft(orientation) + val downOrRight = downOrRight(orientation) + + // Compute the target scene depending on the current offset. + return when { + directionOffset < 0f && upOrLeft != null -> { + TargetScene( + sceneKey = upOrLeft, + distance = -maxDistance, + ) + } + directionOffset > 0f && downOrRight != null -> { + TargetScene( + sceneKey = downOrRight, + distance = maxDistance, + ) + } + else -> { + TargetScene( + sceneKey = key, + distance = 0f, + ) + } + } } -} -/** - * Change fromScene in the case where the user quickly swiped multiple times in the same direction - * to accelerate the transition from A => B then B => C. - */ -private fun maybeHandleAcceleratedSwipe( - transition: SwipeTransition, - orientation: Orientation, -) { - val toScene = transition._toScene - val fromScene = transition._fromScene + internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) { + // The state was changed since the drag started; don't do anything. + if (!isDrivingTransition) { + return + } - // If the swipe was not committed, don't do anything. - if (fromScene == toScene || transition._currentScene != toScene) { - return - } + // We were not animating. + if (swipeTransition._fromScene == swipeTransition._toScene) { + transitionState = TransitionState.Idle(swipeTransition._fromScene.key) + return + } - // If the offset is past the distance then let's change fromScene so that the user can swipe to - // the next screen or go back to the previous one. - val offset = transition.dragOffset - val absoluteDistance = transition.absoluteDistance - if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) { - transition.dragOffset += absoluteDistance - transition._fromScene = toScene - } else if (offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key) { - transition.dragOffset -= absoluteDistance - transition._fromScene = toScene - } + // Compute the destination scene (and therefore offset) to settle in. + val targetOffset: Float + val targetScene: Scene + val offset = swipeTransition.dragOffset + val distance = swipeTransition.distance + if ( + canChangeScene && + shouldCommitSwipe( + offset, + distance, + velocity, + wasCommitted = swipeTransition._currentScene == swipeTransition._toScene, + ) + ) { + targetOffset = distance + targetScene = swipeTransition._toScene + } else { + targetOffset = 0f + targetScene = swipeTransition._fromScene + } - // Important note: toScene and distance will be updated right after this function is called, - // using fromScene and dragOffset. -} + // If the effective current scene changed, it should be reflected right now in the current + // scene state, even before the settle animation is ongoing. That way all the swipeables and + // back handlers will be refreshed and the user can for instance quickly swipe vertically + // from A => B then horizontally from B => C, or swipe from A => B then immediately go back + // B => A. + if (targetScene != swipeTransition._currentScene) { + swipeTransition._currentScene = targetScene + layoutImpl.onChangeScene(targetScene.key) + } -private data class TargetScene( - val sceneKey: SceneKey, - val distance: Float, -) + animateOffset( + initialVelocity = velocity, + targetOffset = targetOffset, + targetScene = targetScene.key + ) + } -private fun Scene.findTargetSceneAndDistance( - orientation: Orientation, - directionOffset: Float, - layoutImpl: SceneTransitionLayoutImpl, -): TargetScene { - val maxDistance = - when (orientation) { - Orientation.Horizontal -> layoutImpl.size.width - Orientation.Vertical -> layoutImpl.size.height - }.toFloat() - - val upOrLeft = upOrLeft(orientation) - val downOrRight = downOrRight(orientation) - - // Compute the target scene depending on the current offset. - return when { - directionOffset < 0f && upOrLeft != null -> { - TargetScene( - sceneKey = upOrLeft, - distance = -maxDistance, - ) + /** + * Whether the swipe to the target scene should be committed or not. This is inspired by + * SwipeableV2.computeTarget(). + */ + private fun shouldCommitSwipe( + offset: Float, + distance: Float, + velocity: Float, + wasCommitted: Boolean, + ): Boolean { + fun isCloserToTarget(): Boolean { + return (offset - distance).absoluteValue < offset.absoluteValue } - directionOffset > 0f && downOrRight != null -> { - TargetScene( - sceneKey = downOrRight, - distance = maxDistance, - ) + + // Swiping up or left. + if (distance < 0f) { + return if (offset > 0f || velocity >= velocityThreshold) { + false + } else { + velocity <= -velocityThreshold || + (offset <= -positionalThreshold && !wasCommitted) || + isCloserToTarget() + } } - else -> { - TargetScene( - sceneKey = key, - distance = 0f, - ) + + // Swiping down or right. + return if (offset < 0f || velocity <= -velocityThreshold) { + false + } else { + velocity >= velocityThreshold || + (offset >= positionalThreshold && !wasCommitted) || + isCloserToTarget() } } -} -private fun CoroutineScope.onDragStopped( - layoutImpl: SceneTransitionLayoutImpl, - transition: SwipeTransition, - velocity: Float, - velocityThreshold: Float, - positionalThreshold: Float, - canChangeScene: Boolean = true, -) { - // The state was changed since the drag started; don't do anything. - if (layoutImpl.state.transitionState != transition) { - return - } + private fun animateOffset( + initialVelocity: Float, + targetOffset: Float, + targetScene: SceneKey, + ) { + swipeTransition.startOffsetAnimation { + coroutineScope + .launch { + if (!isAnimatingOffset) { + swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset) + } + isAnimatingOffset = true + + swipeTransition.offsetAnimatable.animateTo( + targetOffset, + // TODO(b/290184746): Make this spring spec configurable. + spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = OffsetVisibilityThreshold + ), + initialVelocity = initialVelocity, + ) - // We were not animating. - if (transition._fromScene == transition._toScene) { - layoutImpl.state.transitionState = TransitionState.Idle(transition._fromScene.key) - return + // Now that the animation is done, the state should be idle. Note that if the + // state was changed since this animation started, some external code changed it + // and we shouldn't do anything here. Note also that this job will be cancelled + // in the case where the user intercepts this swipe. + if (isDrivingTransition) { + transitionState = TransitionState.Idle(targetScene) + } + } + .also { it.invokeOnCompletion { isAnimatingOffset = false } } + } } - // Compute the destination scene (and therefore offset) to settle in. - val targetScene: Scene - val targetOffset: Float - val offset = transition.dragOffset - val distance = transition.distance - if ( - canChangeScene && - shouldCommitSwipe( - offset, - distance, - velocity, - velocityThreshold, - positionalThreshold, - wasCommitted = transition._currentScene == transition._toScene, - ) - ) { - targetOffset = distance - targetScene = transition._toScene - } else { - targetOffset = 0f - targetScene = transition._fromScene - } + internal fun animateOverscroll(velocity: Velocity): Velocity { + val velocityAmount = + when (orientation) { + Orientation.Vertical -> velocity.y + Orientation.Horizontal -> velocity.x + } - // If the effective current scene changed, it should be reflected right now in the current scene - // state, even before the settle animation is ongoing. That way all the swipeables and back - // handlers will be refreshed and the user can for instance quickly swipe vertically from A => B - // then horizontally from B => C, or swipe from A => B then immediately go back B => A. - if (targetScene != transition._currentScene) { - transition._currentScene = targetScene - layoutImpl.onChangeScene(targetScene.key) - } + if (velocityAmount == 0f) { + // There is no remaining velocity + return Velocity.Zero + } - animateOffset( - transition = transition, - layoutImpl = layoutImpl, - initialVelocity = velocity, - targetOffset = targetOffset, - targetScene = targetScene.key - ) -} + val fromScene = currentScene + val target = fromScene.findTargetSceneAndDistance(velocityAmount) + val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key -/** - * Whether the swipe to the target scene should be committed or not. This is inspired by - * SwipeableV2.computeTarget(). - */ -private fun shouldCommitSwipe( - offset: Float, - distance: Float, - velocity: Float, - velocityThreshold: Float, - positionalThreshold: Float, - wasCommitted: Boolean, -): Boolean { - fun isCloserToTarget(): Boolean { - return (offset - distance).absoluteValue < offset.absoluteValue + if (!isValidTarget || isDrivingTransition) { + // We have not found a valid target or we are already in a transition + return Velocity.Zero + } + + swipeTransition._currentScene = fromScene + swipeTransition._fromScene = fromScene + swipeTransition._toScene = layoutImpl.scene(target.sceneKey) + swipeTransition._distance = target.distance + swipeTransition.absoluteDistance = target.distance.absoluteValue + swipeTransition.stopOffsetAnimation() + swipeTransition.dragOffset = 0f + + transitionState = swipeTransition + + animateOffset( + initialVelocity = velocityAmount, + targetOffset = 0f, + targetScene = fromScene.key + ) + + // The animateOffset animation consumes any remaining velocity. + return velocity } - // Swiping up or left. - if (distance < 0f) { - return if (offset > 0f || velocity >= velocityThreshold) { - false - } else { - velocity <= -velocityThreshold || - (offset <= -positionalThreshold && !wasCommitted) || - isCloserToTarget() + private class SwipeTransition(initialScene: Scene) : TransitionState.Transition { + var _currentScene by mutableStateOf(initialScene) + override val currentScene: SceneKey + get() = _currentScene.key + + var _fromScene by mutableStateOf(initialScene) + override val fromScene: SceneKey + get() = _fromScene.key + + var _toScene by mutableStateOf(initialScene) + override val toScene: SceneKey + get() = _toScene.key + + override val progress: Float + get() { + val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset + if (distance == 0f) { + // This can happen only if fromScene == toScene. + error( + "Transition.progress should be called only when Transition.fromScene != " + + "Transition.toScene" + ) + } + return offset / distance + } + + override val isUserInputDriven = true + + /** The current offset caused by the drag gesture. */ + var dragOffset by mutableFloatStateOf(0f) + + /** + * Whether the offset is animated (the user lifted their finger) or if it is driven by + * gesture. + */ + var isAnimatingOffset by mutableStateOf(false) + + /** The animatable used to animate the offset once the user lifted its finger. */ + val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold) + + /** Job to check that there is at most one offset animation in progress. */ + private var offsetAnimationJob: Job? = null + + /** Ends any previous [offsetAnimationJob] and runs the new [job]. */ + fun startOffsetAnimation(job: () -> Job) { + stopOffsetAnimation() + offsetAnimationJob = job() } - } - // Swiping down or right. - return if (offset < 0f || velocity <= -velocityThreshold) { - false - } else { - velocity >= velocityThreshold || - (offset >= positionalThreshold && !wasCommitted) || - isCloserToTarget() + /** Stops any ongoing offset animation. */ + fun stopOffsetAnimation() { + offsetAnimationJob?.cancel() + } + + /** The absolute distance between [fromScene] and [toScene]. */ + var absoluteDistance = 0f + + /** + * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is + * above or to the left of [toScene]. + */ + var _distance by mutableFloatStateOf(0f) + val distance: Float + get() = _distance } } -private fun CoroutineScope.animateOffset( - transition: SwipeTransition, - layoutImpl: SceneTransitionLayoutImpl, - initialVelocity: Float, - targetOffset: Float, - targetScene: SceneKey, -) { - transition.startOffsetAnimation { - launch { - if (!transition.isAnimatingOffset) { - transition.offsetAnimatable.snapTo(transition.dragOffset) - } - transition.isAnimatingOffset = true - - transition.offsetAnimatable.animateTo( - targetOffset, - // TODO(b/290184746): Make this spring spec configurable. - spring( - stiffness = Spring.StiffnessMediumLow, - visibilityThreshold = OffsetVisibilityThreshold - ), - initialVelocity = initialVelocity, - ) +private class SceneDraggableHandler( + private val gestureHandler: SceneGestureHandler, +) : DraggableHandler { + override suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) { + gestureHandler.onDragStarted() + } - // Now that the animation is done, the state should be idle. Note that if the state - // was changed since this animation started, some external code changed it and we - // shouldn't do anything here. Note also that this job will be cancelled in the case - // where the user intercepts this swipe. - if (layoutImpl.state.transitionState == transition) { - layoutImpl.state.transitionState = TransitionState.Idle(targetScene) - } - } - .also { it.invokeOnCompletion { transition.isAnimatingOffset = false } } + override fun onDelta(pixels: Float) { + gestureHandler.onDrag(delta = pixels) + } + + override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) { + gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true) } } -private fun CoroutineScope.animateOverscroll( - layoutImpl: SceneTransitionLayoutImpl, - transition: SwipeTransition, - velocity: Velocity, - orientation: Orientation, -): Velocity { - val velocityAmount = - when (orientation) { - Orientation.Vertical -> velocity.y - Orientation.Horizontal -> velocity.x +@VisibleForTesting +class SceneNestedScrollHandler( + private val gestureHandler: SceneGestureHandler, +) : NestedScrollHandler { + override val connection: PriorityPostNestedScrollConnection = nestedScrollConnection() + + private fun Offset.toAmount() = + when (gestureHandler.orientation) { + Orientation.Horizontal -> x + Orientation.Vertical -> y } - if (velocityAmount == 0f) { - // There is no remaining velocity - return Velocity.Zero - } + private fun Velocity.toAmount() = + when (gestureHandler.orientation) { + Orientation.Horizontal -> x + Orientation.Vertical -> y + } - val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene) - val target = fromScene.findTargetSceneAndDistance(orientation, velocityAmount, layoutImpl) - val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key + private fun Float.toOffset() = + when (gestureHandler.orientation) { + Orientation.Horizontal -> Offset(x = this, y = 0f) + Orientation.Vertical -> Offset(x = 0f, y = this) + } - if (!isValidTarget || layoutImpl.state.transitionState == transition) { - // We have not found a valid target or we are already in a transition - return Velocity.Zero - } + private fun nestedScrollConnection(): PriorityPostNestedScrollConnection { + // The next potential scene is calculated during the canStart + var nextScene: SceneKey? = null - transition._currentScene = fromScene - transition._fromScene = fromScene - transition._toScene = layoutImpl.scene(target.sceneKey) - transition._distance = target.distance - transition.absoluteDistance = target.distance.absoluteValue - transition.stopOffsetAnimation() - transition.dragOffset = 0f - - layoutImpl.state.transitionState = transition - - animateOffset( - transition = transition, - layoutImpl = layoutImpl, - initialVelocity = velocityAmount, - targetOffset = 0f, - targetScene = fromScene.key - ) + // This is the scene on which we will have priority during the scroll gesture. + var priorityScene: SceneKey? = null - // The animateOffset animation consumes any remaining velocity. - return velocity -} + // If we performed a long gesture before entering priority mode, we would have to avoid + // moving on to the next scene. + var gestureStartedOnNestedChild = false -/** - * The number of pixels below which there won't be a visible difference in the transition and from - * which the animation can stop. - */ -private const val OffsetVisibilityThreshold = 0.5f + return PriorityPostNestedScrollConnection( + canStart = { offsetAvailable, offsetBeforeStart -> + val amount = offsetAvailable.toAmount() + if (amount == 0f) return@PriorityPostNestedScrollConnection false -@Composable -private fun rememberSwipeToSceneNestedScrollConnection( - orientation: Orientation, - coroutineScope: CoroutineScope, - draggableState: DraggableState, - transition: SwipeTransition, - layoutImpl: SceneTransitionLayoutImpl, - velocityThreshold: Float, - positionalThreshold: Float, -): PriorityPostNestedScrollConnection { - val density = LocalDensity.current - val scrollConnection = - remember( - orientation, - coroutineScope, - draggableState, - transition, - layoutImpl, - velocityThreshold, - positionalThreshold, - density, - ) { - fun Offset.toAmount() = - when (orientation) { - Orientation.Horizontal -> x - Orientation.Vertical -> y - } + gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero - fun Velocity.toAmount() = - when (orientation) { - Orientation.Horizontal -> x - Orientation.Vertical -> y - } + val fromScene = gestureHandler.currentScene + nextScene = + when { + amount < 0f -> fromScene.upOrLeft(gestureHandler.orientation) + amount > 0f -> fromScene.downOrRight(gestureHandler.orientation) + else -> null + } - fun Float.toOffset() = - when (orientation) { - Orientation.Horizontal -> Offset(x = this, y = 0f) - Orientation.Vertical -> Offset(x = 0f, y = this) - } + nextScene != null + }, + canContinueScroll = { priorityScene == gestureHandler.swipeTransitionToScene.key }, + onStart = { + priorityScene = nextScene + gestureHandler.onDragStarted() + }, + onScroll = { offsetAvailable -> + val amount = offsetAvailable.toAmount() - // The next potential scene is calculated during the canStart - var nextScene: SceneKey? = null - - // This is the scene on which we will have priority during the scroll gesture. - var priorityScene: SceneKey? = null - - // If we performed a long gesture before entering priority mode, we would have to avoid - // moving on to the next scene. - var gestureStartedOnNestedChild = false - - PriorityPostNestedScrollConnection( - canStart = { offsetAvailable, offsetBeforeStart -> - val amount = offsetAvailable.toAmount() - if (amount == 0f) return@PriorityPostNestedScrollConnection false - - gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero - - val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene) - nextScene = - when { - amount < 0f -> fromScene.upOrLeft(orientation) - amount > 0f -> fromScene.downOrRight(orientation) - else -> null - } - - nextScene != null - }, - canContinueScroll = { priorityScene == transition._toScene.key }, - onStart = { - priorityScene = nextScene - onDragStarted(layoutImpl, transition, orientation) - }, - onScroll = { offsetAvailable -> - val amount = offsetAvailable.toAmount() - - // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture - // is initiated in a nested child. - - // Appends a new coroutine to attempt to drag by [amount] px. In this case we - // are assuming that the [coroutineScope] is tied to the main thread and that - // calls to [launch] are therefore queued. - coroutineScope.launch { draggableState.drag { dragBy(amount) } } - - amount.toOffset() - }, - onStop = { velocityAvailable -> - priorityScene = null - - coroutineScope.onDragStopped( - layoutImpl = layoutImpl, - transition = transition, - velocity = velocityAvailable.toAmount(), - velocityThreshold = velocityThreshold, - positionalThreshold = positionalThreshold, - canChangeScene = !gestureStartedOnNestedChild - ) + // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is + // initiated in a nested child. + gestureHandler.onDrag(amount) - // The onDragStopped animation consumes any remaining velocity. - velocityAvailable - }, - onPostFling = { velocityAvailable -> - // If there is any velocity left, we can try running an overscroll animation - // between scenes. - coroutineScope.animateOverscroll( - layoutImpl = layoutImpl, - transition = transition, - velocity = velocityAvailable, - orientation = orientation - ) - }, - ) - } - DisposableEffect(scrollConnection) { - onDispose { - coroutineScope.launch { - // This should ensure that the draggableState is in a consistent state and that it - // does not cause any unexpected behavior. - scrollConnection.reset() - } - } + amount.toOffset() + }, + onStop = { velocityAvailable -> + priorityScene = null + + gestureHandler.onDragStopped( + velocity = velocityAvailable.toAmount(), + canChangeScene = !gestureStartedOnNestedChild + ) + + // The onDragStopped animation consumes any remaining velocity. + velocityAvailable + }, + onPostFling = { velocityAvailable -> + // If there is any velocity left, we can try running an overscroll animation between + // scenes. + gestureHandler.animateOverscroll(velocity = velocityAvailable) + }, + ) } - return scrollConnection } + +/** + * The number of pixels below which there won't be a visible difference in the transition and from + * which the animation can stop. + */ +private const val OffsetVisibilityThreshold = 0.5f diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt new file mode 100644 index 000000000000..3e0f7ba1bf78 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -0,0 +1,284 @@ +package com.android.compose.animation.scene + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.Velocity +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.TransitionState.Idle +import com.android.compose.animation.scene.TransitionState.Transition +import com.android.compose.test.MonotonicClockTestScope +import com.android.compose.test.runMonotonicClockTest +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Test +import org.junit.runner.RunWith + +private const val SCREEN_SIZE = 100f + +@RunWith(AndroidJUnit4::class) +class SceneGestureHandlerTest { + private class TestGestureScope( + val coroutineScope: MonotonicClockTestScope, + ) { + private var internalCurrentScene: SceneKey by mutableStateOf(SceneA) + + private val layoutState: SceneTransitionLayoutState = + SceneTransitionLayoutState(internalCurrentScene) + + private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = { + scene( + key = SceneA, + userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC), + ) { + Text("SceneA") + } + scene(SceneB) { Text("SceneB") } + scene(SceneC) { Text("SceneC") } + } + + private val sceneGestureHandler = + SceneGestureHandler( + layoutImpl = + SceneTransitionLayoutImpl( + onChangeScene = { internalCurrentScene = it }, + builder = scenesBuilder, + transitions = EmptyTestTransitions, + state = layoutState, + density = Density(1f) + ) + .also { it.size = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) }, + orientation = Orientation.Vertical, + coroutineScope = coroutineScope, + ) + + val draggable = sceneGestureHandler.draggable + + val nestedScroll = sceneGestureHandler.nestedScroll.connection + + val velocityThreshold = sceneGestureHandler.velocityThreshold + + // 10% of the screen + val deltaInPixels10 = SCREEN_SIZE * 0.1f + + // Offset y: 10% of the screen + val offsetY10 = Offset(x = 0f, y = deltaInPixels10) + + val transitionState: TransitionState + get() = layoutState.transitionState + + fun advanceUntilIdle() { + coroutineScope.testScheduler.advanceUntilIdle() + } + + fun assertScene(currentScene: SceneKey, isIdle: Boolean) { + val idleMsg = if (isIdle) "MUST" else "MUST NOT" + assertWithMessage("transitionState $idleMsg be Idle") + .that(transitionState is Idle) + .isEqualTo(isIdle) + assertThat(transitionState.currentScene).isEqualTo(currentScene) + } + } + + @OptIn(ExperimentalTestApi::class) + private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) { + runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() } + } + + @Test + fun testPreconditions() = runGestureTest { assertScene(currentScene = SceneA, isIdle = true) } + + @Test + fun onDragStarted_shouldStartATransition() = runGestureTest { + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) + assertScene(currentScene = SceneA, isIdle = false) + } + + @Test + fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest { + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) + assertScene(currentScene = SceneA, isIdle = false) + val transition = transitionState as Transition + + draggable.onDelta(pixels = deltaInPixels10) + assertThat(transition.progress).isEqualTo(0.1f) + + draggable.onDelta(pixels = deltaInPixels10) + assertThat(transition.progress).isEqualTo(0.2f) + } + + @Test + fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest { + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) + assertScene(currentScene = SceneA, isIdle = false) + + draggable.onDelta(pixels = deltaInPixels10) + assertScene(currentScene = SceneA, isIdle = false) + + draggable.onDragStopped( + coroutineScope = coroutineScope, + velocity = velocityThreshold - 0.01f, + ) + assertScene(currentScene = SceneA, isIdle = false) + + // wait for the stop animation + advanceUntilIdle() + assertScene(currentScene = SceneA, isIdle = true) + } + + @Test + fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest { + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) + assertScene(currentScene = SceneA, isIdle = false) + + draggable.onDelta(pixels = deltaInPixels10) + assertScene(currentScene = SceneA, isIdle = false) + + draggable.onDragStopped( + coroutineScope = coroutineScope, + velocity = velocityThreshold, + ) + assertScene(currentScene = SceneC, isIdle = false) + + // wait for the stop animation + advanceUntilIdle() + assertScene(currentScene = SceneC, isIdle = true) + } + + @Test + fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest { + draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero) + assertScene(currentScene = SceneA, isIdle = false) + + draggable.onDragStopped(coroutineScope = coroutineScope, velocity = 0f) + assertScene(currentScene = SceneA, isIdle = true) + } + + @Test + fun onInitialPreScroll_doNotChangeState() = runGestureTest { + nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag) + assertScene(currentScene = SceneA, isIdle = true) + } + + @Test + fun onPostScrollWithNothingAvailable_doNotChangeState() = runGestureTest { + val consumed = + nestedScroll.onPostScroll( + consumed = Offset.Zero, + available = Offset.Zero, + source = NestedScrollSource.Drag + ) + + assertScene(currentScene = SceneA, isIdle = true) + assertThat(consumed).isEqualTo(Offset.Zero) + } + + @Test + fun onPostScrollWithSomethingAvailable_startSceneTransition() = runGestureTest { + val consumed = + nestedScroll.onPostScroll( + consumed = Offset.Zero, + available = offsetY10, + source = NestedScrollSource.Drag + ) + + assertScene(currentScene = SceneA, isIdle = false) + val transition = transitionState as Transition + assertThat(transition.progress).isEqualTo(0.1f) + assertThat(consumed).isEqualTo(offsetY10) + } + + private fun TestGestureScope.nestedScrollEvents( + available: Offset, + consumedByScroll: Offset = Offset.Zero, + ) { + val consumedByPreScroll = + nestedScroll.onPreScroll(available = available, source = NestedScrollSource.Drag) + val consumed = consumedByPreScroll + consumedByScroll + nestedScroll.onPostScroll( + consumed = consumed, + available = available - consumed, + source = NestedScrollSource.Drag + ) + } + + @Test + fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest { + nestedScrollEvents(available = offsetY10) + assertScene(currentScene = SceneA, isIdle = false) + + val transition = transitionState as Transition + assertThat(transition.progress).isEqualTo(0.1f) + + // start intercept preScroll + val consumed = + nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag) + assertThat(transition.progress).isEqualTo(0.2f) + + // do nothing on postScroll + nestedScroll.onPostScroll( + consumed = consumed, + available = Offset.Zero, + source = NestedScrollSource.Drag + ) + assertThat(transition.progress).isEqualTo(0.2f) + + nestedScrollEvents(available = offsetY10) + assertThat(transition.progress).isEqualTo(0.3f) + assertScene(currentScene = SceneA, isIdle = false) + } + + @Test + fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest { + nestedScrollEvents(available = offsetY10) + assertScene(currentScene = SceneA, isIdle = false) + + nestedScroll.onPreFling(available = Velocity.Zero) + assertScene(currentScene = SceneA, isIdle = false) + + // wait for the stop animation + advanceUntilIdle() + assertScene(currentScene = SceneA, isIdle = true) + } + + @Test + fun onPreFling_velocityAtLeastThreshold_goToNextScene() = runGestureTest { + nestedScrollEvents(available = offsetY10) + assertScene(currentScene = SceneA, isIdle = false) + + nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) + assertScene(currentScene = SceneC, isIdle = false) + + // wait for the stop animation + advanceUntilIdle() + assertScene(currentScene = SceneC, isIdle = true) + } + + @Test + fun scrollStartedInScene_doOverscrollAnimation() = runGestureTest { + // we started the scroll in the scene + nestedScrollEvents(available = offsetY10, consumedByScroll = offsetY10) + + // now we can intercept the scroll events + nestedScrollEvents(available = offsetY10) + assertScene(currentScene = SceneA, isIdle = false) + + nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) + // should start an overscroll animation (the gesture started in the scene) + assertScene(currentScene = SceneA, isIdle = false) + + // wait for the stop animation + advanceUntilIdle() + assertScene(currentScene = SceneA, isIdle = true) + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt new file mode 100644 index 000000000000..cb122dc8e25e --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt @@ -0,0 +1,27 @@ +package com.android.compose.test + +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.TestMonotonicFrameClock +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext + +/** + * This method creates a [CoroutineScope] that can be used in animations created in a composable + * function. + * + * The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle. + */ +@ExperimentalTestApi +fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest { + // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock. + withContext(TestMonotonicFrameClock(this)) { + MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block() + } +} + +class MonotonicClockTestScope( + coroutineScope: CoroutineScope, + val testScheduler: TestCoroutineScheduler +) : CoroutineScope by coroutineScope diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java index a9d2ee3cdfe6..403c7c500426 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java @@ -19,7 +19,6 @@ package com.android.systemui.plugins; import android.graphics.Color; import android.graphics.Rect; import android.view.View; -import android.widget.ImageView; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.annotations.DependsOn; @@ -51,21 +50,11 @@ public interface DarkIconDispatcher { void addDarkReceiver(DarkReceiver receiver); /** - * Adds a receiver to receive callbacks onDarkChanged - */ - void addDarkReceiver(ImageView imageView); - - /** * Must have been previously been added through one of the addDarkReceive methods above. */ void removeDarkReceiver(DarkReceiver object); /** - * Must have been previously been added through one of the addDarkReceive methods above. - */ - void removeDarkReceiver(ImageView object); - - /** * Used to reapply darkness on an object, must have previously been added through * addDarkReceiver. */ @@ -104,8 +93,8 @@ public interface DarkIconDispatcher { } /** - * @return true if more than half of the view area are in any of the given - * areas, false otherwise + * @return true if more than half of the view's area is in any of the given area Rects, false + * otherwise */ static boolean isInAreas(Collection<Rect> areas, View view) { if (areas.isEmpty()) { @@ -120,9 +109,40 @@ public interface DarkIconDispatcher { } /** - * @return true if more than half of the view area are in area, false + * @return true if more than half of the viewBounds are in any of the given area Rects, false * otherwise */ + static boolean isInAreas(Collection<Rect> areas, Rect viewBounds) { + if (areas.isEmpty()) { + return true; + } + for (Rect area : areas) { + if (isInArea(area, viewBounds)) { + return true; + } + } + return false; + } + + /** @return true if more than half of the viewBounds are in the area Rect, false otherwise */ + static boolean isInArea(Rect area, Rect viewBounds) { + if (area.isEmpty()) { + return true; + } + sTmpRect.set(area); + int left = viewBounds.left; + int width = viewBounds.width(); + + int intersectStart = Math.max(left, area.left); + int intersectEnd = Math.min(left + width, area.right); + int intersectAmount = Math.max(0, intersectEnd - intersectStart); + + boolean coversFullStatusBar = area.top <= 0; + boolean majorityOfWidth = 2 * intersectAmount > width; + return majorityOfWidth && coversFullStatusBar; + } + + /** @return true if more than half of the view's area is in the area Rect, false otherwise */ static boolean isInArea(Rect area, View view) { if (area.isEmpty()) { return true; diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 5a70b79cb176..452bc317e2d5 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 8957903b29d4..7e03bd9d4325 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" diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index 587caaf3ecf3..db526b187d38 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 7a6d29ab3c1f..9e2ebf60fe8f 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/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 000000000000..f219cece3ff8 --- /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 fe607e16661c..7b67e3f3c920 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 @@ -48,6 +48,9 @@ abstract class SystemUnfoldSharedModule { 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 000000000000..63ea1165ee04 --- /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-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt index fae9fec0c26d..a263361c0ab2 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt @@ -39,6 +39,12 @@ abstract class FlagsModule { @IntoSet abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition + @Binds + @IntoSet + abstract fun bindsNotOccludedCondition( + impl: NotOccludedCondition + ): ConditionalRestarter.Condition + @Module companion object { @JvmStatic diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt index 7aacb4efba8e..9684d5e38fa7 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt @@ -39,6 +39,12 @@ abstract class FlagsModule { @IntoSet abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition + @Binds + @IntoSet + abstract fun bindsNotOccludedCondition( + impl: NotOccludedCondition + ): ConditionalRestarter.Condition + @Module companion object { @JvmStatic diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java index fd84543ee50b..494efb7d3f87 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 b573fadea15e..0f5f869cba5d 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/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java index 0bf50693344a..7d73896bb370 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/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt new file mode 100644 index 000000000000..8d5b84fea9ab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt @@ -0,0 +1,60 @@ +/* + * 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.common.ui + +import android.content.Context +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt +import androidx.annotation.DimenRes +import com.android.settingslib.Utils +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged +import com.android.systemui.statusbar.policy.onThemeChanged +import com.android.systemui.util.kotlin.emitOnStart +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** Configuration-aware-state-tracking utilities. */ +class ConfigurationState +@Inject +constructor( + private val configurationController: ConfigurationController, + @Application private val context: Context, +) { + /** + * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device + * configuration. + * + * @see android.content.res.Resources.getDimensionPixelSize + */ + fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int> { + return configurationController.onDensityOrFontScaleChanged.emitOnStart().map { + context.resources.getDimensionPixelSize(id) + } + } + + /** + * Returns a [Flow] that emits a color that is kept in sync with the device theme. + * + * @see Utils.getColorAttrDefaultColor + */ + fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int> { + return configurationController.onThemeChanged.emitOnStart().map { + Utils.getColorAttrDefaultColor(context, id, defaultValue) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt new file mode 100644 index 000000000000..b0e69317e0ee --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt @@ -0,0 +1,21 @@ +/* + * 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.common.ui.data + +import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule +import dagger.Module + +@Module(includes = [ConfigurationRepositoryModule::class]) object CommonUiDataLayerModule diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt index b8de8d8226a6..e44927432719 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt @@ -13,18 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License */ +@file:OptIn(ExperimentalCoroutinesApi::class) package com.android.systemui.common.ui.data.repository import android.content.Context import android.content.res.Configuration import android.view.DisplayInfo +import androidx.annotation.DimenRes import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.wrapper.DisplayUtilsWrapper +import dagger.Binds +import dagger.Module import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -48,7 +52,6 @@ interface ConfigurationRepository { fun getDimensionPixelSize(id: Int): Int } -@ExperimentalCoroutinesApi @SysUISingleton class ConfigurationRepositoryImpl @Inject @@ -119,7 +122,12 @@ constructor( return 1f } - override fun getDimensionPixelSize(id: Int): Int { + override fun getDimensionPixelSize(@DimenRes id: Int): Int { return context.resources.getDimensionPixelSize(id) } } + +@Module +interface ConfigurationRepositoryModule { + @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository +} 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 53b6879db3d7..485e5122cfad 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/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 5d6949b3e87f..d8ff535ffd78 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 @@ -344,11 +343,6 @@ abstract class SystemUICoreStartableModule { @Binds @IntoMap - @ClassKey(LockscreenWallpaper::class) - abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable - - @Binds - @IntoMap @ClassKey(ScrimController::class) abstract fun bindScrimController(impl: ScrimController): CoreStartable diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index f0d7592d8940..04b2852db9e2 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -41,7 +41,7 @@ import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule; import com.android.systemui.bouncer.ui.BouncerViewModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; -import com.android.systemui.common.ui.data.repository.CommonRepositoryModule; +import com.android.systemui.common.ui.data.CommonUiDataLayerModule; import com.android.systemui.communal.dagger.CommunalModule; import com.android.systemui.complication.dagger.ComplicationComponent; import com.android.systemui.controls.dagger.ControlsModule; @@ -170,7 +170,7 @@ import javax.inject.Named; BouncerViewModule.class, ClipboardOverlayModule.class, ClockRegistryModule.class, - CommonRepositoryModule.class, + CommonUiDataLayerModule.class, CommunalModule.class, ConnectivityModule.class, ControlsModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 4f166858faf8..3e2ecee1fe3e 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -47,6 +47,8 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn /** Provides a [Flow] of [Display] as returned by [DisplayManager]. */ @@ -54,10 +56,7 @@ interface DisplayRepository { /** Display change event indicating a change to the given displayId has occurred. */ val displayChangeEvent: Flow<Int> - /** - * Provides a nullable set of displays. Updates when new displays have been added or removed but - * not when a display's info has changed. - */ + /** Provides the current set of displays. */ val displays: Flow<Set<Display>> /** @@ -112,10 +111,6 @@ constructor( trySend(DisplayEvent.Changed(displayId)) } } - // Triggers an initial event when subscribed. This is needed to avoid getDisplays to - // be called when this class is constructed, but only when someone subscribes to - // this flow. - trySend(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) displayManager.registerDisplayListener( callback, backgroundHandler, @@ -125,6 +120,7 @@ constructor( ) awaitClose { displayManager.unregisterDisplayListener(callback) } } + .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) } .flowOn(backgroundCoroutineDispatcher) override val displayChangeEvent: Flow<Int> = @@ -134,13 +130,9 @@ constructor( allDisplayEvents .map { getDisplays() } .flowOn(backgroundCoroutineDispatcher) - .stateIn( - applicationScope, - started = SharingStarted.WhileSubscribed(), - // To avoid getting displays on this object construction, they are get after the - // first event. allDisplayEvents emits a changed event when we subscribe to it. - initialValue = emptySet() - ) + .shareIn(applicationScope, started = SharingStarted.WhileSubscribed(), replay = 1) + + override val displays: Flow<Set<Display>> = enabledDisplays private fun getDisplays(): Set<Display> = traceSection("DisplayRepository#getDisplays()") { @@ -148,8 +140,6 @@ constructor( } /** Propagate to the listeners only enabled displays */ - override val displays: Flow<Set<Display>> = enabledDisplays - private val enabledDisplayIds: Flow<Set<Int>> = enabledDisplays .map { enabledDisplaysSet -> enabledDisplaysSet.map { it.displayId }.toSet() } @@ -251,6 +241,7 @@ constructor( val id = pendingDisplayIds.maxOrNull() ?: return@map null object : DisplayRepository.PendingDisplay { override val id = id + override suspend fun enable() { traceSection("DisplayRepository#enable($id)") { if (DEBUG) { @@ -303,8 +294,12 @@ constructor( private interface DisplayConnectionListener : DisplayListener { override fun onDisplayConnected(id: Int) {} + override fun onDisplayDisconnected(id: Int) {} + override fun onDisplayAdded(id: Int) {} + override fun onDisplayRemoved(id: Int) {} + override fun onDisplayChanged(id: Int) {} } 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 d19efbdd8026..87b0f0177d0d 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 @@ -19,8 +19,12 @@ import android.content.Context import android.os.Bundle import android.view.View import android.widget.TextView +import androidx.core.view.updatePadding +import com.android.systemui.biometrics.Utils import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog +import com.android.systemui.statusbar.policy.ConfigurationController +import kotlin.math.max /** * Dialog used to decide what to do with a connected display. @@ -32,8 +36,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 @@ -56,5 +61,23 @@ class MirroringConfirmationDialog( onCancelMirroring.onClick(null) } } + setupInsets() + } + + private fun setupInsets() { + // This avoids overlap between dialog content and navigation bars. + requireViewById<View>(R.id.cd_bottom_sheet).apply { + val navbarInsets = Utils.getNavbarInsets(context) + val defaultDialogBottomInset = + context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding) + // we only care about the bottom inset as in all other configuration where navigations + // are in other display sides there is no overlap with the dialog. + updatePadding(bottom = max(navbarInsets.bottom, defaultDialogBottomInset)) + } + } + + override fun onConfigurationChanged() { + super.onConfigurationChanged() + setupInsets() } } 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 86ef439361b0..91f535df586a 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/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt index 83c239f169ef..93b00eef08b5 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt @@ -19,13 +19,17 @@ package com.android.systemui.flags import android.util.Log import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.flags.ConditionalRestarter.Condition import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch /** Restarts the process after all passed in [Condition]s are true. */ @@ -39,7 +43,6 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, ) : Restarter { - private var restartJob: Job? = null private var pendingReason = "" private var androidRestartRequested = false @@ -57,17 +60,19 @@ constructor( private fun scheduleRestart(reason: String = "") { pendingReason = if (reason.isEmpty()) pendingReason else reason - if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) { - if (restartJob == null) { - restartJob = - applicationScope.launch(backgroundDispatcher) { + applicationScope.launch(backgroundDispatcher) { + combine(conditions.map { condition -> condition.canRestartNow }) { it.all { it } } + // Once all conditions are met, delay. + .transformLatest { allConditionsMet -> + if (allConditionsMet) { delay(TimeUnit.SECONDS.toMillis(restartDelaySec)) - restartNow() + emit(Unit) } - } - } else { - restartJob?.cancel() - restartJob = null + } + // Once we have successfully delayed _once_, continue to restart. + .first() + + restartNow() } } @@ -94,7 +99,7 @@ constructor( * multiple [Condition]s are being checked. If any one [Condition] returns false, all the * [Condition]s will need to be rechecked on the next restart attempt. */ - fun canRestartNow(retryFn: () -> Unit): Boolean + val canRestartNow: Flow<Boolean> } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index c91c9ac119af..10fac4d328e0 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 diff --git a/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt new file mode 100644 index 000000000000..f5b30cf4b54f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt @@ -0,0 +1,24 @@ +package com.android.systemui.flags + +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import dagger.Lazy +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */ +class NotOccludedCondition +@Inject +constructor( + private val keyguardTransitionInteractorLazy: Lazy<KeyguardTransitionInteractor>, +) : ConditionalRestarter.Condition { + + override val canRestartNow: Flow<Boolean> + get() { + return keyguardTransitionInteractorLazy + .get() + .transitionValue(KeyguardState.OCCLUDED) + .map { it == 0f } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt index 3120638cb17f..dc08570447a5 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt @@ -16,34 +16,34 @@ package com.android.systemui.flags +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.statusbar.policy.BatteryController +import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose /** Returns true when the device is plugged in. */ class PluggedInCondition @Inject constructor( - private val batteryController: BatteryController, + private val batteryControllerLazy: Lazy<BatteryController>, ) : ConditionalRestarter.Condition { - var listenersAdded = false - var retryFn: (() -> Unit)? = null - - val batteryCallback = - object : BatteryController.BatteryStateChangeCallback { - override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { - retryFn?.invoke() + override val canRestartNow = conflatedCallbackFlow { + val batteryCallback = + object : BatteryController.BatteryStateChangeCallback { + override fun onBatteryLevelChanged( + level: Int, + pluggedIn: Boolean, + charging: Boolean + ) { + trySend(pluggedIn) + } } - } - - override fun canRestartNow(retryFn: () -> Unit): Boolean { - if (!listenersAdded) { - listenersAdded = true - batteryController.addCallback(batteryCallback) - } + batteryControllerLazy.get().addCallback(batteryCallback) - this.retryFn = retryFn + trySend(batteryControllerLazy.get().isPluggedIn) - return batteryController.isPluggedIn + awaitClose { batteryControllerLazy.get().removeCallback(batteryCallback) } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt index 7ccc26c063d3..4a5cc641dcf1 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 000000000000..2aa397f3e744 --- /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/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt index 49e61afbdcd6..3c9bc368e852 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt @@ -16,34 +16,19 @@ package com.android.systemui.flags -import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.power.domain.interactor.PowerInteractor +import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.flow.Flow /** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */ class ScreenIdleCondition @Inject -constructor( - private val wakefulnessLifecycle: WakefulnessLifecycle, -) : ConditionalRestarter.Condition { +constructor(private val powerInteractorLazy: Lazy<PowerInteractor>) : + ConditionalRestarter.Condition { - var listenersAdded = false - var retryFn: (() -> Unit)? = null - - val observer = - object : WakefulnessLifecycle.Observer { - override fun onFinishedGoingToSleep() { - retryFn?.invoke() - } - } - - override fun canRestartNow(retryFn: () -> Unit): Boolean { - if (!listenersAdded) { - listenersAdded = true - wakefulnessLifecycle.addObserver(observer) + override val canRestartNow: Flow<Boolean> + get() { + return powerInteractorLazy.get().isAsleep } - - this.retryFn = retryFn - - return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 47798955b43d..2b1cdc2ff026 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 0bac40bcbcc1..c8c06ae65d45 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 e893c6305dee..4e6a872cb3f7 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 d06f31fed8db..7e360cfad66d 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 fbe26de4e9ba..b0b857729c77 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 000000000000..023d16cab013 --- /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 000000000000..da74f2fa061e --- /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 078318196883..0e95be20d059 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 ce8b79cd9e38..19621199aad5 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -23,8 +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,20 +34,48 @@ 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) + } + } + + /** + * Request to log that the permission request was displayed. + * + * @param hostUid The UID of the package that initiates MediaProjection. + */ + fun notifyPermissionRequestDisplayed(hostUid: Int) { + try { + service.notifyPermissionRequestDisplayed(hostUid) + } catch (e: RemoteException) { + Log.e(TAG, "Error notifying server of projection displayed", e) + } } - fun notifyPermissionRequestDisplayed() { - notifyToServer(METRICS_STATE_PERMISSION_REQUEST_DISPLAYED, SessionCreationSource.UNKNOWN) + /** + * Request to log that the app selector was displayed. + * + * @param hostUid The UID of the package that initiates MediaProjection. + */ + fun notifyAppSelectorDisplayed(hostUid: Int) { + try { + service.notifyAppSelectorDisplayed(hostUid) + } catch (e: RemoteException) { + Log.e(TAG, "Error notifying server of app selector displayed", e) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index 0bbcfd9de24c..04d55665f572 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -92,6 +92,7 @@ class MediaProjectionAppSelectorActivity( component = componentFactory.create( hostUserHandle = hostUserHandle, + hostUid = hostUid, callingPackage = callingPackage, view = this, resultHandler = this, @@ -305,6 +306,17 @@ class MediaProjectionAppSelectorActivity( ) } + private val hostUid: Int + get() { + if (!intent.hasExtra(EXTRA_HOST_APP_UID)) { + error( + "MediaProjectionAppSelectorActivity should be provided with " + + "$EXTRA_HOST_APP_UID extra" + ) + } + return intent.getIntExtra(EXTRA_HOST_APP_UID, /* defaultValue= */ -1) + } + companion object { const val TAG = "MediaProjectionAppSelectorActivity" @@ -315,8 +327,16 @@ class MediaProjectionAppSelectorActivity( */ const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver" - /** UID of the app that originally launched the media projection flow (host app user) */ + /** + * User on the device that launched the media projection flow. (Primary, Secondary, Guest, + * Work, etc) + */ const val EXTRA_HOST_APP_USER_HANDLE = "launched_from_user_handle" + /** + * The kernel user-ID that has been assigned to the app that originally launched the media + * projection flow. + */ + const val EXTRA_HOST_APP_UID = "launched_from_host_uid" const val KEY_CAPTURE_TARGET = "capture_region" /** Set up intent for the [ChooserActivity] */ diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 8c6f307c84d6..d2471225a093 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -57,6 +57,8 @@ import kotlinx.coroutines.SupervisorJob @Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle +@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUid + @Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope @Module( @@ -143,6 +145,7 @@ interface MediaProjectionAppSelectorComponent { /** Create a factory to inject the activity into the graph */ fun create( @BindsInstance @HostUserHandle hostUserHandle: UserHandle, + @BindsInstance @HostUid hostUid: Int, @BindsInstance @MediaProjectionAppSelector callingPackage: String?, @BindsInstance view: MediaProjectionAppSelectorView, @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt index 69132d3662d4..67ef119a7428 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -47,13 +47,14 @@ constructor( private val thumbnailLoader: RecentTaskThumbnailLoader, @MediaProjectionAppSelector private val isFirstStart: Boolean, private val logger: MediaProjectionMetricsLogger, + @HostUid private val hostUid: Int, ) { fun init() { // Only log during the first start of the app selector. // Don't log when the app selector restarts due to a config change. if (isFirstStart) { - logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + logger.notifyAppSelectorDisplayed(hostUid) } scope.launch { 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 2d830d311562..f7cc589db068 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); @@ -251,7 +252,7 @@ public class MediaProjectionPermissionActivity extends Activity mDialog.show(); if (savedInstanceState == null) { - mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(); + mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid); } } @@ -329,6 +330,9 @@ public class MediaProjectionPermissionActivity extends Activity projection.asBinder()); intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE, getHostUserHandle()); + intent.putExtra( + MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, + getLaunchedFromUid()); intent.putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, mReviewGrantedConsentRequired); intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index e27a59c6fb21..f37f58db05f8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -47,6 +47,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -71,6 +72,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> private final FeatureFlags mFlags; private final PanelInteractor mPanelInteractor; private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; + private final UserContextProvider mUserContextProvider; private long mMillisUntilFinished = 0; @@ -91,7 +93,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> KeyguardStateController keyguardStateController, DialogLaunchAnimator dialogLaunchAnimator, PanelInteractor panelInteractor, - MediaProjectionMetricsLogger mediaProjectionMetricsLogger + MediaProjectionMetricsLogger mediaProjectionMetricsLogger, + UserContextProvider userContextProvider ) { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -103,6 +106,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> mDialogLaunchAnimator = dialogLaunchAnimator; mPanelInteractor = panelInteractor; mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger; + mUserContextProvider = userContextProvider; } @Override @@ -195,7 +199,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> dialog.show(); } - mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(); + int uid = mUserContextProvider.getUserContext().getUserId(); + mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid); return false; }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt index 9d100728e643..905d8effdadf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.base.actions +import android.app.PendingIntent import android.content.Intent import android.view.View import com.android.internal.jank.InteractionJankMonitor @@ -29,7 +30,7 @@ import javax.inject.Inject * dismissing and tile from-view animations. */ @SysUISingleton -class QSTileIntentUserActionHandler +class QSTileIntentUserInputHandler @Inject constructor(private val activityStarter: ActivityStarter) { @@ -43,4 +44,19 @@ constructor(private val activityStarter: ActivityStarter) { } activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController) } + + // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939 + fun handle(view: View?, pendingIntent: PendingIntent) { + if (!pendingIntent.isActivity) { + return + } + val animationController: ActivityLaunchAnimator.Controller? = + view?.let { + ActivityLaunchAnimator.Controller.fromView( + it, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, + ) + } + activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt new file mode 100644 index 000000000000..4f25d3cde6c3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt @@ -0,0 +1,33 @@ +/* + * 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.qs.tiles.base.interactor + +/** Event that triggers data update */ +sealed interface DataUpdateTrigger { + /** + * State update is requested in a response to a user action. + * - [action] is the action that happened + * - [tileData] is the data state of the tile when that action took place + */ + class UserInput<T>(val input: QSTileInput<T>) : DataUpdateTrigger + + /** Force update current state. This is passed when the view needs a new state to show */ + data object ForceUpdate : DataUpdateTrigger + + /** The data is requested loaded for the first time */ + data object InitialRequest : DataUpdateTrigger +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt index 7a22e3cf8bc8..a3e38500123e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.tiles.base.interactor -import com.android.systemui.qs.tiles.viewmodel.QSTileState import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -29,14 +28,20 @@ import kotlinx.coroutines.flow.Flow interface QSTileDataInteractor<DATA_TYPE> { /** - * Returns the data to be mapped to [QSTileState]. Make sure to start the flow [Flow.onStart] - * with the current state to update the tile as soon as possible. + * Returns a data flow scoped to the user. This means the subscription will live when the tile + * is listened for the [userId]. It's cancelled when the tile is not listened or the user + * changes. + * + * You can use [Flow.onStart] on the returned to update the tile with the current state as soon + * as possible. */ - fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<DATA_TYPE> + fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE> /** - * Returns tile availability - whether this device currently supports this tile. Make sure to - * start the flow [Flow.onStart] with the current state to update the tile as soon as possible. + * Returns tile availability - whether this device currently supports this tile. + * + * You can use [Flow.onStart] on the returned to update the tile with the current state as soon + * as possible. */ - fun availability(): Flow<Boolean> + fun availability(userId: Int): Flow<Boolean> } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt index 0aa6b0be5485..102fa3641ff4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt @@ -16,7 +16,11 @@ package com.android.systemui.qs.tiles.base.interactor -data class QSTileDataRequest( +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction + +/** @see QSTileUserActionInteractor.handleInput */ +data class QSTileInput<T>( val userId: Int, - val trigger: StateUpdateTrigger, + val action: QSTileUserAction, + val data: T, ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt index 14fc639c8aa8..09d7a1f7142d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt @@ -17,13 +17,14 @@ package com.android.systemui.qs.tiles.base.interactor import android.annotation.WorkerThread -import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction interface QSTileUserActionInteractor<DATA_TYPE> { - /** - * Processes user input based on [userAction] and [currentData]. It's safe to run long running - * computations inside this function in this. + * Processes user input based on [QSTileInput.userId], [QSTileInput.action], and + * [QSTileInput.data]. It's guaranteed that [QSTileInput.userId] is the same as the id passed to + * [QSTileDataInteractor] to get [QSTileInput.data]. + * + * It's safe to run long running computations inside this function in this. */ - @WorkerThread suspend fun handleInput(userAction: QSTileUserAction, currentData: DATA_TYPE) + @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt index f8de36563dd2..4dc1c82c5282 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt @@ -24,7 +24,7 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.QSTilesLogBuffers import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.statusbar.StatusBarState @@ -128,10 +128,21 @@ constructor( ) } + fun logForceUpdate(tileSpec: TileSpec) { + tileSpec + .getLogBuffer() + .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data force update" }) + } + + fun logInitialRequest(tileSpec: TileSpec) { + tileSpec + .getLogBuffer() + .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data initial update" }) + } + /** Tracks state changes based on the data and trigger event. */ fun <T> logStateUpdate( tileSpec: TileSpec, - trigger: StateUpdateTrigger, tileState: QSTileState, data: T, ) { @@ -141,11 +152,10 @@ constructor( tileSpec.getLogTag(), LogLevel.DEBUG, { - str1 = trigger.toLogString() - str2 = tileState.toLogString() - str3 = data.toString().take(DATA_MAX_LENGTH) + str1 = tileState.toLogString() + str2 = data.toString().take(DATA_MAX_LENGTH) }, - { "tile state update: trigger=$str1, state=$str2, data=$str3" } + { "tile state update: state=$str1, data=$str2" } ) } @@ -162,11 +172,11 @@ constructor( } } - private fun StateUpdateTrigger.toLogString(): String = + private fun DataUpdateTrigger.toLogString(): String = when (this) { - is StateUpdateTrigger.ForceUpdate -> "force" - is StateUpdateTrigger.InitialRequest -> "init" - is StateUpdateTrigger.UserAction<*> -> action.toLogString() + is DataUpdateTrigger.ForceUpdate -> "force" + is DataUpdateTrigger.InitialRequest -> "init" + is DataUpdateTrigger.UserInput<*> -> input.action.toLogString() } private fun QSTileUserAction.toLogString(): String = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt index 2114751ef57b..14de5eb8be7f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt @@ -22,12 +22,12 @@ import com.android.internal.util.Preconditions import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor -import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor -import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle @@ -35,26 +35,31 @@ import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel -import com.android.systemui.util.kotlin.sample +import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.throttle +import com.android.systemui.util.time.SystemClock import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn @@ -66,6 +71,7 @@ import kotlinx.coroutines.flow.stateIn * * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class. */ +@OptIn(ExperimentalCoroutinesApi::class) class BaseQSTileViewModel<DATA_TYPE> @VisibleForTesting constructor( @@ -74,9 +80,11 @@ constructor( private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, private val mapper: QSTileDataToStateMapper<DATA_TYPE>, private val disabledByPolicyInteractor: DisabledByPolicyInteractor, + userRepository: UserRepository, private val falsingManager: FalsingManager, private val qsTileAnalytics: QSTileAnalytics, private val qsTileLogger: QSTileLogger, + private val systemClock: SystemClock, private val backgroundDispatcher: CoroutineDispatcher, private val tileScope: CoroutineScope, ) : QSTileViewModel { @@ -88,9 +96,11 @@ constructor( @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>, disabledByPolicyInteractor: DisabledByPolicyInteractor, + userRepository: UserRepository, falsingManager: FalsingManager, qsTileAnalytics: QSTileAnalytics, qsTileLogger: QSTileLogger, + systemClock: SystemClock, @Background backgroundDispatcher: CoroutineDispatcher, ) : this( config, @@ -98,28 +108,30 @@ constructor( tileDataInteractor, mapper, disabledByPolicyInteractor, + userRepository, falsingManager, qsTileAnalytics, qsTileLogger, + systemClock, backgroundDispatcher, CoroutineScope(SupervisorJob()) ) + private val userIds: MutableStateFlow<Int> = + MutableStateFlow(userRepository.getSelectedUserInfo().id) private val userInputs: MutableSharedFlow<QSTileUserAction> = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - private val userIds: MutableSharedFlow<Int> = - MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) private val forceUpdates: MutableSharedFlow<Unit> = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) private val spec get() = config.tileSpec - private lateinit var tileData: SharedFlow<DataWithTrigger<DATA_TYPE>> + private lateinit var tileData: SharedFlow<DATA_TYPE> override lateinit var state: SharedFlow<QSTileState> override val isAvailable: StateFlow<Boolean> = - tileDataInteractor - .availability() + userIds + .flatMapLatest { tileDataInteractor.availability(it) } .flowOn(backgroundDispatcher) .stateIn( tileScope, @@ -162,15 +174,9 @@ constructor( tileData = createTileDataFlow() state = tileData - // TODO(b/299908705): log data and corresponding tile state - .map { dataWithTrigger -> - mapper.map(config, dataWithTrigger.data).also { state -> - qsTileLogger.logStateUpdate( - spec, - dataWithTrigger.trigger, - state, - dataWithTrigger.data - ) + .map { data -> + mapper.map(config, data).also { state -> + qsTileLogger.logStateUpdate(spec, state, data) } } .flowOn(backgroundDispatcher) @@ -188,88 +194,99 @@ constructor( currentLifeState = lifecycle } - private fun createTileDataFlow(): SharedFlow<DataWithTrigger<DATA_TYPE>> = + private fun createTileDataFlow(): SharedFlow<DATA_TYPE> = userIds .flatMapLatest { userId -> - merge( - userInputFlow(userId), - forceUpdates.map { StateUpdateTrigger.ForceUpdate }, - ) - .onStart { emit(StateUpdateTrigger.InitialRequest) } - .map { trigger -> QSTileDataRequest(userId, trigger) } - } - .flatMapLatest { request -> - // 1) get an updated data source - // 2) process user input, possibly triggering new data to be emitted - // This handles the case when the data isn't buffered in the interactor - // TODO(b/299908705): Log events that trigger data flow to update - val dataFlow = tileDataInteractor.tileData(request) - if (request.trigger is StateUpdateTrigger.UserAction<*>) { - userActionInteractor.handleInput( - request.trigger.action, - request.trigger.tileData as DATA_TYPE, - ) - } - dataFlow.map { DataWithTrigger(it, request.trigger) } + val updateTriggers = + merge( + userInputFlow(userId), + forceUpdates + .map { DataUpdateTrigger.ForceUpdate } + .onEach { qsTileLogger.logForceUpdate(spec) }, + ) + .onStart { + emit(DataUpdateTrigger.InitialRequest) + qsTileLogger.logInitialRequest(spec) + } + tileDataInteractor + .tileData(userId, updateTriggers) + .cancellable() + .flowOn(backgroundDispatcher) } - .flowOn(backgroundDispatcher) .shareIn( tileScope, SharingStarted.WhileSubscribed(), replay = 1, // we only care about the most recent value ) - private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> { - data class StateWithData<T>(val state: QSTileState, val data: T) + /** + * Creates a user input flow which: + * - filters false inputs with [falsingManager] + * - takes care of a tile being disable by policy using [disabledByPolicyInteractor] + * - notifies [userActionInteractor] about the action + * - logs it accordingly using [qsTileLogger] and [qsTileAnalytics] + * + * Subscribing to the result flow twice will result in doubling all actions, logs and analytics. + */ + private fun userInputFlow(userId: Int): Flow<DataUpdateTrigger> { + return userInputs + .filterFalseActions() + .filterByPolicy(userId) + .throttle(CLICK_THROTTLE_DURATION, systemClock) + // Skip the input until there is some data + .mapNotNull { action -> + val state: QSTileState = state.replayCache.lastOrNull() ?: return@mapNotNull null + val data: DATA_TYPE = tileData.replayCache.lastOrNull() ?: return@mapNotNull null + qsTileLogger.logUserActionPipeline(spec, action, state, data) + qsTileAnalytics.trackUserAction(config, action) - return when (config.policy) { - is QSTilePolicy.NoRestrictions -> userInputs - is QSTilePolicy.Restricted -> - userInputs.filter { action -> - val result = - disabledByPolicyInteractor.isDisabled( - userId, - config.policy.userRestriction - ) - !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled -> - if (isDisabled) { - qsTileLogger.logUserActionRejectedByPolicy(action, spec) - } - } - } + DataUpdateTrigger.UserInput(QSTileInput(userId, action, data)) } - .filter { action -> - val isFalseAction = - when (action) { - is QSTileUserAction.Click -> - falsingManager.isFalseTap(FalsingManager.LOW_PENALTY) - is QSTileUserAction.LongClick -> - falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) + .onEach { userActionInteractor.handleInput(it.input) } + .flowOn(backgroundDispatcher) + } + + private fun Flow<QSTileUserAction>.filterByPolicy(userId: Int): Flow<QSTileUserAction> = + when (config.policy) { + is QSTilePolicy.NoRestrictions -> this + is QSTilePolicy.Restricted -> + filter { action -> + val result = + disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction) + !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled -> + if (isDisabled) { + qsTileLogger.logUserActionRejectedByPolicy(action, spec) + } } - if (isFalseAction) { - qsTileLogger.logUserActionRejectedByFalsing(action, spec) } - !isFalseAction - } - .throttle(500) - // Skip the input until there is some data - .sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) { - input, - stateWithData -> - StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data).also { - qsTileLogger.logUserActionPipeline( - spec, - it.action, - stateWithData.state, - stateWithData.data - ) - qsTileAnalytics.trackUserAction(config, it.action) + } + + private fun Flow<QSTileUserAction>.filterFalseActions(): Flow<QSTileUserAction> = + filter { action -> + val isFalseAction = + when (action) { + is QSTileUserAction.Click -> + falsingManager.isFalseTap(FalsingManager.LOW_PENALTY) + is QSTileUserAction.LongClick -> + falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) } + if (isFalseAction) { + qsTileLogger.logUserActionRejectedByFalsing(action, spec) } - } + !isFalseAction + } - private data class DataWithTrigger<T>(val data: T, val trigger: StateUpdateTrigger) + private companion object { + const val CLICK_THROTTLE_DURATION = 200L + } + /** + * Factory interface for assisted inject. Dagger has bad time supporting generics in assisted + * injection factories now. That's why you need to create an interface implementing this one and + * annotate it with [dagger.assisted.AssistedFactory]. + * + * ex: @AssistedFactory interface FooFactory : BaseQSTileViewModel.Factory<FooData> + */ interface Factory<T> { /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index 0ccde741e2cc..dc5ccccd6f7f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -59,7 +59,16 @@ data class QSTileState( // This represents a tile that is currently in a disabled state but is still interactable. A // disabled state indicates that the tile is not currently active (e.g. wifi disconnected or // bluetooth disabled), but is still interactable by the user to modify this state. - INACTIVE(Tile.STATE_INACTIVE), + INACTIVE(Tile.STATE_INACTIVE); + + companion object { + fun valueOf(legacyState: Int): ActivationState = + when (legacyState) { + Tile.STATE_ACTIVE -> ACTIVE + Tile.STATE_INACTIVE -> INACTIVE + else -> UNAVAILABLE + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index f6299e38ae18..33f55ab53233 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -22,6 +22,7 @@ import android.view.View import androidx.annotation.GuardedBy import com.android.internal.logging.InstanceId import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.QSHost import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon @@ -31,9 +32,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.util.function.Supplier import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel -import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectIndexed import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -44,6 +43,7 @@ import kotlinx.coroutines.launch class QSTileViewModelAdapter @AssistedInject constructor( + @Application private val applicationScope: CoroutineScope, private val qsHost: QSHost, @Assisted private val qsTileViewModel: QSTileViewModel, ) : QSTile { @@ -57,25 +57,28 @@ constructor( private val listeningClients: MutableCollection<Any> = mutableSetOf() // Cancels the jobs when the adapter is no longer alive - private val adapterScope = CoroutineScope(SupervisorJob()) + private var availabilityJob: Job? = null // Cancels the jobs when clients stop listening - private val listeningScope = CoroutineScope(SupervisorJob()) + private var stateJob: Job? = null init { - adapterScope.launch { - qsTileViewModel.isAvailable.collectIndexed { index, isAvailable -> - if (!isAvailable) { - qsHost.removeTile(tileSpec) - } - // qsTileViewModel.isAvailable flow often starts with isAvailable == true. That's - // why we only allow isAvailable == true once and throw an exception afterwards. - if (index > 0 && isAvailable) { - // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for additional - // guidance on how to auto add your tile - throw UnsupportedOperationException("Turning on tile is not supported now") + availabilityJob = + applicationScope.launch { + qsTileViewModel.isAvailable.collectIndexed { index, isAvailable -> + if (!isAvailable) { + qsHost.removeTile(tileSpec) + } + // qsTileViewModel.isAvailable flow often starts with isAvailable == true. + // That's + // why we only allow isAvailable == true once and throw an exception afterwards. + if (index > 0 && isAvailable) { + // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for + // additional + // guidance on how to auto add your tile + throw UnsupportedOperationException("Turning on tile is not supported now") + } } } - } // QSTileHost doesn't call this when userId is initialized userSwitch(qsHost.userId) @@ -140,25 +143,28 @@ constructor( ) override fun getMetricsCategory(): Int = 0 + override fun isTileReady(): Boolean = qsTileViewModel.currentState != null + override fun setListening(client: Any?, listening: Boolean) { client ?: return synchronized(listeningClients) { if (listening) { listeningClients.add(client) if (listeningClients.size == 1) { - qsTileViewModel.state - .map { mapState(context, it, qsTileViewModel.config) } - .onEach { legacyState -> - synchronized(callbacks) { - callbacks.forEach { it.onStateChanged(legacyState) } + stateJob = + qsTileViewModel.state + .map { mapState(context, it, qsTileViewModel.config) } + .onEach { legacyState -> + synchronized(callbacks) { + callbacks.forEach { it.onStateChanged(legacyState) } + } } - } - .launchIn(listeningScope) + .launchIn(applicationScope) } } else { listeningClients.remove(client) if (listeningClients.isEmpty()) { - listeningScope.coroutineContext.cancelChildren() + stateJob?.cancel() } } } @@ -172,8 +178,8 @@ constructor( } override fun destroy() { - adapterScope.cancel() - listeningScope.cancel() + stateJob?.cancel() + availabilityJob?.cancel() qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD) } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index f469c6b78e87..ea1205ae6079 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.CountDownTimer; +import android.os.Process; import android.os.UserHandle; import android.util.Log; @@ -141,6 +142,13 @@ public class RecordingController return UserHandle.of(UserHandle.myUserId()); } + /** + * MediaProjection host is SystemUI for the screen recorder, so return 'my process uid' + */ + private int getHostUid() { + return Process.myUid(); + } + /** Create a dialog to show screen recording options to the user. * If screen capturing is currently not allowed it will return a dialog * that warns users about it. */ @@ -155,12 +163,22 @@ public class RecordingController } mMediaProjectionMetricsLogger.notifyProjectionInitiated( + mUserContextProvider.getUserContext().getUserId(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) - ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this, - activityStarter, mUserContextProvider, onStartRecordingClicked) - : new ScreenRecordDialog(context, this, mUserContextProvider, + ? new ScreenRecordPermissionDialog( + context, + getHostUserHandle(), + getHostUid(), + /* controller= */ this, + activityStarter, + mUserContextProvider, + onStartRecordingClicked) + : new ScreenRecordDialog( + context, + /* controller= */ this, + mUserContextProvider, onStartRecordingClicked); } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index ade93b1a913e..3b3aa5334979 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -46,6 +46,7 @@ import com.android.systemui.settings.UserContextProvider class ScreenRecordPermissionDialog( context: Context, private val hostUserHandle: UserHandle, + private val hostUid: Int, private val controller: RecordingController, private val activityStarter: ActivityStarter, private val userContextProvider: UserContextProvider, @@ -88,6 +89,7 @@ class ScreenRecordPermissionDialog( MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE, hostUserHandle ) + intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid) activityStarter.startActivity(intent, /* dismissShade= */ true) } dismiss() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 015828438375..25ee8d80d4f5 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -16,10 +16,7 @@ import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback import java.util.function.Consumer import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** @@ -40,12 +37,7 @@ constructor( private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory, ) { - private lateinit var displays: StateFlow<Set<Display>> - private val displaysCollectionJob: Job = - mainScope.launch { - displays = displayRepository.displays.stateIn(this, SharingStarted.Eagerly, emptySet()) - } - + private val displays = displayRepository.displays private val screenshotControllers = mutableMapOf<Int, ScreenshotController>() private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>() @@ -63,6 +55,7 @@ constructor( val displayIds = getDisplaysToScreenshot(screenshotRequest.type) val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback) displayIds.forEach { displayId: Int -> + Log.d(TAG, "Executing screenshot for display $displayId") dispatchToController( rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId), onSaved = @@ -126,12 +119,12 @@ constructor( callback.reportError() } - private fun getDisplaysToScreenshot(requestType: Int): List<Int> { + private suspend fun getDisplaysToScreenshot(requestType: Int): List<Int> { return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) { // If this is a provided image, let's show the UI on the default display only. listOf(Display.DEFAULT_DISPLAY) } else { - displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId } + displays.first().filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId } } } @@ -163,7 +156,6 @@ constructor( screenshotController.onDestroy() } screenshotControllers.clear() - displaysCollectionJob.cancel() } private fun getScreenshotController(id: Int): ScreenshotController { 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 1ecb127f0c92..ead10d6da860 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 d8692397edda..3d3447b921fc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -35,7 +35,6 @@ import com.android.keyguard.KeyguardMessageAreaController; import com.android.keyguard.LockIconViewController; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.Dumpable; -import com.android.systemui.FeatureFlags; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; @@ -43,6 +42,7 @@ 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; @@ -107,8 +107,8 @@ public class NotificationShadeWindowViewController implements Dumpable { 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; @@ -181,9 +181,9 @@ public class NotificationShadeWindowViewController implements Dumpable { KeyguardTransitionInteractor keyguardTransitionInteractor, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, CommunalViewModel communalViewModel, + CommunalRepository communalRepository, NotificationExpansionRepository notificationExpansionRepository, FeatureFlagsClassic featureFlagsClassic, - FeatureFlags featureFlags, SystemClock clock, BouncerMessageInteractor bouncerMessageInteractor, BouncerLogger bouncerLogger, @@ -214,8 +214,8 @@ public class NotificationShadeWindowViewController implements Dumpable { mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener; mNotificationInsetsController = notificationInsetsController; mCommunalViewModel = communalViewModel; + mCommunalRepository = communalRepository; mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON); - mFeatureFlags = featureFlags; mFeatureFlagsClassic = featureFlagsClassic; mSysUIKeyEventHandler = sysUIKeyEventHandler; mPrimaryBouncerInteractor = primaryBouncerInteractor; @@ -575,7 +575,8 @@ public class NotificationShadeWindowViewController implements Dumpable { * The layout lives in {@link R.id.communal_ui_container}. */ public void setupCommunalHubLayout() { - if (!mFeatureFlags.communalHub() || !ComposeFacade.INSTANCE.isComposeAvailable()) { + if (!mCommunalRepository.isCommunalEnabled() + || !ComposeFacade.INSTANCE.isComposeAvailable()) { return; } 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 f1eb9febfb33..000000000000 --- 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 f32f1c2dcd25..710e59d91c6b 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 389486f0ada3..9c4625e91110 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 5dcf6d1d8f28..f3b5ab6968a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -32,11 +32,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/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 125c8efe1884..1fe6b83b47b1 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/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index e2de37fcbcbe..22912df71334 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -17,18 +17,13 @@ package com.android.systemui.statusbar.dagger import com.android.systemui.CoreStartable -import com.android.systemui.statusbar.core.StatusBarInitializer -import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository -import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryImpl -import com.android.systemui.statusbar.data.repository.StatusBarModeRepository -import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl +import com.android.systemui.statusbar.data.StatusBarDataLayerModule import com.android.systemui.statusbar.phone.LightBarController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap -import dagger.multibindings.IntoSet /** * A module for **only** classes related to the status bar **UI element**. This module specifically @@ -38,24 +33,9 @@ import dagger.multibindings.IntoSet * ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule], * [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.). */ -@Module +@Module(includes = [StatusBarDataLayerModule::class]) abstract class StatusBarModule { @Binds - abstract fun bindStatusBarModeRepository( - impl: StatusBarModeRepositoryImpl - ): StatusBarModeRepository - - @Binds - @IntoMap - @ClassKey(StatusBarModeRepositoryImpl::class) - abstract fun bindStatusBarModeRepositoryStart(impl: StatusBarModeRepositoryImpl): CoreStartable - - @Binds - abstract fun bindKeyguardStatusBarRepository( - impl: KeyguardStatusBarRepositoryImpl - ): KeyguardStatusBarRepository - - @Binds @IntoMap @ClassKey(OngoingCallController::class) abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable @@ -64,10 +44,4 @@ abstract class StatusBarModule { @IntoMap @ClassKey(LightBarController::class) abstract fun bindLightBarController(impl: LightBarController): CoreStartable - - @Binds - @IntoSet - abstract fun statusBarInitializedListener( - statusBarModeRepository: StatusBarModeRepository, - ): StatusBarInitializer.OnStatusBarViewInitializedListener } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt new file mode 100644 index 000000000000..29d53fc15e8b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt @@ -0,0 +1,31 @@ +/* + * 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.data + +import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule +import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule +import dagger.Module + +@Module( + includes = + [ + KeyguardStatusBarRepositoryModule::class, + StatusBarModeRepositoryModule::class, + StatusBarPhoneDataLayerModule::class + ] +) +object StatusBarDataLayerModule diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt index 8136de9b7ac4..d1594ef2e404 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt @@ -22,6 +22,8 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.user.data.repository.UserSwitcherRepository +import dagger.Binds +import dagger.Module import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -78,3 +80,8 @@ constructor( isEnabled && isKeyguardEnabled } } + +@Module +interface KeyguardStatusBarRepositoryModule { + @Binds fun bindImpl(impl: KeyguardStatusBarRepositoryImpl): KeyguardStatusBarRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt index 2b059944798f..47994d92d22b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt @@ -30,7 +30,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.statusbar.core.StatusBarInitializer +import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener import com.android.systemui.statusbar.data.model.StatusBarAppearance import com.android.systemui.statusbar.data.model.StatusBarMode import com.android.systemui.statusbar.phone.BoundsPair @@ -38,6 +38,11 @@ import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator import com.android.systemui.statusbar.phone.StatusBarBoundsProvider import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import dagger.multibindings.IntoSet import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -56,7 +61,7 @@ import kotlinx.coroutines.flow.stateIn * Note: These status bar modes are status bar *window* states that are sent to us from * WindowManager, not determined internally. */ -interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener { +interface StatusBarModeRepository { /** * True if the status bar window is showing transiently and will disappear soon, and false * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR @@ -112,7 +117,7 @@ constructor( private val commandQueue: CommandQueue, private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator, ongoingCallRepository: OngoingCallRepository, -) : StatusBarModeRepository, CoreStartable { +) : StatusBarModeRepository, CoreStartable, OnStatusBarViewInitializedListener { private val commandQueueCallback = object : CommandQueue.Callbacks { @@ -334,3 +339,17 @@ constructor( val statusBarBounds: BoundsPair, ) } + +@Module +interface StatusBarModeRepositoryModule { + @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepository + + @Binds + @IntoMap + @ClassKey(StatusBarModeRepositoryImpl::class) + fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable + + @Binds + @IntoSet + fun bindViewInitListener(impl: StatusBarModeRepositoryImpl): OnStatusBarViewInitializedListener +} 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 bde298d7a33d..ea1d7820c79c 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 dccc23f61092..73c0bfec69a1 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 7d866df73da9..8ee1ade7a185 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 a3bc00248362..f0e60dd2ce54 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 8957f298f160..f98f39e3532c 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. @@ -123,6 +125,8 @@ public class PropertyAnimator { view.setTag(animatorTag, null); view.setTag(animationStartTag, null); view.setTag(animationEndTag, null); + } else { + Log.e(TAG, "Unexpected Animator set during onAnimationEnd. Not cleaning up."); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 2d839704e0b7..0c69a65b96af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -37,8 +37,8 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider -import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.headsUpEvents import com.android.systemui.util.asIndenting @@ -85,7 +85,7 @@ constructor( @Application private val scope: CoroutineScope, private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, private val secureSettings: SecureSettings, - private val notificationListInteractor: NotificationListInteractor, + private val seenNotificationsInteractor: SeenNotificationsInteractor, private val statusBarStateController: StatusBarStateController, ) : Coordinator, Dumpable { @@ -351,7 +351,7 @@ constructor( override fun onCleanup() { logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs) - notificationListInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs) + seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs) hasFilteredAnyNotifs = false } } @@ -389,7 +389,7 @@ constructor( with(pw.asIndenting()) { println( "notificationListInteractor.hasFilteredOutSeenNotifications.value=" + - notificationListInteractor.hasFilteredOutSeenNotifications.value + seenNotificationsInteractor.hasFilteredOutSeenNotifications.value ) println("unseen notifications:") indentIfPossible { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 62a0d138fd05..c2a021d0803f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -16,25 +16,32 @@ package com.android.systemui.statusbar.notification.collection.coordinator +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.util.traceSection import javax.inject.Inject /** - * A small coordinator which updates the notif stack (the view layer which holds notifications) - * with high-level data after the stack is populated with the final entries. + * A small coordinator which updates the notif stack (the view layer which holds notifications) with + * high-level data after the stack is populated with the final entries. */ @CoordinatorScope -class StackCoordinator @Inject internal constructor( +class StackCoordinator +@Inject +internal constructor( + private val featureFlags: FeatureFlagsClassic, private val groupExpansionManagerImpl: GroupExpansionManagerImpl, - private val notificationIconAreaController: NotificationIconAreaController + private val notificationIconAreaController: NotificationIconAreaController, + private val renderListInteractor: RenderNotificationListInteractor, ) : Coordinator { override fun attach(pipeline: NotifPipeline) { @@ -46,6 +53,9 @@ class StackCoordinator @Inject internal constructor( traceSection("StackCoordinator.onAfterRenderList") { controller.setNotifStats(calculateNotifStats(entries)) notificationIconAreaController.updateNotificationIcons(entries) + if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + renderListInteractor.setRenderedList(entries) + } } private fun calculateNotifStats(entries: List<ListEntry>): NotifStats { @@ -75,4 +85,4 @@ class StackCoordinator @Inject internal constructor( hasClearableSilentNotifs = hasClearableSilentNotifs ) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt new file mode 100644 index 000000000000..8064f049c88e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt @@ -0,0 +1,41 @@ +/* + * 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.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** + * Repository of "active" notifications in the notification list. + * + * This repository serves as the boundary between the + * [com.android.systemui.statusbar.notification.collection.NotifPipeline] and the modern + * notifications presentation codebase. + */ +@SysUISingleton +class ActiveNotificationListRepository @Inject constructor() { + /** + * Notifications actively presented to the user in the notification stack. + * + * @see com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener + */ + val activeNotifications = MutableStateFlow(emptyMap<String, ActiveNotificationModel>()) + + /** Are any already-seen notifications currently filtered out of the active list? */ + val hasFilteredOutSeenNotifications = MutableStateFlow(false) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt new file mode 100644 index 000000000000..bfec60bcd6db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.domain.interactor + +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class ActiveNotificationsInteractor +@Inject +constructor( + repository: ActiveNotificationListRepository, +) { + /** Notifications actively presented to the user in the notification stack, in order. */ + val notifications: Flow<Collection<ActiveNotificationModel>> = + repository.activeNotifications.map { it.values } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt index 8f7e269d34b0..8079ce540e1b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt @@ -20,15 +20,14 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository import javax.inject.Inject -/** Interactor for notifications in general. */ +/** Interactor for notification alerting. */ @SysUISingleton -class NotificationsInteractor +class NotificationAlertsInteractor @Inject constructor( private val disableFlagsRepository: DisableFlagsRepository, ) { /** Returns true if notification alerts are allowed. */ - fun areNotificationAlertsEnabled(): Boolean { - return disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled() - } + fun areNotificationAlertsEnabled(): Boolean = + disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt new file mode 100644 index 000000000000..c5396ddbe565 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.domain.interactor + +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import javax.inject.Inject +import kotlinx.coroutines.flow.update + +/** + * Logic for passing information from the + * [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation + * layers. + */ +class RenderNotificationListInteractor +@Inject +constructor( + private val repository: ActiveNotificationListRepository, +) { + /** + * Sets the current list of rendered notification entries as displayed in the notification + * stack. + * + * @see com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository.activeNotifications + */ + fun setRenderedList(entries: List<ListEntry>) { + repository.activeNotifications.update { modelsByKey -> + entries.associateBy( + keySelector = { it.key }, + valueTransform = { it.toModel(modelsByKey[it.key]) } + ) + } + } + + private fun ListEntry.toModel(existing: ActiveNotificationModel?): ActiveNotificationModel { + val isCurrent = + when { + existing == null -> false + key == existing.key -> true + else -> false + } + return if (isCurrent) existing!! else ActiveNotificationModel(key = key) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt index 3fd68a8bdd7c..f3e122ce690b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt @@ -14,25 +14,25 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.stack.domain.interactor +package com.android.systemui.statusbar.notification.domain.interactor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow /** Interactor for business logic associated with the notification stack. */ @SysUISingleton -class NotificationListInteractor +class SeenNotificationsInteractor @Inject constructor( - private val notificationListRepository: NotificationListRepository, + private val notificationListRepository: ActiveNotificationListRepository, ) { /** Are any already-seen notifications currently filtered out of the shade? */ - val hasFilteredOutSeenNotifications: StateFlow<Boolean> - get() = notificationListRepository.hasFilteredOutSeenNotifications + val hasFilteredOutSeenNotifications: StateFlow<Boolean> = + notificationListRepository.hasFilteredOutSeenNotifications fun setHasFilteredOutSeenNotifications(value: Boolean) { - notificationListRepository.setHasFilteredOutSeenNotifications(value) + notificationListRepository.hasFilteredOutSeenNotifications.value = value } } 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 000000000000..94e70e5521c3 --- /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/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt index eb5c1fa3b0ca..de011dbd1ab5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt @@ -16,32 +16,27 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import android.content.Context -import android.graphics.Color import android.graphics.Rect import android.os.Bundle import android.os.Trace import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout -import androidx.annotation.ColorInt import androidx.annotation.VisibleForTesting import androidx.collection.ArrayMap import com.android.internal.statusbar.StatusBarIcon -import com.android.internal.util.ContrastColorUtil -import com.android.settingslib.Utils +import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.flags.RefactorFlag -import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationListener import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.NotificationShelfController import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -55,7 +50,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.phone.ScreenOffAnimationController -import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.wm.shell.bubbles.Bubbles import java.util.Optional @@ -75,44 +69,34 @@ class NotificationIconAreaControllerViewBinderWrapperImpl @Inject constructor( private val context: Context, + private val configuration: ConfigurationState, private val wakeUpCoordinator: NotificationWakeUpCoordinator, private val bypassController: KeyguardBypassController, - private val configurationController: ConfigurationController, private val mediaManager: NotificationMediaManager, notificationListener: NotificationListener, private val dozeParameters: DozeParameters, private val sectionStyleProvider: SectionStyleProvider, private val bubblesOptional: Optional<Bubbles>, demoModeController: DemoModeController, - darkIconDispatcher: DarkIconDispatcher, private val featureFlags: FeatureFlagsClassic, private val statusBarWindowController: StatusBarWindowController, private val screenOffAnimationController: ScreenOffAnimationController, private val shelfIconsViewModel: NotificationIconContainerShelfViewModel, private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel, private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, -) : - NotificationIconAreaController, - DarkIconDispatcher.DarkReceiver, - NotificationWakeUpCoordinator.WakeUpListener, - DemoMode { +) : NotificationIconAreaController, NotificationWakeUpCoordinator.WakeUpListener, DemoMode { - private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context) private val updateStatusBarIcons = Runnable { updateStatusBarIcons() } private val shelfRefactor = RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR) - private val tintAreas = ArrayList<Rect>() private var iconSize = 0 private var iconHPadding = 0 - private var iconTint = Color.WHITE private var notificationEntries = listOf<ListEntry>() private var notificationIconArea: View? = null private var notificationIcons: NotificationIconContainer? = null private var shelfIcons: NotificationIconContainer? = null private var aodIcons: NotificationIconContainer? = null private var aodBindJob: DisposableHandle? = null - private var aodIconAppearTranslation = 0 - private var aodIconTint = 0 private var showLowPriority = true @VisibleForTesting @@ -129,8 +113,6 @@ constructor( demoModeController.addCallback(this) notificationListener.addNotificationSettingsListener(settingsListener) initializeNotificationAreaViews(context) - reloadAodColor() - darkIconDispatcher.addDarkReceiver(this) } @VisibleForTesting @@ -152,7 +134,7 @@ constructor( NotificationIconContainerViewBinder.bind( aodIcons, aodIconsViewModel, - configurationController, + configuration, dozeParameters, featureFlags, screenOffAnimationController, @@ -167,16 +149,17 @@ constructor( NotificationShelfViewBinderWrapperControllerImpl.unsupported override fun setShelfIcons(icons: NotificationIconContainer) { - if (shelfRefactor.isUnexpectedlyInLegacyMode()) return - NotificationIconContainerViewBinder.bind( - icons, - shelfIconsViewModel, - configurationController, - dozeParameters, - featureFlags, - screenOffAnimationController, - ) - shelfIcons = icons + if (shelfRefactor.isUnexpectedlyInLegacyMode()) { + NotificationIconContainerViewBinder.bind( + icons, + shelfIconsViewModel, + configuration, + dozeParameters, + featureFlags, + screenOffAnimationController, + ) + shelfIcons = icons + } } override fun onDensityOrFontScaleChanged(context: Context) { @@ -188,22 +171,6 @@ constructor( return notificationIconArea } - /** - * See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the - * color that should be used to tint any icons in the notification area. - * - * @param tintAreas the areas in which to tint the icons, specified in screen coordinates - * @param darkIntensity - */ - override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) { - this.tintAreas.clear() - this.tintAreas.addAll(tintAreas) - if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) { - this.iconTint = iconTint - } - applyNotificationIconsTint() - } - /** Updates the notifications with the given list of notifications to display. */ override fun updateNotificationIcons(entries: List<ListEntry>) { notificationEntries = entries @@ -249,10 +216,7 @@ constructor( override fun setAnimationsEnabled(enabled: Boolean) = unsupported - override fun onThemeChanged() { - reloadAodColor() - updateAodIconColors() - } + override fun onThemeChanged() = unsupported override fun getHeight(): Int { return if (aodIcons == null) 0 else aodIcons!!.height @@ -260,7 +224,6 @@ constructor( override fun onFullyHiddenChanged(isFullyHidden: Boolean) { updateAodNotificationIcons() - updateAodIconColors() } override fun demoCommands(): List<String> { @@ -296,7 +259,7 @@ constructor( NotificationIconContainerViewBinder.bind( notificationIcons!!, statusBarIconsViewModel, - configurationController, + configuration, dozeParameters, featureFlags, screenOffAnimationController, @@ -335,7 +298,6 @@ constructor( val res = context.resources iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp) iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin) - aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation) } private fun shouldShowNotificationIcon( @@ -383,7 +345,6 @@ constructor( updateStatusBarIcons() updateShelfIcons() updateAodNotificationIcons() - applyNotificationIconsTint() Trace.endSection() } @@ -526,55 +487,7 @@ constructor( hostLayout.setReplacingIcons(null) } - /** Applies [.mIconTint] to the notification icons. */ - private fun applyNotificationIconsTint() { - for (i in 0 until notificationIcons!!.childCount) { - val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView - if (iv.width != 0) { - updateTintForIcon(iv, iconTint) - } else { - iv.executeOnLayout { updateTintForIcon(iv, iconTint) } - } - } - updateAodIconColors() - } - - private fun updateTintForIcon(v: StatusBarIconView, tint: Int) { - val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L) - var color = StatusBarIconView.NO_COLOR - val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil) - if (colorize) { - color = DarkIconDispatcher.getTint(tintAreas, v, tint) - } - v.staticDrawableColor = color - v.setDecorColor(tint) - } - - private fun reloadAodColor() { - aodIconTint = - Utils.getColorAttrDefaultColor( - context, - R.attr.wallpaperTextColor, - DEFAULT_AOD_ICON_COLOR - ) - } - - private fun updateAodIconColors() { - if (aodIcons != null) { - for (i in 0 until aodIcons!!.childCount) { - val iv = aodIcons!!.getChildAt(i) as StatusBarIconView - if (iv.width != 0) { - updateTintForIcon(iv, aodIconTint) - } else { - iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) } - } - } - } - } - companion object { - @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1 - val unsupported: Nothing get() = error( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 0d2f00aa3627..079004c2a60a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -15,27 +15,30 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewbinder -import android.content.res.Resources +import android.graphics.Rect import android.view.View -import androidx.annotation.DimenRes import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators +import com.android.internal.util.ContrastColorUtil +import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.CrossFadeHelper +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.phone.ScreenOffAnimationController -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged -import com.android.systemui.util.kotlin.stateFlow -import kotlinx.coroutines.CoroutineScope +import com.android.systemui.util.children import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */ @@ -43,11 +46,12 @@ object NotificationIconContainerViewBinder { fun bind( view: NotificationIconContainer, viewModel: NotificationIconContainerViewModel, - configurationController: ConfigurationController, + configuration: ConfigurationState, dozeParameters: DozeParameters, featureFlags: FeatureFlagsClassic, screenOffAnimationController: ScreenOffAnimationController, ): DisposableHandle { + val contrastColorUtil = ContrastColorUtil.getInstance(view.context) return view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) } @@ -59,15 +63,13 @@ object NotificationIconContainerViewBinder { } } } - // TODO(278765923): this should live where AOD is bound, not inside of the NIC + // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC // view-binder launch { val iconAppearTranslation = - view.resources.getConfigAwareDimensionPixelSize( - this, - configurationController, - R.dimen.shelf_appear_translation, - ) + configuration + .getDimensionPixelSize(R.dimen.shelf_appear_translation) + .stateIn(this) bindVisibility( viewModel, view, @@ -78,9 +80,40 @@ object NotificationIconContainerViewBinder { viewModel.completeVisibilityAnimation() } } + launch { + viewModel.iconColors + .mapNotNull { lookup -> lookup.iconColors(view.viewBounds) } + .collect { iconLookup -> applyTint(view, iconLookup, contrastColorUtil) } + } + } + } + } + + // TODO(b/305739416): Once SBIV has its own Recommended Architecture stack, this can be moved + // there and cleaned up. + private fun applyTint( + view: NotificationIconContainer, + iconColors: IconColors, + contrastColorUtil: ContrastColorUtil, + ) { + view.children.filterIsInstance<StatusBarIconView>().forEach { iv -> + if (iv.width != 0) { + updateTintForIcon(iv, iconColors, contrastColorUtil) } } } + + private fun updateTintForIcon( + v: StatusBarIconView, + iconColors: IconColors, + contrastColorUtil: ContrastColorUtil, + ) { + val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L) + val isColorized = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil) + v.staticDrawableColor = iconColors.staticDrawableColor(v.viewBounds, isColorized) + v.setDecorColor(iconColors.tint) + } + private suspend fun bindVisibility( viewModel: NotificationIconContainerViewModel, view: NotificationIconContainer, @@ -173,14 +206,16 @@ object NotificationIconContainerViewBinder { } private const val AOD_ICONS_APPEAR_DURATION: Long = 200 -} -fun Resources.getConfigAwareDimensionPixelSize( - scope: CoroutineScope, - configurationController: ConfigurationController, - @DimenRes id: Int, -): StateFlow<Int> = - scope.stateFlow( - changedSignals = configurationController.onDensityOrFontScaleChanged, - getValue = { getDimensionPixelSize(id) } - ) + private val View.viewBounds: Rect + get() { + val tmpArray = intArrayOf(0, 0) + getLocationOnScreen(tmpArray) + return Rect( + /* left = */ tmpArray[0], + /* top = */ tmpArray[1], + /* right = */ left + width, + /* bottom = */ top + height, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt index 3289a3ce5574..e9de4bd654d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt @@ -15,6 +15,10 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import android.graphics.Color +import android.graphics.Rect +import androidx.annotation.ColorInt +import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.flags.FeatureFlagsClassic @@ -23,8 +27,11 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.util.kotlin.pairwise @@ -44,6 +51,7 @@ import kotlinx.coroutines.flow.map class NotificationIconContainerAlwaysOnDisplayViewModel @Inject constructor( + configuration: ConfigurationState, private val deviceEntryInteractor: DeviceEntryInteractor, private val dozeParameters: DozeParameters, private val featureFlags: FeatureFlagsClassic, @@ -57,6 +65,11 @@ constructor( private val onDozeAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1) private val onVisAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1) + override val iconColors: Flow<ColorLookup> = + configuration.getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR).map { tint -> + ColorLookup { IconColorsImpl(tint) } + } + override val animationsEnabled: Flow<Boolean> = combine( shadeInteractor.isShadeTouchable, @@ -157,4 +170,12 @@ constructor( } .toAnimatedValueFlow(completionEvents = onVisAnimationComplete) } + + private class IconColorsImpl(override val tint: Int) : IconColors { + override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int = tint + } + + companion object { + @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt index c44a2b60142c..f305155e9b3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup import com.android.systemui.util.ui.AnimatedValue import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -29,4 +30,5 @@ class NotificationIconContainerShelfViewModel @Inject constructor() : override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow() override fun completeDozeAnimation() {} override fun completeVisibilityAnimation() {} + override val iconColors: Flow<ColorLookup> = emptyFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index 035687a4a91b..ee01fcca82d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -15,8 +15,14 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import android.graphics.Rect import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors +import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor import com.android.systemui.util.ui.AnimatedValue import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -27,7 +33,9 @@ import kotlinx.coroutines.flow.emptyFlow class NotificationIconContainerStatusBarViewModel @Inject constructor( + darkIconInteractor: DarkIconInteractor, keyguardInteractor: KeyguardInteractor, + notificationsInteractor: ActiveNotificationsInteractor, shadeInteractor: ShadeInteractor, ) : NotificationIconContainerViewModel { override val animationsEnabled: Flow<Boolean> = @@ -37,9 +45,36 @@ constructor( ) { panelTouchesEnabled, isKeyguardShowing -> panelTouchesEnabled && !isKeyguardShowing } - + override val iconColors: Flow<ColorLookup> = + combine( + darkIconInteractor.tintAreas, + darkIconInteractor.tintColor, + // Included so that tints are re-applied after entries are changed. + notificationsInteractor.notifications, + ) { areas, tint, _ -> + ColorLookup { viewBounds: Rect -> + if (DarkIconDispatcher.isInAreas(areas, viewBounds)) { + IconColorsImpl(tint, areas) + } else { + null + } + } + } override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow() override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow() override fun completeDozeAnimation() {} override fun completeVisibilityAnimation() {} + + private class IconColorsImpl( + override val tint: Int, + private val areas: Collection<Rect>, + ) : IconColors { + override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int { + return if (isColorized && DarkIconDispatcher.isInAreas(areas, viewBounds)) { + tint + } else { + DarkIconDispatcher.DEFAULT_ICON_TINT + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt index 65eb22075ec7..c98811b0e285 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import android.graphics.Rect import com.android.systemui.util.ui.AnimatedValue import kotlinx.coroutines.flow.Flow @@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.Flow * AOD. */ interface NotificationIconContainerViewModel { + /** Are changes to the icon container animated? */ val animationsEnabled: Flow<Boolean> @@ -32,15 +34,39 @@ interface NotificationIconContainerViewModel { /** Is the icon container visible? */ val isVisible: Flow<AnimatedValue<Boolean>> + /** The colors with which to display the notification icons. */ + val iconColors: Flow<ColorLookup> + /** * Signal completion of the [isDozing] animation; if [isDozing]'s [AnimatedValue.isAnimating] - * property was `true`, calling this method will update it to `false. + * property was `true`, calling this method will update it to `false`. */ fun completeDozeAnimation() /** * Signal completion of the [isVisible] animation; if [isVisible]'s [AnimatedValue.isAnimating] - * property was `true`, calling this method will update it to `false. + * property was `true`, calling this method will update it to `false`. */ fun completeVisibilityAnimation() + + /** + * Lookup the colors to use for the notification icons based on the bounds of the icon + * container. A result of `null` indicates that no color changes should be applied. + */ + fun interface ColorLookup { + fun iconColors(viewBounds: Rect): IconColors? + } + + /** Colors to apply to notification icons. */ + interface IconColors { + + /** A tint to apply to the icons. */ + val tint: Int + + /** + * Returns the color to be applied to an icon, based on that icon's view bounds and whether + * or not the notification icon is colorized. + */ + fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt new file mode 100644 index 000000000000..ea29cab3b7dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.shared + +/** Model for entries in the notification stack. */ +data class ActiveNotificationModel( + /** Notification key associated with this entry. */ + val key: String, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java index e4d96c30f15c..6bb957339b6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java @@ -67,18 +67,6 @@ public interface NotificationListContainer extends void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer); /** - * Generate an animation for an added child view. - * @param child The view to be added. - * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen. - */ - void generateAddAnimation(ExpandableView child, boolean fromMoreCard); - - /** - * Generate a child order changed event. - */ - void generateChildOrderChangedEvent(); - - /** * Returns the number of children in the NotificationListContainer. * * @return the number of children in the NotificationListContainer @@ -187,21 +175,6 @@ public interface NotificationListContainer extends default void bindRow(ExpandableNotificationRow row) {} /** - * Does this list contain a given view. True by default is fine, since we only ask this if the - * view has a parent. - */ - default boolean containsView(View v) { - return true; - } - - /** - * Tells the container that an animation is about to expand it. - */ - default void setWillExpand(boolean willExpand) {} - - void setNotificationActivityStarter(NotificationActivityStarter notificationActivityStarter); - - /** * @return the start location where we start clipping notifications. */ default int getTopClippingStartLocation() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index dba93d9718cb..77d5a2d70d43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -324,7 +324,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private NotificationsController mNotificationsController; private ActivityStarter mActivityStarter; private final int[] mTempInt2 = new int[2]; - private boolean mGenerateChildOrderChangedEvent; private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>(); private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>(); private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations @@ -3144,10 +3143,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestChildrenUpdate(); } - public boolean containsView(View v) { - return v.getParent() == this; - } - public void applyLaunchAnimationParams(LaunchAnimationParameters params) { // Modify the clipping for launching notifications mLaunchAnimationParams = params; @@ -3410,11 +3405,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAnimationEvents.add(animEvent); } mChildrenChangingPositions.clear(); - if (mGenerateChildOrderChangedEvent) { - mAnimationEvents.add(new AnimationEvent(null, - AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); - mGenerateChildOrderChangedEvent = false; - } } private void generateChildAdditionEvents() { @@ -4764,14 +4754,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable info.setClassName(ScrollView.class.getName()); } - public void generateChildOrderChangedEvent() { - if (mIsExpanded && mAnimationsEnabled) { - mGenerateChildOrderChangedEvent = true; - mNeedsAnimation = true; - requestChildrenUpdate(); - } - } - public int getContainerChildCount() { return getChildCount(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 6a70815f82f3..8e88a91b5cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -106,6 +106,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.SilentHeader; +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -114,7 +115,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationSnooze; -import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; @@ -194,7 +194,7 @@ public class NotificationStackScrollLayoutController { private final GroupExpansionManager mGroupExpansionManager; private final NotifPipelineFlags mNotifPipelineFlags; - private final NotificationListInteractor mNotificationListInteractor; + private final SeenNotificationsInteractor mSeenNotificationsInteractor; private final KeyguardTransitionRepository mKeyguardTransitionRepo; private NotificationStackScrollLayout mView; @@ -662,7 +662,7 @@ public class NotificationStackScrollLayoutController { UiEventLogger uiEventLogger, NotificationRemoteInputManager remoteInputManager, VisibilityLocationProviderDelegator visibilityLocationProviderDelegator, - NotificationListInteractor notificationListInteractor, + SeenNotificationsInteractor seenNotificationsInteractor, ShadeController shadeController, InteractionJankMonitor jankMonitor, StackStateLogger stackLogger, @@ -715,7 +715,7 @@ public class NotificationStackScrollLayoutController { mUiEventLogger = uiEventLogger; mRemoteInputManager = remoteInputManager; mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator; - mNotificationListInteractor = notificationListInteractor; + mSeenNotificationsInteractor = seenNotificationsInteractor; mShadeController = shadeController; mNotifIconAreaController = notifIconAreaController; mFeatureFlags = featureFlags; @@ -1725,28 +1725,11 @@ public class NotificationStackScrollLayoutController { } @Override - public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) { - mView.generateAddAnimation(child, fromMoreCard); - } - - @Override - public void generateChildOrderChangedEvent() { - mView.generateChildOrderChangedEvent(); - } - - @Override public int getContainerChildCount() { return mView.getContainerChildCount(); } @Override - public void setNotificationActivityStarter( - NotificationActivityStarter notificationActivityStarter) { - NotificationStackScrollLayoutController.this - .setNotificationActivityStarter(notificationActivityStarter); - } - - @Override public int getTopClippingStartLocation() { return mView.getTopClippingStartLocation(); } @@ -1841,16 +1824,6 @@ public class NotificationStackScrollLayoutController { } @Override - public boolean containsView(View v) { - return mView.containsView(v); - } - - @Override - public void setWillExpand(boolean willExpand) { - mView.setWillExpand(willExpand); - } - - @Override public void dumpPipeline(@NonNull PipelineDumper d) { d.dump("NotificationStackScrollLayoutController.this", NotificationStackScrollLayoutController.this); @@ -2006,7 +1979,7 @@ public class NotificationStackScrollLayoutController { public void setNotifStats(@NonNull NotifStats notifStats) { mNotifStats = notifStats; mView.setHasFilteredOutSeenNotifications( - mNotificationListInteractor.getHasFilteredOutSeenNotifications().getValue()); + mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue()); updateFooter(); updateShowEmptyShadeView(); } 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 59f10aed4145..daa4f1807625 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 8d9fd12356a6..cb85966ca581 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(); @@ -1583,7 +1546,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager); mLightBarController.setBiometricUnlockController(mBiometricUnlockController); - mMediaManager.setBiometricUnlockController(mBiometricUnlockController); Trace.endSection(); } @@ -1870,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)); @@ -1944,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. * @@ -2139,7 +2087,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { releaseGestureWakeLock(); runLaunchTransitionEndRunnable(); mKeyguardStateController.setLaunchTransitionFadingAway(false); - mPresenterLazy.get().updateMediaMetaData(true /* metaDataChanged */, true); } /** @@ -2163,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, @@ -3178,7 +3124,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mAmbientIndicationContainer instanceof AutoReinflateContainer) { ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout(); } - mNotificationIconAreaController.onThemeChanged(); + if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) { + mNotificationIconAreaController.onThemeChanged(); + } } @Override @@ -3222,8 +3170,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/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 2960520f00b4..2206be5e614b 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 00fd9fbfffe3..000000000000 --- 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 54d81b83197e..5a8b636e54fc 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 5b552640f397..744d70e36972 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 90fddd9ae22c..267b56378d82 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 57a8e6fe0d91..07e2571bcb38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -52,7 +52,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; -import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor; +import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -80,7 +80,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu private final HeadsUpManager mHeadsUpManager; private final AboveShelfObserver mAboveShelfObserver; private final DozeScrimController mDozeScrimController; - private final NotificationsInteractor mNotificationsInteractor; + private final NotificationAlertsInteractor mNotificationAlertsInteractor; private final NotificationStackScrollLayoutController mNsslController; private final LockscreenShadeTransitionController mShadeTransitionController; private final PowerInteractor mPowerInteractor; @@ -107,7 +107,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, - NotificationsInteractor notificationsInteractor, + NotificationAlertsInteractor notificationAlertsInteractor, LockscreenShadeTransitionController shadeTransitionController, PowerInteractor powerInteractor, CommandQueue commandQueue, @@ -127,7 +127,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu mQsController = quickSettingsController; mHeadsUpManager = headsUp; mDynamicPrivacyController = dynamicPrivacyController; - mNotificationsInteractor = notificationsInteractor; + mNotificationAlertsInteractor = notificationAlertsInteractor; mNsslController = stackScrollerController; mShadeTransitionController = shadeTransitionController; mPowerInteractor = powerInteractor; @@ -205,7 +205,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu // End old BaseStatusBar.userSwitched mCommandQueue.animateCollapsePanels(); mMediaManager.clearCurrentMediaNotification(); - updateMediaMetaData(true, false); } @Override @@ -220,11 +219,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu } @Override - public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { - mMediaManager.updateMediaMetaData(metaDataChanged); - } - - @Override public void onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded) { mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); @@ -309,7 +303,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu @Override public boolean suppressInterruptions(NotificationEntry entry) { - return !mNotificationsInteractor.areNotificationAlertsEnabled(); + return !mNotificationAlertsInteractor.areNotificationAlertsEnabled(); } }; } 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 85fd2afed9ec..71e25e9556eb 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,75 @@ 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 - ) - - val lp = attributes - lp.fitInsetsSides = 0 - lp.horizontalMargin = 0f - attributes = lp - } + decorView.setPadding(0, 0, 0, 0) + attributes = + attributes.apply { + fitInsetsSides = 0 + horizontalMargin = 0f + } } - setCanceledOnTouchOutside(true) } + + 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) + } + + override fun onStart() { + super.onStart() + configurationController?.addCallback(onConfigChanged) + } + + override fun onStop() { + super.onStop() + configurationController?.removeCallback(onConfigChanged) + } + + /** Can be overridden by subclasses to receive config changed events. */ + open fun onConfigurationChanged() {} + + private val onConfigChanged = + object : ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + super.onConfigChanged(newConfig) + setupEdgeToEdge() + onConfigurationChanged() + } + } } 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 9d627af357cb..2558645e3f64 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/common/ui/data/repository/CommonRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt index 9b0c3fa7e92b..9645c69ab089 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt @@ -13,13 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.systemui.statusbar.phone.data -package com.android.systemui.common.ui.data.repository - -import dagger.Binds +import com.android.systemui.statusbar.phone.data.repository.DarkIconRepositoryModule import dagger.Module -@Module -interface CommonRepositoryModule { - @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository -} +@Module(includes = [DarkIconRepositoryModule::class]) object StatusBarPhoneDataLayerModule diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt index f6ed8c884816..ba377497bce4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt @@ -13,23 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package com.android.systemui.statusbar.notification.stack.data.repository +package com.android.systemui.statusbar.phone.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange +import dagger.Binds +import dagger.Module import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -/** Repository for information about the current notification list. */ +/** Dark-mode state for tinting icons. */ +interface DarkIconRepository { + val darkState: StateFlow<DarkChange> +} + @SysUISingleton -class NotificationListRepository @Inject constructor() { - private val _hasFilteredOutSeenNotifications = MutableStateFlow(false) - val hasFilteredOutSeenNotifications: StateFlow<Boolean> = - _hasFilteredOutSeenNotifications.asStateFlow() +class DarkIconRepositoryImpl +@Inject +constructor( + darkIconDispatcher: SysuiDarkIconDispatcher, +) : DarkIconRepository { + override val darkState: StateFlow<DarkChange> = darkIconDispatcher.darkChangeFlow() +} - fun setHasFilteredOutSeenNotifications(value: Boolean) { - _hasFilteredOutSeenNotifications.value = value - } +@Module +interface DarkIconRepositoryModule { + @Binds fun bindImpl(impl: DarkIconRepositoryImpl): DarkIconRepository } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt new file mode 100644 index 000000000000..246645ee0ead --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt @@ -0,0 +1,34 @@ +/* + * 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.domain.interactor + +import android.graphics.Rect +import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** States pertaining to calculating colors for icons in dark mode. */ +class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) { + /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.areas */ + val tintAreas: Flow<Collection<Rect>> = repository.darkState.map { it.areas } + /** + * @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.darkIntensity + */ + val darkIntensity: Flow<Float> = repository.darkState.map { it.darkIntensity } + /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.tint */ + val tintColor: Flow<Int> = repository.darkState.map { it.tint } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt index 21acfb41f10c..25d67aff50a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt @@ -13,7 +13,7 @@ */ package com.android.systemui.statusbar.policy -import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -23,14 +23,30 @@ import kotlinx.coroutines.flow.Flow * @see ConfigurationController.ConfigurationListener.onDensityOrFontScaleChanged */ val ConfigurationController.onDensityOrFontScaleChanged: Flow<Unit> - get() = - ConflatedCallbackFlow.conflatedCallbackFlow { - val listener = - object : ConfigurationController.ConfigurationListener { - override fun onDensityOrFontScaleChanged() { - trySend(Unit) - } + get() = conflatedCallbackFlow { + val listener = + object : ConfigurationController.ConfigurationListener { + override fun onDensityOrFontScaleChanged() { + trySend(Unit) } - addCallback(listener) - awaitClose { removeCallback(listener) } - } + } + addCallback(listener) + awaitClose { removeCallback(listener) } + } + +/** + * A [Flow] that emits whenever the theme has changed. + * + * @see ConfigurationController.ConfigurationListener.onThemeChanged + */ +val ConfigurationController.onThemeChanged: Flow<Unit> + get() = conflatedCallbackFlow { + val listener = + object : ConfigurationController.ConfigurationListener { + override fun onThemeChanged() { + trySend(Unit) + } + } + addCallback(listener) + awaitClose { removeCallback(listener) } + } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt index 9269df31e37e..8c66c2f0fab3 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 000000000000..ed960f31228a --- /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 ed3eacd27c0a..71314f1f1775 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/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt new file mode 100644 index 000000000000..909a18be4b9e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt @@ -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.systemui.util.kotlin + +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.awaitCancellation + +/** + * Suspends to keep getting updates until cancellation. Once cancelled, mark this as eligible for + * garbage collection. + * + * This utility is useful if you want to bind a [repeatWhenAttached] invocation to the lifetime of a + * coroutine, such that cancelling the coroutine cleans up the handle. For example: + * ``` + * myFlow.collectLatest { value -> + * val disposableHandle = myView.repeatWhenAttached { doStuff() } + * doSomethingWith(value) + * // un-bind when done + * disposableHandle.awaitCancellationThenDispose() + * } + * ``` + */ +suspend fun DisposableHandle.awaitCancellationThenDispose() { + try { + awaitCancellation() + } finally { + dispose() + } +} 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 b3834f58be2f..31b90bae27eb 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt @@ -373,4 +373,11 @@ inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine( args[6] as T9, ) } -}
\ No newline at end of file +} + +/** + * Returns a [Flow] that immediately emits [Unit] when started, then emits from the given upstream + * [Flow] as normal. + */ +@Suppress("NOTHING_TO_INLINE") +inline fun Flow<Unit>.emitOnStart(): Flow<Unit> = onStart { emit(Unit) } diff --git a/packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt b/packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt new file mode 100644 index 000000000000..6d45d23879e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt @@ -0,0 +1,82 @@ +/* + * 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.view + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.util.kotlin.awaitCancellationThenDispose +import com.android.systemui.util.kotlin.stateFlow +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest + +/** + * Perform an inflation right away, then re-inflate whenever the [flow] emits, and call [onInflate] + * on the resulting view each time. Dispose of the [DisposableHandle] returned by [onInflate] when + * done. + * + * This never completes unless cancelled, it just suspends and waits for updates. + * + * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate]. + * + * An example use-case of this is when a view needs to be re-inflated whenever a configuration + * change occurs, which would require the ViewBinder to then re-bind the new view. For example, the + * code in the parent view's binder would look like: + * ``` + * parentView.repeatWhenAttached { + * LayoutInflater.from(parentView.context) + * .reinflateOnChange( + * R.layout.my_layout, + * parentView, + * attachToRoot = false, + * coroutineScope = lifecycleScope, + * configurationController.onThemeChanged, + * ), + * ) { view -> + * ChildViewBinder.bind(view as ChildView, childViewModel) + * } + * } + * ``` + * + * In turn, the bind method (passed through [onInflate]) uses [repeatWhenAttached], which returns a + * [DisposableHandle]. + */ +suspend fun LayoutInflater.reinflateAndBindLatest( + resource: Int, + root: ViewGroup?, + attachToRoot: Boolean, + flow: Flow<Unit>, + onInflate: (View) -> DisposableHandle?, +) = coroutineScope { + val viewFlow: Flow<View> = stateFlow(flow) { inflate(resource, root, attachToRoot) } + viewFlow.bindLatest(onInflate) +} + +/** + * Use the [bind] method to bind the view every time this flow emits, and suspend to await for more + * updates. New emissions lead to the previous binding call being cancelled if not completed. + * Dispose of the [DisposableHandle] returned by [bind] when done. + */ +suspend fun Flow<View>.bindLatest(bind: (View) -> DisposableHandle?) { + this.collectLatest { view -> + val disposableHandle = bind(view) + disposableHandle?.awaitCancellationThenDispose() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index aea3030967d2..fdf5966419b4 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 ea74510a9070..c4b43e1cbe77 100644 --- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt +++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt @@ -16,10 +16,17 @@ package com.android import android.content.Context +import android.content.res.Resources +import android.testing.TestableContext +import android.testing.TestableResources import com.android.systemui.FakeSystemUiModule import com.android.systemui.SysuiTestCase +import com.android.systemui.SysuiTestableContext import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.broadcast.FakeBroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import dagger.Binds import dagger.Module import dagger.Provides @@ -31,12 +38,29 @@ import dagger.Provides FakeSystemUiModule::class, ] ) -class SysUITestModule { - @Provides fun provideContext(test: SysuiTestCase): Context = test.context +interface SysUITestModule { - @Provides @Application fun provideAppContext(test: SysuiTestCase): Context = test.context + @Binds fun bindTestableContext(sysuiTestableContext: SysuiTestableContext): TestableContext + @Binds fun bindContext(testableContext: TestableContext): Context + @Binds @Application fun bindAppContext(context: Context): Context + @Binds @Application fun bindAppResources(resources: Resources): Resources + @Binds @Main fun bindMainResources(resources: Resources): Resources + @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher - @Provides - fun provideBroadcastDispatcher(test: SysuiTestCase): BroadcastDispatcher = - test.fakeBroadcastDispatcher + companion object { + @Provides + fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context + + @Provides + fun provideTestableResources(context: TestableContext): TestableResources = + context.getOrCreateTestableResources() + + @Provides + fun provideResources(testableResources: TestableResources): Resources = + testableResources.resources + + @Provides + fun provideFakeBroadcastDispatcher(test: SysuiTestCase): FakeBroadcastDispatcher = + test.fakeBroadcastDispatcher + } } diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt index 167e3417c162..ff1d5b2ea3bf 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 df7d1b7a4aad..aed795a440b4 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/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java index 80b281e458d0..882bcab5fab6 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; @@ -105,21 +104,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); ConfigurationController configurationController = mock(ConfigurationController.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt index 0e14591c5f53..52c6e22cfcbb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt @@ -18,9 +18,11 @@ package com.android.systemui.flags import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -61,80 +63,70 @@ class ConditionalRestarterTest : SysuiTestCase() { @Test fun restart_ImmediatelySatisfied() = testScope.runTest { - conditionA.canRestart = true - conditionB.canRestart = true + conditionA.canRestart.emit(true) + conditionB.canRestart.emit(true) restarter.restartSystemUI("Restart for test") - advanceUntilIdle() + runCurrent() verify(systemExitRestarter).restartSystemUI(any()) } @Test fun restart_WaitsForConditionA() = testScope.runTest { - conditionA.canRestart = false - conditionB.canRestart = true + conditionA.canRestart.emit(false) + conditionB.canRestart.emit(true) restarter.restartSystemUI("Restart for test") - advanceUntilIdle() + runCurrent() // No restart occurs yet. verify(systemExitRestarter, never()).restartSystemUI(any()) - conditionA.canRestart = true - conditionA.retryFn?.invoke() - advanceUntilIdle() + conditionA.canRestart.emit(true) + runCurrent() verify(systemExitRestarter).restartSystemUI(any()) } @Test fun restart_WaitsForConditionB() = testScope.runTest { - conditionA.canRestart = true - conditionB.canRestart = false + conditionA.canRestart.emit(true) + conditionB.canRestart.emit(false) restarter.restartSystemUI("Restart for test") - advanceUntilIdle() + runCurrent() // No restart occurs yet. verify(systemExitRestarter, never()).restartSystemUI(any()) - conditionB.canRestart = true - conditionB.retryFn?.invoke() - advanceUntilIdle() + conditionB.canRestart.emit(true) + runCurrent() verify(systemExitRestarter).restartSystemUI(any()) } @Test fun restart_WaitsForAllConditions() = testScope.runTest { - conditionA.canRestart = true - conditionB.canRestart = false + conditionA.canRestart.emit(true) + conditionB.canRestart.emit(false) restarter.restartSystemUI("Restart for test") - advanceUntilIdle() + runCurrent() // No restart occurs yet. verify(systemExitRestarter, never()).restartSystemUI(any()) // B becomes true, but A is now false - conditionA.canRestart = false - conditionB.canRestart = true - conditionB.retryFn?.invoke() - advanceUntilIdle() + conditionA.canRestart.emit(false) + conditionB.canRestart.emit(true) // No restart occurs yet. verify(systemExitRestarter, never()).restartSystemUI(any()) - conditionA.canRestart = true - conditionA.retryFn?.invoke() - advanceUntilIdle() + conditionA.canRestart.emit(true) + runCurrent() verify(systemExitRestarter).restartSystemUI(any()) } class FakeCondition : ConditionalRestarter.Condition { - var retryFn: (() -> Unit)? = null - var canRestart = false + val canRestart = MutableStateFlow(false) - override fun canRestartNow(retryFn: () -> Unit): Boolean { - this.retryFn = retryFn - - return canRestart - } + override val canRestartNow: Flow<Boolean> = canRestart } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt new file mode 100644 index 000000000000..db6f85f12a42 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt @@ -0,0 +1,91 @@ +/* + * 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.test.suitebuilder.annotation.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +/** + * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()! + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class NotOccludedConditionTest : SysuiTestCase() { + private lateinit var condition: NotOccludedCondition + + @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor + private val transitionValue = MutableStateFlow(0f) + + private val testDispatcher: TestDispatcher = StandardTestDispatcher() + private val testScope: TestScope = TestScope(testDispatcher) + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(keyguardTransitionInteractor.transitionValue(KeyguardState.OCCLUDED)) + .thenReturn(transitionValue) + condition = NotOccludedCondition({ keyguardTransitionInteractor }) + testScope.runCurrent() + } + + @Test + fun testCondition_occluded() = + testScope.runTest { + val canRestart by collectLastValue(condition.canRestartNow) + + transitionValue.emit(1f) + assertThat(canRestart).isFalse() + } + + @Test + fun testCondition_notOccluded() = + testScope.runTest { + val canRestart by collectLastValue(condition.canRestartNow) + + transitionValue.emit(0f) + assertThat(canRestart).isTrue() + } + + @Test + fun testCondition_invokesRetry() = + testScope.runTest { + val canRestart by collectLastValue(condition.canRestartNow) + + transitionValue.emit(1f) + + assertThat(canRestart).isFalse() + + transitionValue.emit(0f) + + assertThat(canRestart).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt index 647b05a77b90..7d7abab4a0f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt @@ -17,8 +17,13 @@ package com.android.systemui.flags import android.test.suitebuilder.annotation.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.statusbar.policy.BatteryController import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor @@ -35,42 +40,51 @@ class PluggedInConditionTest : SysuiTestCase() { private lateinit var condition: PluggedInCondition @Mock private lateinit var batteryController: BatteryController + private val testDispatcher: TestDispatcher = StandardTestDispatcher() + private val testScope: TestScope = TestScope(testDispatcher) + private val callbackCaptor = + ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java) @Before fun setup() { MockitoAnnotations.initMocks(this) - condition = PluggedInCondition(batteryController) + + condition = PluggedInCondition({ batteryController }) } @Test - fun testCondition_unplugged() { - whenever(batteryController.isPluggedIn).thenReturn(false) + fun testCondition_unplugged() = + testScope.runTest { + whenever(batteryController.isPluggedIn).thenReturn(false) - assertThat(condition.canRestartNow({})).isFalse() - } + val canRestart by collectLastValue(condition.canRestartNow) + + assertThat(canRestart).isFalse() + } @Test - fun testCondition_pluggedIn() { - whenever(batteryController.isPluggedIn).thenReturn(true) + fun testCondition_pluggedIn() = + testScope.runTest { + whenever(batteryController.isPluggedIn).thenReturn(true) - assertThat(condition.canRestartNow({})).isTrue() - } + val canRestart by collectLastValue(condition.canRestartNow) + + assertThat(canRestart).isTrue() + } @Test - fun testCondition_invokesRetry() { - whenever(batteryController.isPluggedIn).thenReturn(false) - var retried = false - val retryFn = { retried = true } + fun testCondition_invokesRetry() = + testScope.runTest { + whenever(batteryController.isPluggedIn).thenReturn(false) - // No restart yet, but we do register a listener now. - assertThat(condition.canRestartNow(retryFn)).isFalse() - val captor = - ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java) - verify(batteryController).addCallback(captor.capture()) + val canRestart by collectLastValue(condition.canRestartNow) - whenever(batteryController.isPluggedIn).thenReturn(true) + assertThat(canRestart).isFalse() - captor.value.onBatteryLevelChanged(0, true, true) - assertThat(retried).isTrue() - } + verify(batteryController).addCallback(callbackCaptor.capture()) + + callbackCaptor.value.onBatteryLevelChanged(0, true, false) + + assertThat(canRestart).isTrue() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt index f7a773ea30ec..1f04828c359a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt @@ -17,15 +17,17 @@ package com.android.systemui.flags import android.test.suitebuilder.annotation.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP -import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.power.domain.interactor.PowerInteractor import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -36,42 +38,50 @@ import org.mockito.MockitoAnnotations class ScreenIdleConditionTest : SysuiTestCase() { private lateinit var condition: ScreenIdleCondition - @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock private lateinit var powerInteractor: PowerInteractor + private val isAsleep = MutableStateFlow(false) + + private val testDispatcher: TestDispatcher = StandardTestDispatcher() + private val testScope: TestScope = TestScope(testDispatcher) @Before fun setup() { MockitoAnnotations.initMocks(this) - condition = ScreenIdleCondition(wakefulnessLifecycle) + whenever(powerInteractor.isAsleep).thenReturn(isAsleep) + condition = ScreenIdleCondition({ powerInteractor }) } @Test - fun testCondition_awake() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) + fun testCondition_awake() = + testScope.runTest { + val canRestart by collectLastValue(condition.canRestartNow) - assertThat(condition.canRestartNow {}).isFalse() - } + isAsleep.emit(false) + + assertThat(canRestart).isFalse() + } @Test - fun testCondition_asleep() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) + fun testCondition_asleep() = + testScope.runTest { + val canRestart by collectLastValue(condition.canRestartNow) - assertThat(condition.canRestartNow {}).isTrue() - } + isAsleep.emit(true) + + assertThat(canRestart).isTrue() + } @Test - fun testCondition_invokesRetry() { - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE) - var retried = false - val retryFn = { retried = true } + fun testCondition_invokesRetry() = + testScope.runTest { + val canRestart by collectLastValue(condition.canRestartNow) - // No restart yet, but we do register a listener now. - assertThat(condition.canRestartNow(retryFn)).isFalse() - val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java) - verify(wakefulnessLifecycle).addObserver(captor.capture()) + isAsleep.emit(false) - whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP) + assertThat(canRestart).isFalse() - captor.value.onFinishedGoingToSleep() - assertThat(retried).isTrue() - } + isAsleep.emit(true) + + assertThat(canRestart).isTrue() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt index bb6786ade21a..e16b8d400149 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt @@ -18,6 +18,17 @@ package com.android.systemui.flags import android.platform.test.flag.junit.SetFlagsRule +/** + * Set the given flag's value to the real value for the current build configuration. + * This prevents test code from crashing because it is reading an unspecified flag value. + * + * REMINDER: You should always test your code with your flag in both configurations, so + * generally you should be explicitly enabling or disabling your flag. This method is for + * situations where the flag needs to be read (e.g. in the class constructor), but its value + * shouldn't affect the actual test cases. In those cases, it's mildly safer to use this method + * than to hard-code `false` or `true` because then at least if you're wrong, and the flag value + * *does* matter, you'll notice when the flag is flipped and tests start failing. + */ fun SetFlagsRule.setFlagDefault(flagName: String) { if (getFlagDefault(flagName)) { enableFlags(flagName) 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 7a13a0a96ee6..489665cd130a 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 000000000000..1ff46db99624 --- /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 d7802aabb298..6cab023d59b0 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 000000000000..fd1e2c7b19a5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt @@ -0,0 +1,84 @@ +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) + } + + @Test + fun notifyPermissionRequestDisplayed_forwardsToService() { + val hostUid = 987 + + logger.notifyPermissionRequestDisplayed(hostUid) + + verify(service).notifyPermissionRequestDisplayed(hostUid) + } + + @Test + fun notifyAppSelectorDisplayed_forwardsToService() { + val hostUid = 654 + + logger.notifyAppSelectorDisplayed(hostUid) + + verify(service).notifyAppSelectorDisplayed(hostUid) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index 6cdf4efd67da..5255f71b9c09 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -44,7 +44,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private val thumbnailLoader = FakeThumbnailLoader() - private fun createController(isFirstStart: Boolean = true) = + private fun createController(isFirstStart: Boolean = true, hostUid: Int = 123) = MediaProjectionAppSelectorController( taskListProvider, view, @@ -55,7 +55,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { callerPackageName, thumbnailLoader, isFirstStart, - logger + logger, + hostUid, ) @Before @@ -212,20 +213,22 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { @Test fun init_firstStart_logsAppSelectorDisplayed() { - val controller = createController(isFirstStart = true) + val hostUid = 123456789 + val controller = createController(isFirstStart = true, hostUid) controller.init() - verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + verify(logger).notifyAppSelectorDisplayed(hostUid) } @Test fun init_notFirstStart_doesNotLogAppSelectorDisplayed() { - val controller = createController(isFirstStart = false) + val hostUid = 123456789 + val controller = createController(isFirstStart = false, hostUid) controller.init() - verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) + verify(logger, never()).notifyAppSelectorDisplayed(hostUid) } private fun givenCaptureAllowed(isAllow: Boolean) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index ac03073be17b..c6d156f51905 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -52,6 +52,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -96,6 +97,8 @@ public class ScreenRecordTileTest extends SysuiTestCase { private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger; @Mock private Dialog mPermissionDialogPrompt; + @Mock + private UserContextProvider mUserContextProvider; private TestableLooper mTestableLooper; private ScreenRecordTile mTile; @@ -106,6 +109,7 @@ public class ScreenRecordTileTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); + when(mUserContextProvider.getUserContext()).thenReturn(mContext); when(mHost.getContext()).thenReturn(mContext); mTile = new ScreenRecordTile( @@ -124,7 +128,8 @@ public class ScreenRecordTileTest extends SysuiTestCase { mKeyguardStateController, mDialogLaunchAnimator, mPanelInteractor, - mMediaProjectionMetricsLogger + mMediaProjectionMetricsLogger, + mUserContextProvider ); mTile.initialize(); @@ -308,7 +313,8 @@ public class ScreenRecordTileTest extends SysuiTestCase { onDismissAction.getValue().onDismiss(); verify(mPermissionDialogPrompt).show(); - verify(mMediaProjectionMetricsLogger).notifyPermissionRequestDisplayed(); + verify(mMediaProjectionMetricsLogger) + .notifyPermissionRequestDisplayed(mContext.getUserId()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt index 5659f0173860..95ee3b7a8495 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt @@ -32,16 +32,16 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) -class QSTileIntentUserActionHandlerTest : SysuiTestCase() { +class QSTileIntentUserInputHandlerTest : SysuiTestCase() { @Mock private lateinit var activityStarted: ActivityStarter - lateinit var underTest: QSTileIntentUserActionHandler + lateinit var underTest: QSTileIntentUserInputHandler @Before fun setup() { MockitoAnnotations.initMocks(this) - underTest = QSTileIntentUserActionHandler(activityStarted) + underTest = QSTileIntentUserInputHandler(activityStarted) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt index 9907278402b0..31d02ed78404 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt @@ -26,7 +26,6 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.util.mockito.any @@ -144,7 +143,6 @@ class QSTileLoggerTest : SysuiTestCase() { fun testLogStateUpdate() { underTest.logStateUpdate( TileSpec.create("test_spec"), - StateUpdateTrigger.ForceUpdate, QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}, "test_data", ) @@ -152,21 +150,36 @@ class QSTileLoggerTest : SysuiTestCase() { assertThat(logBuffer.getStringBuffer()) .contains( "tile state update: " + - "trigger=force, " + - "state=[" + - "label=, " + + "state=[label=, " + "state=INACTIVE, " + "s_label=null, " + "cd=null, " + "sd=null, " + "svi=None, " + "enabled=ENABLED, " + - "a11y=null" + - "], " + + "a11y=null], " + "data=test_data" ) } + @Test + fun testLogForceUpdate() { + underTest.logForceUpdate( + TileSpec.create("test_spec"), + ) + + assertThat(logBuffer.getStringBuffer()).contains("tile data force update") + } + + @Test + fun testLogInitialUpdate() { + underTest.logInitialRequest( + TileSpec.create("test_spec"), + ) + + assertThat(logBuffer.getStringBuffer()).contains("tile data initial update") + } + private fun LogBuffer.getStringBuffer(): String { val stringWriter = StringWriter() dump(PrintWriter(stringWriter), 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt index 2084aeb7fe83..9b85012b29a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt @@ -29,11 +29,11 @@ import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor -import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper -import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.StandardTestDispatcher @@ -55,6 +55,7 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { @Mock private lateinit var qsTileLogger: QSTileLogger @Mock private lateinit var qsTileAnalytics: QSTileAnalytics + private val fakeUserRepository = FakeUserRepository() private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>() private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>() private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor() @@ -86,7 +87,7 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty() assertThat(fakeQSTileDataInteractor.dataRequests.first()) - .isEqualTo(QSTileDataRequest(1, StateUpdateTrigger.InitialRequest)) + .isEqualTo(FakeQSTileDataInteractor.DataRequest(1)) } private fun createViewModel( @@ -102,9 +103,11 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {} }, fakeDisabledByPolicyInteractor, + fakeUserRepository, fakeFalsingManager, qsTileAnalytics, qsTileLogger, + FakeSystemClock(), testCoroutineDispatcher, scope.backgroundScope, ) 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 90d2e78a411f..c439cfe6270f 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/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index da4dc850fcae..bf12d7deb076 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.screenrecord +import android.content.Intent import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -25,11 +26,13 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN import com.android.systemui.mediaprojection.permission.SINGLE_APP import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals @@ -38,6 +41,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.eq +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @@ -62,6 +68,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { ScreenRecordPermissionDialog( context, UserHandle.of(0), + TEST_HOST_UID, controller, starter, userContextProvider, @@ -105,6 +112,19 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { } @Test + fun startClicked_singleAppSelected_passesHostUidToAppSelector() { + dialog.show() + onSpinnerItemSelected(SINGLE_APP) + + clickOnStart() + + assertExtraPassedToAppSelector( + extraKey = MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, + value = TEST_HOST_UID + ) + } + + @Test fun showDialog_dialogIsShowing() { dialog.show() @@ -133,9 +153,25 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() { dialog.requireViewById<View>(android.R.id.button2).performClick() } + private fun clickOnStart() { + dialog.requireViewById<View>(android.R.id.button1).performClick() + } + private fun onSpinnerItemSelected(position: Int) { val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) checkNotNull(spinner.onItemSelectedListener) .onItemSelected(spinner, mock(), position, /* id= */ 0) } + + private fun assertExtraPassedToAppSelector(extraKey: String, value: Int) { + val intentCaptor = argumentCaptor<Intent>() + verify(starter).startActivity(intentCaptor.capture(), /* dismissShade= */ eq(true)) + + val intent = intentCaptor.value + assertThat(intent.extras!!.getInt(extraKey)).isEqualTo(value) + } + + companion object { + private const val TEST_HOST_UID = 12345 + } } 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 da49230417b8..b421e1b45477 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -23,7 +23,6 @@ import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import androidx.core.view.contains import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardMessageAreaController import com.android.keyguard.KeyguardSecurityContainerController @@ -31,8 +30,6 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent -import com.android.systemui.FakeFeatureFlagsImpl -import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.back.domain.interactor.BackActionInteractor import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository @@ -47,6 +44,7 @@ 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 @@ -150,6 +148,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { 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 @@ -175,7 +174,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { private lateinit var testScope: TestScope private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic - private lateinit var featureFlags: FakeFeatureFlagsImpl @Before fun setUp() { @@ -199,7 +197,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { featureFlagsClassic.set(MIGRATE_NSSL, false) featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false) - featureFlags = FakeFeatureFlagsImpl() + mCommunalRepository = FakeCommunalRepository() testScope = TestScope() fakeClock = FakeSystemClock() @@ -235,9 +233,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, mCommunalViewModel, + mCommunalRepository, notificationExpansionRepository, featureFlagsClassic, - featureFlags, fakeClock, BouncerMessageInteractor( repository = BouncerMessageRepositoryImpl(), @@ -499,7 +497,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { return } - featureFlags.setFlag(FLAG_COMMUNAL_HUB, true) + mCommunalRepository.setIsCommunalEnabled(true) val mockCommunalPlaceholder = mock(View::class.java) val fakeViewIndex = 20 @@ -520,7 +518,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { return } - featureFlags.setFlag(FLAG_COMMUNAL_HUB, false) + mCommunalRepository.setIsCommunalEnabled(false) val mockCommunalPlaceholder = mock(View::class.java) val fakeViewIndex = 20 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 c94741fa5320..9c571015f750 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -28,7 +28,6 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent -import com.android.systemui.FakeFeatureFlagsImpl import com.android.systemui.SysuiTestCase import com.android.systemui.back.domain.interactor.BackActionInteractor import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository @@ -43,6 +42,7 @@ 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 @@ -145,6 +145,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { 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 @@ -181,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) @@ -222,9 +225,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, mCommunalViewModel, + mCommunalRepository, NotificationExpansionRepository(), featureFlags, - FakeFeatureFlagsImpl(), FakeSystemClock(), BouncerMessageInteractor( repository = BouncerMessageRepositoryImpl(), 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 a5f5fc7e36f2..43adc69be13f 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 cfcf4257ce28..000000000000 --- 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 839770267c74..df8afde1b9a3 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 4fcccf887e58..fee8b82a3038 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 @@ -370,6 +372,63 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { } @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 initializeSystemStatusAnimationScheduler(testScope = this) @@ -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/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index 546abd4ec79a..6c1f537e754f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -39,10 +39,10 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository -import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import com.android.systemui.util.mockito.any @@ -246,7 +246,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() { unseenFilter.onCleanup() // THEN: The SeenNotificationProvider has been updated to reflect the suppression - assertThat(notificationListInteractor.hasFilteredOutSeenNotifications.value).isTrue() + assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue() } } @@ -597,7 +597,8 @@ class KeyguardCoordinatorTest : SysuiTestCase() { FakeSettings().apply { putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) } - val notificationListInteractor = NotificationListInteractor(NotificationListRepository()) + val seenNotificationsInteractor = + SeenNotificationsInteractor(ActiveNotificationListRepository()) val keyguardCoordinator = KeyguardCoordinator( testDispatcher, @@ -610,7 +611,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() { testScope.backgroundScope, sectionHeaderVisibilityProvider, fakeSettings, - notificationListInteractor, + seenNotificationsInteractor, statusBarStateController, ) keyguardCoordinator.attach(notifPipeline) @@ -618,7 +619,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() { KeyguardCoordinatorTestScope( keyguardCoordinator, testScope, - notificationListInteractor, + seenNotificationsInteractor, fakeSettings, ) .testBlock() @@ -628,7 +629,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() { private inner class KeyguardCoordinatorTestScope( private val keyguardCoordinator: KeyguardCoordinator, private val scope: TestScope, - val notificationListInteractor: NotificationListInteractor, + val seenNotificationsInteractor: SeenNotificationsInteractor, private val fakeSettings: FakeSettings, ) : CoroutineScope by scope { val testScheduler: TestCoroutineScheduler diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index 655bd7243836..a736182b6329 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -19,6 +19,8 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -27,6 +29,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfte import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.phone.NotificationIconAreaController @@ -37,8 +40,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations.initMocks import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations.initMocks @SmallTest @RunWith(AndroidTestingRunner::class) @@ -52,13 +55,23 @@ class StackCoordinatorTest : SysuiTestCase() { @Mock private lateinit var pipeline: NotifPipeline @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController + @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor @Mock private lateinit var stackController: NotifStackController @Mock private lateinit var section: NotifSection + val featureFlags = + FakeFeatureFlagsClassic().apply { setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR) } + @Before fun setUp() { initMocks(this) - coordinator = StackCoordinator(groupExpansionManagerImpl, notificationIconAreaController) + coordinator = + StackCoordinator( + featureFlags, + groupExpansionManagerImpl, + notificationIconAreaController, + renderListInteractor, + ) coordinator.attach(pipeline) afterRenderListListener = withArgCaptor { verify(pipeline).addOnAfterRenderListListener(capture()) @@ -68,11 +81,19 @@ class StackCoordinatorTest : SysuiTestCase() { @Test fun testUpdateNotificationIcons() { + featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, false) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry))) } @Test + fun testSetRenderedListOnInteractor() { + featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, true) + afterRenderListListener.onAfterRenderList(listOf(entry), stackController) + verify(renderListInteractor).setRenderedList(eq(listOf(entry))) + } + + @Test fun testSetNotificationStats_clearableAlerting() { whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt new file mode 100644 index 000000000000..683d0aa33d4a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.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.statusbar.notification.domain.interactor + +import android.app.StatusBarManager +import androidx.test.filters.SmallTest +import com.android.SysUITestModule +import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import org.junit.Test + +@SmallTest +class NotificationAlertsInteractorTest : SysuiTestCase() { + + @Component(modules = [SysUITestModule::class]) + @SysUISingleton + interface TestComponent { + val underTest: NotificationAlertsInteractor + val disableFlags: FakeDisableFlagsRepository + + @Component.Factory + interface Factory { + fun create(@BindsInstance test: SysuiTestCase): TestComponent + } + } + + private val testComponent: TestComponent = + DaggerNotificationAlertsInteractorTest_TestComponent.factory().create(test = this) + + @Test + fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() = + with(testComponent) { + disableFlags.disableFlags.value = + DisableFlagsModel( + StatusBarManager.DISABLE_NONE, + StatusBarManager.DISABLE2_NONE, + ) + assertThat(underTest.areNotificationAlertsEnabled()).isTrue() + } + + @Test + fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() = + with(testComponent) { + disableFlags.disableFlags.value = + DisableFlagsModel( + StatusBarManager.DISABLE_NOTIFICATION_ALERTS, + StatusBarManager.DISABLE2_NONE, + ) + assertThat(underTest.areNotificationAlertsEnabled()).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt deleted file mode 100644 index fe49016a5d45..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.statusbar.notification.domain.interactor - -import android.app.StatusBarManager -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager -import com.android.systemui.log.LogBufferFactory -import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.statusbar.disableflags.DisableFlagsLogger -import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository -import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl -import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.mock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import org.junit.Before -import org.junit.Test -import org.mockito.Mockito.verify - -@SmallTest -@OptIn(ExperimentalCoroutinesApi::class) -class NotificationsInteractorTest : SysuiTestCase() { - - private lateinit var underTest: NotificationsInteractor - - private val testScope = TestScope(UnconfinedTestDispatcher()) - private val commandQueue: CommandQueue = mock() - private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10) - private val disableFlagsLogger = DisableFlagsLogger() - private lateinit var disableFlagsRepository: DisableFlagsRepository - - @Before - fun setUp() { - disableFlagsRepository = - DisableFlagsRepositoryImpl( - commandQueue, - DISPLAY_ID, - testScope.backgroundScope, - mock(), - logBuffer, - disableFlagsLogger, - ) - underTest = NotificationsInteractor(disableFlagsRepository) - } - - @Test - fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() { - val callback = getCommandQueueCallback() - - callback.disable( - DISPLAY_ID, - StatusBarManager.DISABLE_NONE, - StatusBarManager.DISABLE2_NONE, - /* animate= */ false - ) - - assertThat(underTest.areNotificationAlertsEnabled()).isTrue() - } - - @Test - fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() { - val callback = getCommandQueueCallback() - - callback.disable( - DISPLAY_ID, - StatusBarManager.DISABLE_NOTIFICATION_ALERTS, - StatusBarManager.DISABLE2_NONE, - /* animate= */ false - ) - - assertThat(underTest.areNotificationAlertsEnabled()).isFalse() - } - - private fun getCommandQueueCallback(): CommandQueue.Callbacks { - val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() - verify(commandQueue).addCallback(callbackCaptor.capture()) - return callbackCaptor.value - } - - private companion object { - const val DISPLAY_ID = 1 - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt new file mode 100644 index 000000000000..8c5c43986f8a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.shared.byKey +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@SmallTest +class RenderNotificationsListInteractorTest : SysuiTestCase() { + + private val notifsRepository = ActiveNotificationListRepository() + private val notifsInteractor = ActiveNotificationsInteractor(notifsRepository) + private val underTest = + RenderNotificationListInteractor( + notifsRepository, + ) + + @Test + fun setRenderedList_preservesOrdering() = runTest { + val notifs by collectLastValue(notifsInteractor.notifications) + val keys = (1..50).shuffled().map { "$it" } + val entries = keys.map { mock<ListEntry> { whenever(key).thenReturn(it) } } + underTest.setRenderedList(entries) + assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt new file mode 100644 index 000000000000..2a3c1a53559e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.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.statusbar.notification.domain.interactor + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class SeenNotificationsInteractorTest : SysuiTestCase() { + + private val repository = ActiveNotificationListRepository() + private val underTest = SeenNotificationsInteractor(repository) + + @Test + fun testNoFilteredOutSeenNotifications() = runTest { + val hasFilteredOutSeenNotifications by + collectLastValue(underTest.hasFilteredOutSeenNotifications) + + underTest.setHasFilteredOutSeenNotifications(false) + + assertThat(hasFilteredOutSeenNotifications).isFalse() + } + + @Test + fun testHasFilteredOutSeenNotifications() = runTest { + val hasFilteredOutSeenNotifications by + collectLastValue(underTest.hasFilteredOutSeenNotifications) + + underTest.setHasFilteredOutSeenNotifications(true) + + assertThat(hasFilteredOutSeenNotifications).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index e1e7f92265f0..e21ebeb77f96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import android.graphics.Rect import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.SysUITestModule @@ -34,10 +35,13 @@ import com.android.systemui.keyguard.shared.model.DozeTransitionModel 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.plugins.DarkIconDispatcher import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher +import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository import com.android.systemui.user.domain.UserDomainLayerModule import com.android.systemui.util.mockito.whenever @@ -61,18 +65,6 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Mock lateinit var dozeParams: DozeParameters private lateinit var testComponent: TestComponent - private val underTest: NotificationIconContainerStatusBarViewModel - get() = testComponent.underTest - private val deviceProvisioningRepository - get() = testComponent.deviceProvisioningRepository - private val keyguardTransitionRepository - get() = testComponent.keyguardTransitionRepository - private val keyguardRepository - get() = testComponent.keyguardRepository - private val powerRepository - get() = testComponent.powerRepository - private val scope - get() = testComponent.scope @Before fun setup() { @@ -82,7 +74,6 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory() .create( test = this, - // Configurable bindings featureFlags = FakeFeatureFlagsClassicModule { set(Flags.FACE_AUTH_REFACTOR, value = false) @@ -93,155 +84,247 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { dozeParameters = dozeParams, ), ) - - keyguardRepository.setKeyguardShowing(false) - deviceProvisioningRepository.setFactoryResetProtectionActive(false) - powerRepository.updateWakefulness( - rawState = WakefulnessState.AWAKE, - lastWakeReason = WakeSleepReason.OTHER, - lastSleepReason = WakeSleepReason.OTHER, - ) + .apply { + keyguardRepository.setKeyguardShowing(false) + deviceProvisioningRepository.setFactoryResetProtectionActive(false) + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.OTHER, + lastSleepReason = WakeSleepReason.OTHER, + ) + } } @Test fun animationsEnabled_isFalse_whenFrpIsActive() = - scope.runTest { - deviceProvisioningRepository.setFactoryResetProtectionActive(true) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, + with(testComponent) { + scope.runTest { + deviceProvisioningRepository.setFactoryResetProtectionActive(true) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + ) ) - ) - val animationsEnabled by collectLastValue(underTest.animationsEnabled) - runCurrent() - assertThat(animationsEnabled).isFalse() + val animationsEnabled by collectLastValue(underTest.animationsEnabled) + runCurrent() + assertThat(animationsEnabled).isFalse() + } } @Test fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() = - scope.runTest { - powerRepository.updateWakefulness( - rawState = WakefulnessState.ASLEEP, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, + with(testComponent) { + scope.runTest { + powerRepository.updateWakefulness( + rawState = WakefulnessState.ASLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + ) ) - ) - keyguardRepository.setDozeTransitionModel( - DozeTransitionModel( - to = DozeStateModel.DOZE_AOD, + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel( + to = DozeStateModel.DOZE_AOD, + ) ) - ) - val animationsEnabled by collectLastValue(underTest.animationsEnabled) - runCurrent() - assertThat(animationsEnabled).isFalse() + val animationsEnabled by collectLastValue(underTest.animationsEnabled) + runCurrent() + assertThat(animationsEnabled).isFalse() + } } @Test fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() = - scope.runTest { - powerRepository.updateWakefulness( - rawState = WakefulnessState.ASLEEP, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, + with(testComponent) { + scope.runTest { + powerRepository.updateWakefulness( + rawState = WakefulnessState.ASLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + ) ) - ) - keyguardRepository.setDozeTransitionModel( - DozeTransitionModel( - to = DozeStateModel.DOZE_PULSING, + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel( + to = DozeStateModel.DOZE_PULSING, + ) ) - ) - val animationsEnabled by collectLastValue(underTest.animationsEnabled) - runCurrent() - assertThat(animationsEnabled).isTrue() + val animationsEnabled by collectLastValue(underTest.animationsEnabled) + runCurrent() + assertThat(animationsEnabled).isTrue() + } } @Test fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() = - scope.runTest { - powerRepository.updateWakefulness( - rawState = WakefulnessState.STARTING_TO_SLEEP, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - transitionState = TransitionState.STARTED, + with(testComponent) { + scope.runTest { + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, ) - ) - whenever(dozeParams.shouldControlScreenOff()).thenReturn(false) - val animationsEnabled by collectLastValue(underTest.animationsEnabled) - runCurrent() - assertThat(animationsEnabled).isFalse() + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + ) + ) + whenever(dozeParams.shouldControlScreenOff()).thenReturn(false) + val animationsEnabled by collectLastValue(underTest.animationsEnabled) + runCurrent() + assertThat(animationsEnabled).isFalse() + } } @Test fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() = - scope.runTest { - powerRepository.updateWakefulness( - rawState = WakefulnessState.STARTING_TO_SLEEP, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - transitionState = TransitionState.STARTED, + with(testComponent) { + scope.runTest { + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + ) ) - ) - whenever(dozeParams.shouldControlScreenOff()).thenReturn(true) - val animationsEnabled by collectLastValue(underTest.animationsEnabled) - runCurrent() - assertThat(animationsEnabled).isTrue() + whenever(dozeParams.shouldControlScreenOff()).thenReturn(true) + val animationsEnabled by collectLastValue(underTest.animationsEnabled) + runCurrent() + assertThat(animationsEnabled).isTrue() + } } @Test fun animationsEnabled_isTrue_whenNotAsleep() = - scope.runTest { - powerRepository.updateWakefulness( - rawState = WakefulnessState.AWAKE, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, + with(testComponent) { + scope.runTest { + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + ) ) - ) - val animationsEnabled by collectLastValue(underTest.animationsEnabled) - runCurrent() - assertThat(animationsEnabled).isTrue() + val animationsEnabled by collectLastValue(underTest.animationsEnabled) + runCurrent() + assertThat(animationsEnabled).isTrue() + } } @Test fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() = - scope.runTest { - val animationsEnabled by collectLastValue(underTest.animationsEnabled) + with(testComponent) { + scope.runTest { + val animationsEnabled by collectLastValue(underTest.animationsEnabled) - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + transitionState = TransitionState.STARTED, + ) ) - ) - keyguardRepository.setKeyguardShowing(true) - runCurrent() + keyguardRepository.setKeyguardShowing(true) + runCurrent() + + assertThat(animationsEnabled).isFalse() + + keyguardRepository.setKeyguardShowing(false) + runCurrent() + + assertThat(animationsEnabled).isTrue() + } + } + + @Test + fun iconColors_testsDarkBounds() = + with(testComponent) { + scope.runTest { + darkIconRepository.darkState.value = + SysuiDarkIconDispatcher.DarkChange( + emptyList(), + 0f, + 0xAABBCC, + ) + val iconColorsLookup by collectLastValue(underTest.iconColors) + assertThat(iconColorsLookup).isNotNull() + + val iconColors = iconColorsLookup?.iconColors(Rect()) + assertThat(iconColors).isNotNull() + iconColors!! + + assertThat(iconColors.tint).isEqualTo(0xAABBCC) - assertThat(animationsEnabled).isFalse() + val staticDrawableColor = iconColors.staticDrawableColor(Rect(), isColorized = true) - keyguardRepository.setKeyguardShowing(false) - runCurrent() + assertThat(staticDrawableColor).isEqualTo(0xAABBCC) + } + } - assertThat(animationsEnabled).isTrue() + @Test + fun iconColors_staticDrawableColor_nonColorized() = + with(testComponent) { + scope.runTest { + darkIconRepository.darkState.value = + SysuiDarkIconDispatcher.DarkChange( + emptyList(), + 0f, + 0xAABBCC, + ) + val iconColorsLookup by collectLastValue(underTest.iconColors) + val iconColors = iconColorsLookup?.iconColors(Rect()) + val staticDrawableColor = + iconColors?.staticDrawableColor(Rect(), isColorized = false) + assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT) + } + } + + @Test + fun iconColors_staticDrawableColor_isColorized_notInDarkTintArea() = + with(testComponent) { + scope.runTest { + darkIconRepository.darkState.value = + SysuiDarkIconDispatcher.DarkChange( + listOf(Rect(0, 0, 5, 5)), + 0f, + 0xAABBCC, + ) + val iconColorsLookup by collectLastValue(underTest.iconColors) + val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4)) + val staticDrawableColor = + iconColors?.staticDrawableColor(Rect(6, 6, 7, 7), isColorized = true) + assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT) + } + } + + @Test + fun iconColors_notInDarkTintArea() = + with(testComponent) { + scope.runTest { + darkIconRepository.darkState.value = + SysuiDarkIconDispatcher.DarkChange( + listOf(Rect(0, 0, 5, 5)), + 0f, + 0xAABBCC, + ) + val iconColorsLookup by collectLastValue(underTest.iconColors) + val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7)) + assertThat(iconColors).isNull() + } } @SysUISingleton @@ -249,7 +332,6 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { modules = [ SysUITestModule::class, - // Real impls BiometricsDomainLayerModule::class, UserDomainLayerModule::class, ] @@ -258,6 +340,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { val underTest: NotificationIconContainerStatusBarViewModel + val darkIconRepository: FakeDarkIconRepository val deviceProvisioningRepository: FakeDeviceProvisioningRepository val keyguardTransitionRepository: FakeKeyguardTransitionRepository val keyguardRepository: FakeKeyguardRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt index 23ae26c255ce..1bb7b61668e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt @@ -24,9 +24,9 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.widget.NotificationDrawableConsumer -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.graphics.ImageLoader +import com.android.systemui.res.R import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat @@ -45,6 +45,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions private const val FREE_IMAGE_DELAY_MS = 4000L +private const val MAX_IMAGE_SIZE = 512 // size of the test drawables in pixels @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -81,6 +82,7 @@ class BigPictureIconManagerTest : SysuiTestCase() { @Before fun setUp() { allowTestableLooperAsMainThread() + overrideMaxImageSizes() iconManager = BigPictureIconManager( context, @@ -430,6 +432,17 @@ class BigPictureIconManagerTest : SysuiTestCase() { verifyZeroInteractions(mockConsumer) } + private fun overrideMaxImageSizes() { + testableResources.addOverride( + com.android.internal.R.dimen.notification_big_picture_max_width, + MAX_IMAGE_SIZE + ) + testableResources.addOverride( + com.android.internal.R.dimen.notification_big_picture_max_height, + MAX_IMAGE_SIZE + ) + } + private fun assertIsPlaceHolder(drawable: Drawable) { assertThat(drawable).isInstanceOf(PlaceHolderDrawable::class.java) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt new file mode 100644 index 000000000000..ed9405814e6a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt @@ -0,0 +1,21 @@ +/* + * 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.shared + +import com.google.common.truth.Correspondence + +val byKey: Correspondence<ActiveNotificationModel, String> = + Correspondence.transforming({ it?.key }, "has a key of") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 20197e3ed547..3dafb23c8a37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -82,13 +82,13 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; -import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository; -import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; @@ -171,8 +171,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; - private final NotificationListInteractor mNotificationListInteractor = - new NotificationListInteractor(new NotificationListRepository()); + private final SeenNotificationsInteractor mSeenNotificationsInteractor = + new SeenNotificationsInteractor(new ActiveNotificationListRepository()); private NotificationStackScrollLayoutController mController; @@ -504,7 +504,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Test public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() { initController(/* viewIsAttached= */ true); - mNotificationListInteractor.setHasFilteredOutSeenNotifications(true); + mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true); mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true); verify(mNotificationStackScrollLayout).updateFooter(); @@ -704,7 +704,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mUiEventLogger, mRemoteInputManager, mVisibilityLocationProviderDelegator, - mNotificationListInteractor, + mSeenNotificationsInteractor, mShadeController, mJankMonitor, mStackLogger, 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 c8cbe42fb0d5..a59cd87d78da 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 47671fbadd0a..000000000000 --- 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 6b3bd22d5e62..15c09b53938f 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/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index ee4f2089c05c..53c621d24601 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -53,7 +53,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; -import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor; +import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -79,8 +79,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { private CommandQueue mCommandQueue; private FakeMetricsLogger mMetricsLogger; private final ShadeController mShadeController = mock(ShadeController.class); - private final NotificationsInteractor mNotificationsInteractor = - mock(NotificationsInteractor.class); + private final NotificationAlertsInteractor mNotificationAlertsInteractor = + mock(NotificationAlertsInteractor.class); private final KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class); private final InitController mInitController = new InitController(); @@ -116,7 +116,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class), mKeyguardStateController, - mNotificationsInteractor, + mNotificationAlertsInteractor, mock(LockscreenShadeTransitionController.class), mock(PowerInteractor.class), mCommandQueue, @@ -226,7 +226,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { .setTag("a") .setNotification(n) .build(); - when(mNotificationsInteractor.areNotificationAlertsEnabled()).thenReturn(false); + when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false); assertTrue("When alerts aren't enabled, interruptions are suppressed", mInterruptSuppressor.suppressInterruptions(entry)); 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 000000000000..1e628bd35053 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt @@ -0,0 +1,80 @@ +/* + * 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.content.res.Configuration +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.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +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 val config = mock<Configuration>() + + 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()) + } + + @Test + fun onConfigurationChanged_calledInSubclass() { + var onConfigChangedCalled = false + val subclass = + object : SystemUIBottomSheetDialog(mContext, configurationController) { + override fun onConfigurationChanged() { + onConfigChangedCalled = true + } + } + + subclass.show() + + val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() + verify(configurationController).addCallback(capture(captor)) + captor.value.onConfigChanged(config) + + assertThat(onConfigChangedCalled).isTrue() + } +} 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 000000000000..4eb159103b49 --- /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 000000000000..065132300564 --- /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/util/kotlin/LayoutInflaterUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/LayoutInflaterUtilTest.kt new file mode 100644 index 000000000000..1c8465a482de --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/LayoutInflaterUtilTest.kt @@ -0,0 +1,137 @@ +/* + * 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.kotlin + +import android.content.Context +import android.testing.AndroidTestingRunner +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.view.reinflateAndBindLatest +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class LayoutInflaterUtilTest : SysuiTestCase() { + @JvmField @Rule val mockito = MockitoJUnit.rule() + + private var inflationCount = 0 + private var callbackCount = 0 + @Mock private lateinit var disposableHandle: DisposableHandle + + inner class TestLayoutInflater : LayoutInflater(context) { + override fun inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View { + inflationCount++ + return View(context) + } + + override fun cloneInContext(p0: Context?): LayoutInflater { + // not needed for this test + return this + } + } + + val underTest = TestLayoutInflater() + + @After + fun cleanUp() { + inflationCount = 0 + callbackCount = 0 + } + + @Test + fun testReinflateAndBindLatest_inflatesWithoutEmission() = runTest { + backgroundScope.launch { + underTest.reinflateAndBindLatest( + resource = 0, + root = null, + attachToRoot = false, + emptyFlow<Unit>() + ) { + callbackCount++ + null + } + } + + // Inflates without an emission + runCurrent() + assertThat(inflationCount).isEqualTo(1) + assertThat(callbackCount).isEqualTo(1) + } + + @Test + fun testReinflateAndBindLatest_reinflatesOnEmission() = runTest { + val observable = MutableSharedFlow<Unit>() + val flow = observable.asSharedFlow() + backgroundScope.launch { + underTest.reinflateAndBindLatest( + resource = 0, + root = null, + attachToRoot = false, + flow + ) { + callbackCount++ + null + } + } + + listOf(1, 2, 3).forEach { count -> + runCurrent() + assertThat(inflationCount).isEqualTo(count) + assertThat(callbackCount).isEqualTo(count) + observable.emit(Unit) + } + } + + @Test + fun testReinflateAndBindLatest_disposesOnCancel() = runTest { + val job = launch { + underTest.reinflateAndBindLatest( + resource = 0, + root = null, + attachToRoot = false, + emptyFlow() + ) { + callbackCount++ + disposableHandle + } + } + + runCurrent() + job.cancelAndJoin() + verify(disposableHandle).dispose() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt index aaf8d0761dce..6e3a732aa8ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.util.ui import android.testing.AndroidTestingRunner @@ -20,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.launchIn 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 468c5a73645b..fc2030f694ad 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/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt index 5dcc7423ecc6..8353cf78d983 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt @@ -34,7 +34,10 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor private val _scaleForResolution = MutableStateFlow(1f) override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow() - suspend fun onAnyConfigurationChange() { + private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>() + private val colors = mutableMapOf<Int, MutableStateFlow<Int>>() + + fun onAnyConfigurationChange() { _onAnyConfigurationChange.tryEmit(Unit) } @@ -42,12 +45,12 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor _scaleForResolution.value = scale } - override fun getResolutionScale(): Float { - return _scaleForResolution.value - } + override fun getResolutionScale(): Float = _scaleForResolution.value + + override fun getDimensionPixelSize(id: Int): Int = pixelSizes[id]?.value ?: 0 - override fun getDimensionPixelSize(id: Int): Int { - return 0 + fun setDimensionPixelSize(id: Int, pixelSize: Int) { + pixelSizes.getOrPut(id) { MutableStateFlow(pixelSize) }.value = pixelSize } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt index 5cd09d8df429..5fd0b4f61b43 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt @@ -43,9 +43,10 @@ fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay = mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) } /** Fake [DisplayRepository] implementation for testing. */ -class FakeDisplayRepository() : DisplayRepository { - private val flow = MutableSharedFlow<Set<Display>>() - private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>() +class FakeDisplayRepository : DisplayRepository { + private val flow = MutableSharedFlow<Set<Display>>(replay = 1) + private val pendingDisplayFlow = + MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1) /** Emits [value] as [displays] flow value. */ suspend fun emit(value: Set<Display>) = flow.emit(value) @@ -59,7 +60,7 @@ class FakeDisplayRepository() : DisplayRepository { override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> get() = pendingDisplayFlow - private val _displayChangeEvent = MutableSharedFlow<Int>() + private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1) override val displayChangeEvent: Flow<Int> = _displayChangeEvent suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt index 1cb4ab76c9d5..55935961466d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt @@ -19,62 +19,35 @@ package com.android.systemui.qs.tiles.base.interactor import javax.annotation.CheckReturnValue import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.flatMapLatest class FakeQSTileDataInteractor<T>( - private val dataFlow: MutableSharedFlow<FakeData<T>> = - MutableSharedFlow(replay = Int.MAX_VALUE), + private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE), private val availabilityFlow: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = Int.MAX_VALUE), ) : QSTileDataInteractor<T> { - private val mutableDataRequests = mutableListOf<QSTileDataRequest>() - val dataRequests: List<QSTileDataRequest> = mutableDataRequests + private val mutableDataRequests = mutableListOf<DataRequest>() + val dataRequests: List<DataRequest> = mutableDataRequests - private val mutableAvailabilityRequests = mutableListOf<Unit>() - val availabilityRequests: List<Unit> = mutableAvailabilityRequests + private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>() + val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests - @CheckReturnValue - fun emitData(data: T): FilterEmit = - object : FilterEmit { - override fun forRequest(request: QSTileDataRequest): Boolean = - dataFlow.tryEmit(FakeData(data, DataFilter.ForRequest(request))) - override fun forAnyRequest(): Boolean = dataFlow.tryEmit(FakeData(data, DataFilter.Any)) - } + @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data) fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable) suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable) - override fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<T> { - mutableDataRequests.add(qsTileDataRequest) - return dataFlow - .filter { - when (it.filter) { - is DataFilter.Any -> true - is DataFilter.ForRequest -> it.filter.request == qsTileDataRequest - } - } - .map { it.data } + override fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<T> { + mutableDataRequests.add(DataRequest(userId)) + return triggers.flatMapLatest { dataFlow } } - override fun availability(): Flow<Boolean> { - mutableAvailabilityRequests.add(Unit) + override fun availability(userId: Int): Flow<Boolean> { + mutableAvailabilityRequests.add(AvailabilityRequest(userId)) return availabilityFlow } - interface FilterEmit { - fun forRequest(request: QSTileDataRequest): Boolean - fun forAnyRequest(): Boolean - } - - class FakeData<T>( - val data: T, - val filter: DataFilter, - ) - - sealed class DataFilter { - object Any : DataFilter() - class ForRequest(val request: QSTileDataRequest) : DataFilter() - } + data class DataRequest(val userId: Int) + data class AvailabilityRequest(val userId: Int) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt index 9c99cb52d5ee..597d52dcb299 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt @@ -16,22 +16,19 @@ package com.android.systemui.qs.tiles.base.interactor -import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock class FakeQSTileUserActionInteractor<T> : QSTileUserActionInteractor<T> { private val mutex: Mutex = Mutex() - private val mutableInputs: MutableList<FakeInput<T>> = mutableListOf() + private val mutableInputs: MutableList<QSTileInput<T>> = mutableListOf() - val inputs: List<FakeInput<T>> = mutableInputs + val inputs: List<QSTileInput<T>> = mutableInputs - fun lastInput(): FakeInput<T>? = inputs.lastOrNull() + fun lastInput(): QSTileInput<T>? = inputs.lastOrNull() - override suspend fun handleInput(userAction: QSTileUserAction, currentData: T) { - mutex.withLock { mutableInputs.add(FakeInput(userAction, currentData)) } + override suspend fun handleInput(input: QSTileInput<T>) { + mutex.withLock { mutableInputs.add(input) } } - - data class FakeInput<T>(val userAction: QSTileUserAction, val data: T) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt index e59f642071fb..8f18e1331427 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt @@ -15,8 +15,10 @@ */ package com.android.systemui.statusbar.data +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepositoryModule import com.android.systemui.statusbar.disableflags.data.FakeStatusBarDisableFlagsDataLayerModule import com.android.systemui.statusbar.notification.data.FakeStatusBarNotificationsDataLayerModule +import com.android.systemui.statusbar.phone.data.FakeStatusBarPhoneDataLayerModule import com.android.systemui.statusbar.pipeline.data.FakeStatusBarPipelineDataLayerModule import com.android.systemui.statusbar.policy.data.FakeStatusBarPolicyDataLayerModule import dagger.Module @@ -25,7 +27,9 @@ import dagger.Module includes = [ FakeStatusBarDisableFlagsDataLayerModule::class, + FakeStatusBarModeRepositoryModule::class, FakeStatusBarNotificationsDataLayerModule::class, + FakeStatusBarPhoneDataLayerModule::class, FakeStatusBarPipelineDataLayerModule::class, FakeStatusBarPolicyDataLayerModule::class, ] diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt index 61ba4649f559..f25d282208f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt @@ -18,17 +18,17 @@ package com.android.systemui.statusbar.data.repository import com.android.systemui.statusbar.data.model.StatusBarAppearance import com.android.systemui.statusbar.data.model.StatusBarMode -import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow -class FakeStatusBarModeRepository : StatusBarModeRepository { +class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepository { override val isTransientShown = MutableStateFlow(false) override val isInFullscreenMode = MutableStateFlow(false) override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null) override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT) - override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {} - override fun showTransient() { isTransientShown.value = true } @@ -36,3 +36,8 @@ class FakeStatusBarModeRepository : StatusBarModeRepository { isTransientShown.value = false } } + +@Module +interface FakeStatusBarModeRepositoryModule { + @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt new file mode 100644 index 000000000000..d2c3b7a090bd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt @@ -0,0 +1,21 @@ +/* + * 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.data + +import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepositoryModule +import dagger.Module + +@Module(includes = [FakeDarkIconRepositoryModule::class]) object FakeStatusBarPhoneDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt new file mode 100644 index 000000000000..50d3f0a106f2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt @@ -0,0 +1,33 @@ +/* + * 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.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +@SysUISingleton +class FakeDarkIconRepository @Inject constructor() : DarkIconRepository { + override val darkState = MutableStateFlow(DarkChange(emptyList(), 0f, 0)) +} + +@Module +interface FakeDarkIconRepositoryModule { + @Binds fun bindFake(fake: FakeDarkIconRepository): DarkIconRepository +} 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 5ffc094b88b3..7473ca6a6486 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 6743515c2ec7..003013e18583 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 6e87beeb295f..ea6786e6c4bc 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 000000000000..61b0b40a55bf --- /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 3406102b28ac..98421a9e1d3e 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 dc1126edde41..4c224fb33b26 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 1a735f89a2bc..75ecdb78fe00 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 e65a185adfd2..87f9cf10f824 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 898cdcc30e0f..4dca5a5c807b 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 beea063221fb..8624dd5f3e6b 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 55aa7164a67b..003046ab884f 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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 31817f1c427d..bdb5d9356a0d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5476,7 +5476,7 @@ public class ActivityManagerService extends IActivityManager.Stub + " Calling package: " + packageName + "; intent: " + intent + "; options: " + options); } - target.send(code, intent, resolvedType, allowlistToken, null, + target.send(code, intent, resolvedType, null, null, requiredPermission, options); } catch (RemoteException e) { } diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java index 9b5f18caf71a..710278d6b3c6 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 e0e6cade5f27..59d8e7e96ba6 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/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index 940c58b7a5f0..354f3d3d13e0 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -23,6 +23,7 @@ import android.app.IApplicationThread; import android.app.ProcessMemoryState.HostingComponentType; import android.content.pm.ApplicationInfo; import android.os.Debug; +import android.os.Process; import android.os.SystemClock; import android.util.DebugUtils; import android.util.TimeUtils; @@ -271,15 +272,17 @@ final class ProcessProfileRecord { origBase.makeInactive(); } final ApplicationInfo info = mApp.info; + final int attributionUid = getUidForAttribution(mApp); final ProcessState baseProcessTracker = tracker.getProcessStateLocked( - info.packageName, info.uid, info.longVersionCode, mApp.processName); + info.packageName, attributionUid, info.longVersionCode, + mApp.processName); setBaseProcessTracker(baseProcessTracker); baseProcessTracker.makeActive(); pkgList.forEachPackage((pkgName, holder) -> { if (holder.state != null && holder.state != origBase) { holder.state.makeInactive(); } - tracker.updateProcessStateHolderLocked(holder, pkgName, mApp.info.uid, + tracker.updateProcessStateHolderLocked(holder, pkgName, attributionUid, mApp.info.longVersionCode, mApp.processName); if (holder.state != baseProcessTracker) { holder.state.makeActive(); @@ -536,7 +539,7 @@ final class ProcessProfileRecord { tracker.reportCachedKill(pkgList.getPackageListLocked(), mLastCachedPss); pkgList.forEachPackageProcessStats(holder -> FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED, - mApp.info.uid, + getUidForAttribution(mApp), holder.state.getName(), holder.state.getPackage(), mLastCachedPss, @@ -595,6 +598,21 @@ final class ProcessProfileRecord { tracker.mPendingMemState = -1; } + /** + * Returns the uid that should be used for attribution purposes in profiling / stats. + * + * In most cases this returns the uid of the process itself. For isolated processes though, + * since the process uid is dynamically allocated and can't easily be traced back to the app, + * for attribution we use the app package uid. + */ + private static int getUidForAttribution(ProcessRecord processRecord) { + if (Process.isIsolatedUid(processRecord.uid)) { + return processRecord.info.uid; + } else { + return processRecord.uid; + } + } + @GuardedBy("mProfilerLock") int getPid() { return mPid; diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index aa788ebb6ba7..a57a785153e0 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -145,6 +145,7 @@ public class SettingsToPropertiesMapper { "media_drm", "media_solutions", "nfc", + "pdf_viewer", "pixel_audio_android", "pixel_system_sw_touch", "pixel_watch", diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index bb9ea285385b..cbaf05bb4e23 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 ba43c8df92c5..292fc14ac6eb 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/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 3d347bea6bae..f9bc8dcc7d0b 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/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index a7889684aeea..e5f01df75fcd 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2307,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); @@ -2589,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) { diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index d97c8e71c73c..8c39d7de54f7 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/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java index 5d5d59bcb6ef..8cb334dc2260 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 33190ade4f42..360a6a721988 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 000000000000..5bad06777328 --- /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 8cbc368467bb..58927d14ee23 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -76,7 +76,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; @@ -162,7 +161,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 +196,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 +292,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); @@ -379,10 +384,12 @@ public final class MediaProjectionManagerService extends SystemService if (mProjectionGrant != null) { // Cache the session details. mProjectionGrant.mSession = incomingSession; - mMediaProjectionMetricsLogger.notifyProjectionStateChange( - mProjectionGrant.uid, - FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, - FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + if (incomingSession != null) { + // Only log in progress when session is not null. + // setContentRecordingSession is called with a null session for the stop case. + mMediaProjectionMetricsLogger.logInProgress( + mProjectionGrant.uid, incomingSession.getTargetUid()); + } dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession); } return true; @@ -452,6 +459,21 @@ 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); + } + + @VisibleForTesting + void notifyPermissionRequestDisplayed(int hostUid) { + mMediaProjectionMetricsLogger.logPermissionRequestDisplayed(hostUid); + } + + @VisibleForTesting + void notifyAppSelectorDisplayed(int hostUid) { + mMediaProjectionMetricsLogger.logAppSelectorDisplayed(hostUid); + } + /** * Handles result of dialog shown from * {@link BinderService#buildReviewGrantedConsentIntentLocked()}. @@ -842,6 +864,43 @@ 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 + @EnforcePermission(MANAGE_MEDIA_PROJECTION) + public void notifyPermissionRequestDisplayed(int hostUid) { + notifyPermissionRequestDisplayed_enforcePermission(); + final long token = Binder.clearCallingIdentity(); + try { + MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + @EnforcePermission(MANAGE_MEDIA_PROJECTION) + public void notifyAppSelectorDisplayed(int hostUid) { + notifyAppSelectorDisplayed_enforcePermission(); + final long token = Binder.clearCallingIdentity(); + try { + MediaProjectionManagerService.this.notifyAppSelectorDisplayed(hostUid); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; final long token = Binder.clearCallingIdentity(); 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 f18ecad09c42..55a30bf225a3 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,197 @@ 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_APP_SELECTOR_DISPLAYED; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; +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_PERMISSION_REQUEST_DISPLAYED; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED; + +import android.content.Context; +import android.util.Log; 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 String TAG = "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) { + Log.d(TAG, "logInitiated"); + Duration durationSinceLastActiveSession = mTimestampStore.timeSinceLastActiveSession(); + int timeSinceLastActiveInSeconds = + durationSinceLastActiveSession == null + ? TIME_SINCE_LAST_ACTIVE_UNKNOWN + : (int) durationSinceLastActiveSession.toSeconds(); + write( + mSessionIdGenerator.createAndGetNewSessionId(), + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED, + hostUid, + TARGET_UID_UNKNOWN, + timeSinceLastActiveInSeconds, + sessionCreationSource); + } + + /** + * Logs that the user entered the setup flow and permission dialog is displayed. This state is + * not sent when the permission is already granted and we skipped showing the permission dialog. + * + * @param hostUid UID of the package that initiates MediaProjection. + */ + public void logPermissionRequestDisplayed(int hostUid) { + Log.d(TAG, "logPermissionRequestDisplayed"); + write( + mSessionIdGenerator.getCurrentSessionId(), + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED, + hostUid, + TARGET_UID_UNKNOWN, + TIME_SINCE_LAST_ACTIVE_UNKNOWN, + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + + /** + * Logs that the app selector dialog is shown for the user. + * + * @param hostUid UID of the package that initiates MediaProjection. + */ + public void logAppSelectorDisplayed(int hostUid) { + Log.d(TAG, "logAppSelectorDisplayed"); + write( + mSessionIdGenerator.getCurrentSessionId(), + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED, + hostUid, + TARGET_UID_UNKNOWN, + TIME_SINCE_LAST_ACTIVE_UNKNOWN, + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + + /** + * Logs that the virtual display is created and capturing the selected region begins. + * + * @param hostUid UID of the package that initiates MediaProjection. + * @param targetUid UID of the package that is captured if selected. + */ + public void logInProgress(int hostUid, int targetUid) { + Log.d(TAG, "logInProgress"); + write( + mSessionIdGenerator.getCurrentSessionId(), + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, + hostUid, + targetUid, + TIME_SINCE_LAST_ACTIVE_UNKNOWN, + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + + /** + * Logs that the capturing stopped, either normally or because of error. + * + * @param hostUid UID of the package that initiates MediaProjection. + * @param targetUid UID of the package that is captured if selected. + */ + public void logStopped(int hostUid, int targetUid) { + boolean wasCaptureInProgress = + mPreviousState + == MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; + Log.d(TAG, "logStopped: wasCaptureInProgress=" + wasCaptureInProgress); + write( + mSessionIdGenerator.getCurrentSessionId(), + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED, + hostUid, + targetUid, + TIME_SINCE_LAST_ACTIVE_UNKNOWN, + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + + if (wasCaptureInProgress) { + 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 ff70cb35f9dc..244de0b3dd99 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 4026d0c43484..bfec58c8983d 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 b4d36db96c01..7ca56990f2d0 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/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 7556f27f208a..a5c5ae270261 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -16,7 +16,7 @@ package com.android.server.pm; -import static android.content.pm.Flags.preventSdkLibApp; +import static android.content.pm.Flags.disallowSdkLibsToBeApps; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; @@ -113,7 +113,6 @@ import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; -import android.content.pm.Flags; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; @@ -162,7 +161,6 @@ import com.android.internal.util.CollectionUtils; import com.android.server.EventLogTags; import com.android.server.LocalManagerRegistry; import com.android.server.SystemConfig; -import com.android.server.art.model.ArtFlags; import com.android.server.art.model.DexoptParams; import com.android.server.art.model.DexoptResult; import com.android.server.pm.Installer.LegacyDexoptDisabledException; @@ -998,7 +996,7 @@ final class InstallPackageHelper { } final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0; final boolean isSdkLibrary = packageToScan.isSdkLibrary(); - if (isApex || (isSdkLibrary && preventSdkLibApp())) { + if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) { request.getScannedPackageSetting().setAppId(Process.INVALID_UID); } else { createdAppId.put(packageName, optimisticallyRegisterAppId(request)); @@ -2564,15 +2562,8 @@ final class InstallPackageHelper { LocalManagerRegistry.getManager(PackageManagerLocal.class); try (PackageManagerLocal.FilteredSnapshot snapshot = packageManagerLocal.withFilteredSnapshot()) { - boolean ignoreDexoptProfile = - (installRequest.getInstallFlags() - & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE) - != 0; - /*@DexoptFlags*/ int extraFlags = - ignoreDexoptProfile && Flags.useArtServiceV2() - ? ArtFlags.FLAG_IGNORE_PROFILE - : 0; - DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags); + DexoptParams params = + dexoptOptions.convertToDexoptParams(0 /* extraFlags */); DexoptResult dexOptResult = DexOptHelper.getArtManagerLocal().dexoptPackage( snapshot, packageName, params); installRequest.onDexoptFinished(dexOptResult); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index a161e8c39ca2..1135466ae1d4 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -65,6 +65,7 @@ import android.content.pm.IncrementalStatesInfo; import android.content.pm.LauncherActivityInfoInternal; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.LauncherUserInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; @@ -1377,6 +1378,25 @@ public class LauncherAppsService extends SystemService { } @Override + public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) { + // Only system launchers, which have access to recents should have access to this API. + // TODO(b/303803157): Add the new permission check if we decide to have one. + if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) { + throw new SecurityException("Caller is not the recents app"); + } + if (!canAccessProfile(user.getIdentifier(), + "Can't access LauncherUserInfo for another user")) { + return null; + } + long ident = injectClearCallingIdentity(); + try { + return mUserManagerInternal.getLauncherUserInfo(user.getIdentifier()); + } finally { + injectRestoreCallingIdentity(ident); + } + } + + @Override public void startActivityAsUser(IApplicationThread caller, String callingPackage, String callingFeatureId, ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException { diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 0fb1f7a0780c..781e5f891a43 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -38,6 +38,8 @@ import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; import android.content.pm.VersionedPackage; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -48,6 +50,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Environment; import android.os.ParcelableException; +import android.os.Process; import android.os.SELinux; import android.os.UserHandle; import android.text.TextUtils; @@ -162,15 +165,13 @@ public class PackageArchiver { }); } - /** - * Creates archived state for the package and user. - */ - public CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId) + /** Creates archived state for the package and user. */ + private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId) throws PackageManager.NameNotFoundException { PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(), Binder.getCallingUid(), userId); String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); - verifyInstaller(responsibleInstallerPackage); + verifyInstaller(responsibleInstallerPackage, userId); List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(), userId); @@ -268,27 +269,34 @@ public class PackageArchiver { return iconFile.toPath(); } - private void verifyInstaller(String installerPackage) + private void verifyInstaller(String installerPackage, int userId) throws PackageManager.NameNotFoundException { if (TextUtils.isEmpty(installerPackage)) { throw new PackageManager.NameNotFoundException("No installer found"); } - if (!verifySupportsUnarchival(installerPackage)) { + // Allow shell for easier development. + if ((Binder.getCallingUid() != Process.SHELL_UID) + && !verifySupportsUnarchival(installerPackage, userId)) { throw new PackageManager.NameNotFoundException("Installer does not support unarchival"); } } /** - * @return true if installerPackage support unarchival: - * - has an action Intent.ACTION_UNARCHIVE_PACKAGE, - * - has permissions to install packages. + * Returns true if {@code installerPackage} supports unarchival being able to handle + * {@link Intent#ACTION_UNARCHIVE_PACKAGE} */ - public boolean verifySupportsUnarchival(String installerPackage) { - // TODO(b/278553670) Check if installerPackage supports unarchival. + public boolean verifySupportsUnarchival(String installerPackage, int userId) { if (TextUtils.isEmpty(installerPackage)) { return false; } - return true; + + Intent intent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE).setPackage(installerPackage); + + ParceledListSlice<ResolveInfo> intentReceivers = + Binder.withCleanCallingIdentity( + () -> mPm.queryIntentReceivers(mPm.snapshotComputer(), + intent, /* resolvedType= */ null, /* flags= */ 0, userId)); + return intentReceivers != null && !intentReceivers.getList().isEmpty(); } void requestUnarchive( diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index d0e5f96f8d0f..5dc7dab73141 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -3445,7 +3445,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } if (!mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival( - getInstallSource().mInstallerPackageName)) { + getInstallSource().mInstallerPackageName, userId)) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_SESSION_INVALID, "Installer has to support unarchival in order to install archived " diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index d4abad8499f2..7264e2eff4aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3703,9 +3703,6 @@ class PackageManagerShellCommand extends ShellCommand { sessionParams.installFlags |= PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK; break; - case "--ignore-dexopt-profile": - sessionParams.installFlags |= PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE; - break; default: throw new IllegalArgumentException("Unknown option " + opt); } @@ -4802,7 +4799,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" [--enable-rollback]"); pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); pw.println(" [--apex] [--non-staged] [--force-non-staged]"); - pw.println(" [--staged-ready-timeout TIMEOUT] [--ignore-dexopt-profile]"); + pw.println(" [--staged-ready-timeout TIMEOUT]"); pw.println(" [PATH [SPLIT...]|-]"); pw.println(" Install an application. Must provide the apk data to install, either as"); pw.println(" file path(s) or '-' to read from stdin. Options are:"); @@ -4842,13 +4839,6 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" milliseconds for pre-reboot verification to complete when"); pw.println(" performing staged install. This flag is used to alter the waiting"); pw.println(" time. You can skip the waiting time by specifying a TIMEOUT of '0'"); - pw.println(" --ignore-dexopt-profile: If set, all profiles are ignored by dexopt"); - pw.println(" during the installation, including the profile in the DM file and"); - pw.println(" the profile embedded in the APK file. If an invalid profile is"); - pw.println(" provided during installation, no warning will be reported by `adb"); - pw.println(" install`."); - pw.println(" This option does not affect later dexopt operations (e.g.,"); - pw.println(" background dexopt and manual `pm compile` invocations)."); pw.println(""); pw.println(" install-existing [--user USER_ID|all|current]"); pw.println(" [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE"); diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index 29d99a73a034..e8cebefb8631 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -120,8 +120,8 @@ public final class SuspendPackageHelper { return packageNames; } - final SuspendParams newSuspendParams = - new SuspendParams(dialogInfo, appExtras, launcherExtras, quarantined); + final SuspendParams newSuspendParams = suspended + ? new SuspendParams(dialogInfo, appExtras, launcherExtras, quarantined) : null; final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length); @@ -156,8 +156,8 @@ public final class SuspendPackageHelper { final WatchedArrayMap<String, SuspendParams> suspendParamsMap = packageState.getUserStateOrDefault(userId).getSuspendParams(); - SuspendParams oldSuspendParams = suspendParamsMap == null - ? null : suspendParamsMap.get(packageName); + final SuspendParams oldSuspendParams = suspendParamsMap == null + ? null : suspendParamsMap.get(callingPackage); boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams); if (suspended && !changed) { diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 04cd183327fb..0e7ce2efc8b8 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; +import android.content.pm.LauncherUserInfo; import android.content.pm.UserInfo; import android.content.pm.UserProperties; import android.graphics.Bitmap; @@ -407,6 +408,11 @@ public abstract class UserManagerInternal { public abstract @NonNull UserInfo[] getUserInfos(); /** + * Gets a {@link LauncherUserInfo} for the given {@code userId}, or {@code null} if not found. + */ + public abstract @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId); + + /** * Sets all default cross profile intent filters between {@code parentUserId} and * {@code profileUserId}. */ diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7331bc141ec2..154ee6eda138 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -62,6 +62,7 @@ import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.LauncherUserInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; @@ -7153,6 +7154,24 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId) { + UserInfo userInfo; + synchronized (mUsersLock) { + userInfo = getUserInfoLU(userId); + } + if (userInfo != null) { + final UserTypeDetails userDetails = getUserTypeDetails(userInfo); + final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder( + userDetails.getName(), + userInfo.serialNumber) + .build(); + return uiInfo; + } else { + return null; + } + } + + @Override public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) { int state; synchronized (mUserStates) { diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 46121dcd9dae..8240c47a607b 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -18,7 +18,7 @@ package com.android.server.pm.pkg.parsing; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; -import static android.content.pm.Flags.preventSdkLibApp; +import static android.content.pm.Flags.disallowSdkLibsToBeApps; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -404,7 +404,7 @@ public class ParsingPackageUtils { try { final File baseApk = new File(lite.getBaseApkPath()); - boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp(); + boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps(); final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, lite.getPath(), assetLoader, flags, shouldSkipComponents); if (result.isError()) { @@ -458,7 +458,7 @@ public class ParsingPackageUtils { final PackageLite lite = liteResult.getResult(); final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { - boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp(); + boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps(); final ParseResult<ParsingPackage> result = parseBaseApk(input, apkFile, apkFile.getCanonicalPath(), diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 3a6664a72439..7c0fc998abcf 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 c54e3bdf0d51..5f8bbe5f18ad 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 c7a3c4349f4c..bdcde66a8102 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; @@ -3981,11 +3434,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } @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/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 01b8bf794dae..52ab9b855c7c 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -539,7 +539,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord if (usf != null) { mUserSavedFiles.get(userId).remove(code); mSavedFilesInOrder.remove(usf); - mPersister.removeSnap(code, userId); + mPersister.removeSnapshot(code, userId); } } diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java index d604402e3e1e..5db02dff8351 100644 --- a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java @@ -58,7 +58,7 @@ class BaseAppSnapshotPersister { * @param id The id of task that has been removed. * @param userId The id of the user the task belonged to. */ - void removeSnap(int id, int userId) { + void removeSnapshot(int id, int userId) { synchronized (mLock) { mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue .createDeleteWriteQueueItem(id, userId, mPersistInfoProvider)); diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 06448d0c1e84..022ef6152929 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,10 +37,12 @@ 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}. @@ -47,6 +50,16 @@ import com.android.internal.protolog.common.ProtoLog; 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. */ @NonNull @@ -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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index b7b5c2af0e3e..4fa6e2990faf 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -937,6 +937,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // so we still request the window to resize if the current frame is empty. if (!w.getFrame().isEmpty()) { w.updateLastFrames(); + mWmService.mFrameChangingWindows.remove(w); } w.onResizeHandled(); } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 7a442e708130..2281395f8cb5 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -847,6 +847,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } handleResizingWindows(); + clearFrameChangingWindows(); if (mWmService.mDisplayFrozen) { ProtoLog.v(WM_DEBUG_ORIENTATION, @@ -1015,6 +1016,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } /** + * Clears frame changing windows after handling moving and resizing windows. + */ + private void clearFrameChangingWindows() { + final ArrayList<WindowState> frameChangingWindows = mWmService.mFrameChangingWindows; + for (int i = frameChangingWindows.size() - 1; i >= 0; i--) { + frameChangingWindows.get(i).updateLastFrames(); + } + frameChangingWindows.clear(); + } + + /** * @param w WindowState this method is applied to. * @param obscured True if there is a window on top of this obscuring the display. * @param syswin System window? diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java index 2e7ff7a6b9b8..2be2a1a363db 100644 --- a/services/core/java/com/android/server/wm/SnapshotController.java +++ b/services/core/java/com/android/server/wm/SnapshotController.java @@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import android.os.Trace; import android.view.WindowManager; +import android.window.TaskSnapshot; import java.io.PrintWriter; import java.util.ArrayList; @@ -85,7 +86,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. @@ -126,6 +127,18 @@ class SnapshotController { } mActivitySnapshotController.handleTransitionFinish(windows); mActivitySnapshotController.endSnapshotProcess(); + // Remove task snapshot if it is visible at the end of transition. + for (int i = changeInfos.size() - 1; i >= 0; --i) { + final WindowContainer wc = changeInfos.get(i).mContainer; + final Task task = wc.asTask(); + if (task != null && wc.isVisibleRequested()) { + final TaskSnapshot snapshot = mTaskSnapshotController.getSnapshot(task.mTaskId, + task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */); + if (snapshot != null) { + mTaskSnapshotController.removeAndDeleteSnapshot(task.mTaskId, task.mUserId); + } + } + } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 4922e9028ed9..71dbd29a0410 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1333,7 +1333,7 @@ class Task extends TaskFragment { clearRootProcess(); - mAtmService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents( + mAtmService.mWindowManager.mTaskSnapshotController.removeAndDeleteSnapshot( mTaskId, mUserId); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 2b12e7497233..d8e18e47fa89 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -284,9 +284,9 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot } } - void notifyTaskRemovedFromRecents(int taskId, int userId) { + void removeAndDeleteSnapshot(int taskId, int userId) { mCache.onIdRemoved(taskId); - mPersister.onTaskRemovedFromRecents(taskId, userId); + mPersister.removeSnapshot(taskId, userId); } void removeSnapshotCache(int taskId) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java index 3e8c0177b3b3..233daadfc496 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java @@ -67,10 +67,11 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister { * @param taskId The id of task that has been removed. * @param userId The id of the user the task belonged to. */ - void onTaskRemovedFromRecents(int taskId, int userId) { + @Override + void removeSnapshot(int taskId, int userId) { synchronized (mLock) { mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId); - super.removeSnap(taskId, userId); + super.removeSnapshot(taskId, userId); } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 94e66ffd8373..33ef3c5629e3 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 1ed14310a500..15bd6078dc2d 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/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java index fbd226e46edf..1456184beb1e 100644 --- a/services/core/java/com/android/server/wm/WindowFrames.java +++ b/services/core/java/com/android/server/wm/WindowFrames.java @@ -51,7 +51,8 @@ public class WindowFrames { final Rect mFrame = new Rect(); /** - * The last real frame that was reported to the client. + * The frame used to check if mFrame is changed, e.g., moved or resized. It will be committed + * after handling the moving or resizing windows. */ final Rect mLastFrame = new Rect(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 88f72f9dbc90..4a074ff25c74 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -588,6 +588,12 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mResizingWindows = new ArrayList<>(); /** + * Windows that their frames are being changed. Used so we can clear the frame-changing states + * after handling the moved or resized windows. + */ + final ArrayList<WindowState> mFrameChangingWindows = new ArrayList<>(); + + /** * Mapping of displayId to {@link DisplayImePolicy}. * Note that this can be accessed without holding the lock. */ diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 3a793e921c68..f14a6f912af1 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1200,6 +1200,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void updateTrustedOverlay() { mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl, isWindowTrustedOverlay()); + mInputWindowHandle.forceChange(); } boolean isWindowTrustedOverlay() { @@ -1346,6 +1347,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP windowFrames.setContentChanged(true); } + if (!windowFrames.mFrame.equals(windowFrames.mLastFrame) + || !windowFrames.mRelFrame.equals(windowFrames.mLastRelFrame)) { + mWmService.mFrameChangingWindows.add(this); + } + if (mAttrs.type == TYPE_DOCK_DIVIDER) { if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) { mMovedByResize = true; @@ -3716,10 +3722,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mDragResizingChangeReported = true; mWindowFrames.clearReportResizeHints(); - // We update mLastFrame always rather than in the conditional with the last inset - // variables, because mFrameSizeChanged only tracks the width and height changing. - updateLastFrames(); - final int prevRotation = mLastReportedConfiguration .getMergedConfiguration().windowConfiguration.getRotation(); fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 922f69c1e816..323d387eff1a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -24,6 +24,7 @@ import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMIT import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED; import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED; import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET; +import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT; import android.Manifest; @@ -40,7 +41,6 @@ import android.app.admin.PolicyUpdateReceiver; import android.app.admin.PolicyValue; import android.app.admin.TargetUser; import android.app.admin.UserRestrictionPolicyKey; -import android.app.admin.flags.FlagUtils; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -159,7 +159,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); - if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + if (devicePolicySizeTrackingEnabled()) { if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value, policyDefinition, userId)) { return; @@ -282,7 +282,7 @@ final class DevicePolicyEngine { } PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); - if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + if (devicePolicySizeTrackingEnabled()) { decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin); } @@ -428,7 +428,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); - if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + if (devicePolicySizeTrackingEnabled()) { if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value, policyDefinition, UserHandle.USER_ALL)) { return; @@ -499,7 +499,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition); - if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + if (devicePolicySizeTrackingEnabled()) { decreasePolicySizeForAdmin(policyState, enforcingAdmin); } @@ -1781,7 +1781,7 @@ final class DevicePolicyEngine { private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer) throws IOException { - if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + if (devicePolicySizeTrackingEnabled()) { if (mAdminPolicySize != null) { for (int i = 0; i < mAdminPolicySize.size(); i++) { int userId = mAdminPolicySize.keyAt(i); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8509155861f5..5f2d87cf7e80 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -219,6 +219,7 @@ import static android.app.admin.ProvisioningException.ERROR_REMOVE_NON_REQUIRED_ import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED; import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED; import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED; +import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled; import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE; import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -330,7 +331,6 @@ import android.app.admin.SystemUpdatePolicy; import android.app.admin.UnsafeStateException; import android.app.admin.UserRestrictionPolicyKey; import android.app.admin.WifiSsidPolicy; -import android.app.admin.flags.FlagUtils; import android.app.backup.IBackupManager; import android.app.compat.CompatChanges; import android.app.role.OnRoleHoldersChangedListener; @@ -3430,7 +3430,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } revertTransferOwnershipIfNecessaryLocked(); - if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) { + if (!policyEngineMigrationV2Enabled()) { updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked()); } } @@ -21571,7 +21571,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Objects.requireNonNull(packageName, "Admin package name must be provided"); final CallerIdentity caller = getCallerIdentity(packageName); - if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) { + if (!policyEngineMigrationV2Enabled()) { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), "USB data signaling can only be controlled by a device owner or " @@ -21581,7 +21581,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } synchronized (getLockObject()) { - if (FlagUtils.isPolicyEngineMigrationV2Enabled()) { + if (policyEngineMigrationV2Enabled()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, caller.getPackageName(), @@ -21621,7 +21621,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isUsbDataSignalingEnabled(String packageName) { final CallerIdentity caller = getCallerIdentity(packageName); - if (FlagUtils.isPolicyEngineMigrationV2Enabled()) { + if (policyEngineMigrationV2Enabled()) { Boolean enabled = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.USB_DATA_SIGNALING, caller.getUserId()); diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp index 6eacef767042..c617ec49ab32 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 c4906041ea5d..304f605d5b95 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 bee7c4019fc1..b826590b7440 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 000000000000..0c5d36e38972 --- /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/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index 610ea903767e..7a6ac4ef8f26 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; @@ -44,6 +45,8 @@ import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; import android.content.pm.VersionedPackage; import android.content.res.Resources; import android.graphics.Bitmap; @@ -122,6 +125,8 @@ public class PackageArchiverTest { private PackageSetting mPackageSetting; + private PackageManagerService mPackageManagerService; + private PackageArchiver mArchiveManager; @Before @@ -130,7 +135,7 @@ public class PackageArchiverTest { rule.system().stageNominalSystemState(); when(rule.mocks().getInjector().getPackageInstallerService()).thenReturn( mInstallerService); - PackageManagerService pm = spy(new PackageManagerService(rule.mocks().getInjector(), + mPackageManagerService = spy(new PackageManagerService(rule.mocks().getInjector(), /* factoryTest= */false, MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint, /* isEngBuild= */ false, @@ -154,15 +159,18 @@ public class PackageArchiverTest { when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( mLauncherActivityInfos); - doReturn(mComputer).when(pm).snapshotComputer(); + doReturn(mComputer).when(mPackageManagerService).snapshotComputer(); when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn( Binder.getCallingUid()); when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn( mock(Resources.class)); + doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class)))) + .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(), + eq(mUserId)); - mArchiveManager = spy(new PackageArchiver(mContext, pm)); + mArchiveManager = spy(new PackageArchiver(mContext, mPackageManagerService)); doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE), any(LauncherActivityInfo.class), eq(mUserId), anyInt()); doReturn(mIcon).when(mArchiveManager).decodeIcon( @@ -237,6 +245,21 @@ public class PackageArchiverTest { } @Test + public void archiveApp_installerDoesntSupportUnarchival() { + doReturn(new ParceledListSlice<>(List.of())) + .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(), + eq(mUserId)); + + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + "Installer does not support unarchival"); + } + + @Test public void archiveApp_noMainActivities() { when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( List.of()); 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 eefe5af314a6..3dbab1360af4 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 6bfd93b66f61..4bb7d63995ac 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 f94aff706a67..4e6dd064165d 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 @@ -26,10 +26,10 @@ import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN; 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; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; @@ -39,6 +39,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; @@ -67,7 +68,6 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.testutils.OffsettableClock; import com.android.server.wm.WindowManagerInternal; @@ -133,7 +133,7 @@ public class MediaProjectionManagerServiceTest { private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector = new MediaProjectionManagerService.Injector() { @Override - MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { + MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) { return mMediaProjectionMetricsLogger; } }; @@ -311,6 +311,70 @@ public class MediaProjectionManagerServiceTest { } @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, mPreventReusedTokenDisabledInjector); @@ -586,6 +650,40 @@ 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); + } + + @Test + public void notifyPermissionRequestDisplayed_forwardsToLogger() { + int hostUid = 123; + mService = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + + mService.notifyPermissionRequestDisplayed(hostUid); + + verify(mMediaProjectionMetricsLogger).logPermissionRequestDisplayed(hostUid); + } + + @Test + public void notifyAppSelectorDisplayed_forwardsToLogger() { + int hostUid = 456; + mService = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + + mService.notifyAppSelectorDisplayed(hostUid); + + verify(mMediaProjectionMetricsLogger).logAppSelectorDisplayed(hostUid); + } + /** * Executes and validates scenario where the consent result indicates the projection ends. */ @@ -749,18 +847,79 @@ 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)); service.setContentRecordingSession(DISPLAY_SESSION); - verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange( + verify(mMediaProjectionMetricsLogger).logInProgress( projection.uid, - MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, - FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN + DISPLAY_SESSION.getTargetUid() + ); + } + + @Test + public void setContentRecordingSession_taskSession_logsCaptureInProgressWithTargetUid() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService service = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = + startProjectionPreconditions(service); + projection.start(mIMediaProjectionCallback); + doReturn(true) + .when(mWindowManagerInternal) + .setContentRecordingSession(any(ContentRecordingSession.class)); + int targetUid = 123455; + + ContentRecordingSession taskSession = + ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid); + service.setContentRecordingSession(taskSession); + + verify(mMediaProjectionMetricsLogger).logInProgress(projection.uid, targetUid); + } + + @Test + public void setContentRecordingSession_failure_doesNotLogCaptureInProgress() throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService service = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = + startProjectionPreconditions(service); + projection.start(mIMediaProjectionCallback); + doReturn(false).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + + service.setContentRecordingSession(DISPLAY_SESSION); + + verify(mMediaProjectionMetricsLogger, never()).logInProgress( + anyInt(), + anyInt() + ); + } + + @Test + public void setContentRecordingSession_sessionNull_doesNotLogCaptureInProgress() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService service = + new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); + MediaProjectionManagerService.MediaProjection projection = + startProjectionPreconditions(service); + projection.start(mIMediaProjectionCallback); + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + + service.setContentRecordingSession(null); + + verify(mMediaProjectionMetricsLogger, never()).logInProgress( + anyInt(), + anyInt() ); } 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 000000000000..410604f02e1f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java @@ -0,0 +1,558 @@ +/* + * 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_APP_SELECTOR_DISPLAYED; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS; +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_PERMISSION_REQUEST_DISPLAYED; +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.never; +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.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_capturingWasInProgress_registersActiveSessionEnded() { + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verify(mTimestampStore).registerActiveSessionEnded(); + } + + @Test + public void logStopped_capturingWasNotInProgress_doesNotRegistersActiveSessionEnded() { + mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID); + + verify(mTimestampStore, never()).registerActiveSessionEnded(); + } + + @Test + public void logInProgress_logsStateChangedAtomId() { + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + + verifyStateChangedAtomIdLogged(); + } + + @Test + public void logInProgress_logsCurrentSessionId() { + int currentSessionId = 987; + when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId); + + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + + verifySessionIdLogged(currentSessionId); + } + + @Test + public void logInProgress_logsStateInProgress() { + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + + verifyStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS); + } + + @Test + public void logInProgress_logsHostUid() { + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + + verifyHostUidLogged(TEST_HOST_UID); + } + + @Test + public void logInProgress_logsTargetUid() { + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + + verifyTargetUidLogged(TEST_TARGET_UID); + } + + @Test + public void logInProgress_logsUnknownTimeSinceLastActive() { + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + + verifyTimeSinceLastActiveSessionLogged(-1); + } + + @Test + public void logInProgress_logsUnknownSessionCreationSource() { + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + + verifyCreationSourceLogged( + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + + @Test + public void logInProgress_logsPreviousState() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN); + + mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED); + + mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS); + + mLogger.logInProgress(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED); + } + + @Test + public void logPermissionRequestDisplayed_logsStateChangedAtomId() { + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + + verifyStateChangedAtomIdLogged(); + } + + @Test + public void logPermissionRequestDisplayed_logsCurrentSessionId() { + int currentSessionId = 765; + when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId); + + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + + verifySessionIdLogged(currentSessionId); + } + + @Test + public void logPermissionRequestDisplayed_logsStateDisplayed() { + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + + verifyStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED); + } + + @Test + public void logPermissionRequestDisplayed_logsHostUid() { + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + + verifyHostUidLogged(TEST_HOST_UID); + } + + @Test + public void logPermissionRequestDisplayed_logsUnknownTargetUid() { + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + + verifyTargetUidLogged(-2); + } + + @Test + public void logPermissionRequestDisplayed_logsUnknownTimeSinceLastActive() { + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + + verifyTimeSinceLastActiveSessionLogged(-1); + } + + @Test + public void logPermissionRequestDisplayed_logsUnknownSessionCreationSource() { + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + + verifyCreationSourceLogged( + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + + @Test + public void logPermissionRequestDisplayed_logsPreviousState() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN); + + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED); + + mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED); + + mLogger.logPermissionRequestDisplayed(TEST_HOST_UID); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED); + } + + @Test + public void logAppSelectorDisplayed_logsStateChangedAtomId() { + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + + verifyStateChangedAtomIdLogged(); + } + + @Test + public void logAppSelectorDisplayed_logsCurrentSessionId() { + int currentSessionId = 765; + when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId); + + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + + verifySessionIdLogged(currentSessionId); + } + + @Test + public void logAppSelectorDisplayed_logsStateDisplayed() { + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + + verifyStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED); + } + + @Test + public void logAppSelectorDisplayed_logsHostUid() { + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + + verifyHostUidLogged(TEST_HOST_UID); + } + + @Test + public void logAppSelectorDisplayed_logsUnknownTargetUid() { + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + + verifyTargetUidLogged(-2); + } + + @Test + public void logAppSelectorDisplayed_logsUnknownTimeSinceLastActive() { + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + + verifyTimeSinceLastActiveSessionLogged(-1); + } + + @Test + public void logAppSelectorDisplayed_logsUnknownSessionCreationSource() { + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + + verifyCreationSourceLogged( + MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN); + } + + @Test + public void logAppSelectorDisplayed_logsPreviousState() { + mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN); + + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED); + + mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED); + + mLogger.logAppSelectorDisplayed(TEST_HOST_UID); + verifyPreviousStateLogged( + MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED); + } + + 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/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 5dfce0613190..89c6a2201434 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -974,7 +974,6 @@ public final class UserManagerTest { assertThrows(SecurityException.class, userProps::getAlwaysVisible); } - // Make sure only max managed profiles can be created @MediumTest @Test 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 9bd938f2e0a7..cf8548cfe689 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 75d012a8e1f2..6792cfe6e788 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/wmtests/Android.bp b/services/tests/wmtests/Android.bp index af39b2f027ee..1b8d746f271f 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 8db09f9e3a16..61c4d06131e1 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 2244dbe8af98..261d3cc3c8d9 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 d2eb1cc0222b..78566fb06179 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,7 +603,7 @@ 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( @@ -566,7 +611,131 @@ public class ContentRecorderTests extends WindowTestsBase { } @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 486486869d05..3cb4a1d7b7ec 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 = @@ -65,6 +71,11 @@ class SystemServiceTestsBase { } /** + * 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 7634d9f601d4..03188f813555 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); @@ -195,15 +208,18 @@ public class SystemServicesTestRule implements TestRule { private void setUpSystemCore() { doReturn(mock(Watchdog.class)).when(Watchdog::getInstance); doAnswer(invocation -> { - // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig - // only registers once and it doesn't reference to outside. - if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) { - mDeviceConfigListeners.add(invocation.getArgument(2)); + if ("addOnPropertiesChangedListener".equals(invocation.getMethod().getName())) { + // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig + // only registers once and it doesn't reference to outside. + if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) { + mDeviceConfigListeners.add(invocation.getArgument(2)); + } + // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests + // uses splash_screen_exception_list. So still execute real registration. } - // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests - // uses splash_screen_exception_list. So still execute real registration. return invocation.callRealMethod(); - }).when(() -> DeviceConfig.addOnPropertiesChangedListener(anyString(), any(), any())); + }).when(() -> DeviceConfig.addOnPropertiesChangedListener( + anyString(), any(), any(DeviceConfig.OnPropertiesChangedListener.class))); mContext = getInstrumentation().getTargetContext(); spyOn(mContext); @@ -384,20 +400,24 @@ public class SystemServicesTestRule implements TestRule { } private void tearDown() { - for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) { - final DisplayContent dc = mWmService.mRoot.getChildAt(i); - // Unregister SettingsObserver. - dc.getDisplayPolicy().release(); - // Unregister SensorEventListener (foldable device may register for hinge angle). - dc.getDisplayRotation().onDisplayRemoved(); - if (dc.mDisplayRotationCompatPolicy != null) { - dc.mDisplayRotationCompatPolicy.dispose(); + if (mWmService != null) { + for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) { + final DisplayContent dc = mWmService.mRoot.getChildAt(i); + // Unregister SettingsObserver. + dc.getDisplayPolicy().release(); + // Unregister SensorEventListener (foldable device may register for hinge angle). + dc.getDisplayRotation().onDisplayRemoved(); + if (dc.mDisplayRotationCompatPolicy != null) { + dc.mDisplayRotationCompatPolicy.dispose(); + } } } - // Unregister display listener from root to avoid issues with subsequent tests. - mContext.getSystemService(DisplayManager.class) - .unregisterDisplayListener(mAtmService.mRootWindowContainer); + if (mAtmService != null) { + // Unregister display listener from root to avoid issues with subsequent tests. + mContext.getSystemService(DisplayManager.class) + .unregisterDisplayListener(mAtmService.mRootWindowContainer); + } for (int i = mDeviceConfigListeners.size() - 1; i >= 0; i--) { DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListeners.get(i)); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java index 13a4c1142574..8fecbb9d4dec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java @@ -96,7 +96,7 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa @Test public void testTaskRemovedFromRecents() { mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); - mPersister.onTaskRemovedFromRecents(1, mTestUserId); + mPersister.removeSnapshot(1, mTestUserId); mSnapshotPersistQueue.waitForQueueEmpty(); assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.proto").exists()); assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.jpg").exists()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java index 08438c8cca5d..267bec9cccd2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; +import static android.os.Build.HW_TIMEOUT_MULTIPLIER; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -79,7 +80,7 @@ public class TaskStackChangedListenerTest { private ImageReader mImageReader; private final ArrayList<Activity> mStartedActivities = new ArrayList<>(); - private static final int WAIT_TIMEOUT_MS = 5000; + private static final int WAIT_TIMEOUT_MS = 5000 * HW_TIMEOUT_MULTIPLIER; private static final Object sLock = new Object(); @Before 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 000000000000..31418d65b8b6 --- /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 55b5d11d938a..e413663160b5 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 7a0bf9038f7c..038b93fc2a43 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1056,6 +1056,14 @@ public class CarrierConfigManager { "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 * the call is being placed. In a dual SIM scenario, if Sim A has the emergency numbers @@ -9673,7 +9681,6 @@ public class CarrierConfigManager { * * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT - * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED */ public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = @@ -9874,6 +9881,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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 73220c353b36..c0d6b301def0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -17483,6 +17483,8 @@ public class TelephonyManager { * {@link CarrierConfigManager * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG} * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}. + * + * @hide */ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index e9af486219f7..11cbcb1c149d 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. @@ -2189,6 +2264,22 @@ public class ApnSetting implements Parcelable { } /** + * 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. * * @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)} @@ -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 f346b9208bcd..88a32d15a7a6 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 82aa85d55e4c..f4f2be642e81 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 48d504141116..f788efae4c59 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 f575fcc0e945..d86dc50b3a5c 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 93d0520d87bc..be07053f3039 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 78b58f4da065..f66eff9b4cb0 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 3f931c48ddbd..65214764f0ac 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 b85362a07538..4d31c28b1231 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 1bdb6e717b12..42e34b313d8e 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 3d9c0679945d..000000000000 --- 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 000000000000..c8547015b92b --- /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 index 4fc9bcb309c5..9d7096ea0e73 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 index cc501e6c4308..7b088431b0bb 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 index bb11be5bb520..989619e08d98 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 000000000000..2e9620bb13c5 --- /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() + } +} |