Merge "Support for multiple gestures at once" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 6d74a84..db51537 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -17,11 +17,14 @@
":android.companion.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
+ ":android.hardware.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}",
":android.security.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
":android.view.flags-aconfig-java{.generated_srcjars}",
":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
@@ -51,6 +54,7 @@
":aconfig_midi_flags_java_lib{.generated_srcjars}",
":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
":com.android.net.flags-aconfig-java{.generated_srcjars}",
+ ":device_policy_aconfig_flags_lib{.generated_srcjars}",
]
filegroup {
@@ -140,6 +144,21 @@
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",
@@ -150,6 +169,11 @@
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"],
}
@@ -274,6 +298,19 @@
aconfig_declarations: "android.view.accessibility.flags-aconfig",
}
+// Hardware
+aconfig_declarations {
+ name: "android.hardware.flags-aconfig",
+ package: "android.hardware.flags",
+ srcs: ["core/java/android/hardware/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.hardware.flags-aconfig-java",
+ aconfig_declarations: "android.hardware.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Widget
aconfig_declarations {
name: "android.widget.flags-aconfig",
@@ -287,6 +324,12 @@
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",
@@ -523,3 +566,36 @@
aconfig_declarations: "com.android.net.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// DevicePolicy
+aconfig_declarations {
+ name: "device_policy_aconfig_flags",
+ package: "android.app.admin.flags",
+ srcs: [
+ "core/java/android/app/admin/flags/flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "device_policy_aconfig_flags_lib",
+ aconfig_declarations: "device_policy_aconfig_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+cc_aconfig_library {
+ name: "device_policy_aconfig_flags_c_lib",
+ aconfig_declarations: "device_policy_aconfig_flags",
+}
+
+// Notifications
+aconfig_declarations {
+ name: "android.service.notification.flags-aconfig",
+ package: "android.service.notification",
+ srcs: ["core/java/android/service/notification/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.service.notification.flags-aconfig-java",
+ aconfig_declarations: "android.service.notification.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 0c199a6..7d5e788 100644
--- a/Android.bp
+++ b/Android.bp
@@ -323,7 +323,6 @@
":installd_aidl",
":libaudioclient_aidl",
":libbinder_aidl",
- ":libbluetooth-binder-aidl",
":libcamera_client_aidl",
":libcamera_client_framework_aidl",
":libupdate_engine_aidl",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 015487d..b42f7bc 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -28,3 +28,6 @@
ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
+
+# This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py.
+flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index 45bb161..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -77,6 +77,42 @@
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 a143d6f..bbe1485 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 @@
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 @@
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 @@
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 @@
sInitialJwiEstimatedNetworkUploadKBLogger.logSample(
safelyScaleBytesToKBForHistogram(
work.getEstimatedNetworkUploadBytes()));
- sJwiMinimumChunkKBLogger.logSampleWithUid(uId,
+ sJwiMinimumChunkKBLogger.logSampleWithUid(callingUid,
safelyScaleBytesToKBForHistogram(
work.getMinimumNetworkChunkBytes()));
}
@@ -1640,11 +1640,12 @@
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 @@
// 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 @@
| 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 @@
}
}
- 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 @@
&& !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 @@
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 @@
// 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 @@
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 d48d84b..1fdf906 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.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 @@
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 @@
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 e162100..e086bfe 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -139,9 +139,22 @@
// 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 7e41660..7e78185 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -739,7 +739,9 @@
"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 020ca33..ee2af12 100644
--- a/cmds/svc/src/com/android/commands/svc/NfcCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
@@ -16,10 +16,11 @@
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;
+import android.os.Looper;
public class NfcCommand extends Svc.Command {
@@ -42,27 +43,26 @@
@Override
public void run(String[] args) {
- INfcAdapter adapter = INfcAdapter.Stub.asInterface(
- ServiceManager.getService(Context.NFC_SERVICE));
-
+ Looper.prepareMainLooper();
+ ActivityThread.initializeMainlineModules();
+ 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/cmds/svc/src/com/android/commands/svc/OWNERS b/cmds/svc/src/com/android/commands/svc/OWNERS
new file mode 100644
index 0000000..d5a5d7b
--- /dev/null
+++ b/cmds/svc/src/com/android/commands/svc/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+per-file NfcCommand.java = file:platform/packages/apps/Nfc:/OWNERS
diff --git a/core/api/current.txt b/core/api/current.txt
index 66aeb0f..4a03d02 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -668,6 +668,7 @@
field public static final int debuggable = 16842767; // 0x101000f
field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562
field public static final int defaultHeight = 16844021; // 0x10104f5
+ field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale;
field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
field public static final int defaultValue = 16843245; // 0x10101ed
field public static final int defaultWidth = 16844020; // 0x10104f4
@@ -6188,6 +6189,7 @@
ctor public LocaleConfig(@NonNull android.os.LocaleList);
method public int describeContents();
method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context);
+ method @FlaggedApi("android.content.res.default_locale") @Nullable public java.util.Locale getDefaultLocale();
method public int getStatus();
method @Nullable public android.os.LocaleList getSupportedLocales();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9530,7 +9532,8 @@
method @Nullable public CharSequence getDisplayName();
method public int getId();
method public int getSystemDataSyncFlags();
- method @Nullable public String getTag();
+ method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
+ method public boolean isSelfManaged();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
}
@@ -9600,7 +9603,7 @@
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
- method public void clearAssociationTag(int);
+ method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
method public void disableSystemDataSyncForTypes(int, int);
method @Deprecated public void disassociate(@NonNull String);
@@ -9610,7 +9613,7 @@
method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
method public void requestNotificationAccess(android.content.ComponentName);
- method public void setAssociationTag(int, @NonNull String);
+ method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
@@ -9641,13 +9644,13 @@
method @Deprecated @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
- method @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
- field public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
- field public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
- field public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2
- field public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3
- field public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
- field public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+ method @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
@@ -11719,7 +11722,7 @@
method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
- method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+ method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
method public void setTargetOverlayable(@Nullable String);
}
@@ -12675,6 +12678,7 @@
method public boolean isDeviceUpgrading();
method public abstract boolean isInstantApp();
method public abstract boolean isInstantApp(@NonNull String);
+ method @FlaggedApi("android.content.pm.quarantined_enabled") public boolean isPackageQuarantined(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isPackageSuspended();
@@ -12913,6 +12917,7 @@
field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000
field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200
field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
+ field @FlaggedApi("android.content.pm.quarantined_enabled") public static final long MATCH_QUARANTINED_COMPONENTS = 4294967296L; // 0x100000000L
field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
@@ -14352,14 +14357,14 @@
public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
method public void beginTransaction();
method public void beginTransactionNonExclusive();
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionReadOnly();
method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
method public static boolean deleteDatabase(@NonNull java.io.File);
method public void disableWriteAheadLogging();
@@ -14370,13 +14375,13 @@
method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
method public static String findEditTable(String);
method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount();
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastChangedRowCount();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastInsertRowId();
method public long getMaximumSize();
method public long getPageSize();
method public String getPath();
method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getTotalChangedRowCount();
method public int getVersion();
method public boolean inTransaction();
method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14598,7 +14603,7 @@
method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
}
- @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable {
+ @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable {
method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -16254,6 +16259,8 @@
public static class Paint.FontMetricsInt {
ctor public Paint.FontMetricsInt();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetricsInt);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetrics);
field public int ascent;
field public int bottom;
field public int descent;
@@ -17572,7 +17579,7 @@
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 {
@@ -17760,18 +17767,18 @@
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 {
@@ -18168,6 +18175,13 @@
field public static final int YCBCR_P010 = 54; // 0x36
}
+ @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable {
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public int describeContents();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean supportMixedColorSpaces();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.hardware.flags.overlayproperties_class_api") @NonNull public static final android.os.Parcelable.Creator<android.hardware.OverlayProperties> CREATOR;
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
@@ -18547,12 +18561,12 @@
ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
ctor @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") public BiometricPrompt.CryptoObject(@NonNull javax.crypto.KeyAgreement);
- method public javax.crypto.Cipher getCipher();
+ method @Nullable public javax.crypto.Cipher getCipher();
method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement();
- method public javax.crypto.Mac getMac();
+ method @Nullable public javax.crypto.Mac getMac();
method @Nullable public android.security.identity.PresentationSession getPresentationSession();
- method public java.security.Signature getSignature();
+ method @Nullable public java.security.Signature getSignature();
}
}
@@ -23775,6 +23789,8 @@
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
@@ -23974,7 +23990,7 @@
method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
method public void registerControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.ControllerCallback);
method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
+ method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
@@ -23983,7 +23999,7 @@
method public void transferTo(@NonNull android.media.MediaRoute2Info);
method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void unregisterRouteListingPreferenceCallback(@NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
+ method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void unregisterRouteListingPreferenceUpdatedCallback(@NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
method public void unregisterTransferCallback(@NonNull android.media.MediaRouter2.TransferCallback);
}
@@ -24004,11 +24020,6 @@
method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
}
- @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public abstract static class MediaRouter2.RouteListingPreferenceCallback {
- ctor @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public MediaRouter2.RouteListingPreferenceCallback();
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void onRouteListingPreferenceChanged(@Nullable android.media.RouteListingPreference);
- }
-
public class MediaRouter2.RoutingController {
method public void deselectRoute(@NonNull android.media.MediaRoute2Info);
method @Nullable public android.os.Bundle getControlHints();
@@ -33464,7 +33475,7 @@
field public static final String DISALLOW_MICROPHONE_TOGGLE = "disallow_microphone_toggle";
field public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
field public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
- field public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio";
+ field @FlaggedApi("android.nfc.enable_nfc_user_restriction") public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio";
field public static final String DISALLOW_NETWORK_RESET = "no_network_reset";
field public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
field public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
@@ -45472,7 +45483,6 @@
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
@@ -45702,6 +45712,7 @@
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
@@ -46710,6 +46721,7 @@
method @NonNull public android.text.DynamicLayout.Builder setJustificationMode(int);
method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.DynamicLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
@@ -46898,6 +46910,7 @@
method public int getLineVisibleEnd(int);
method public float getLineWidth(int);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @IntRange(from=1) public final int getMaxLines();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
method public int getOffsetForHorizontal(int, float);
method public int getOffsetToLeftOf(int);
method public int getOffsetToRightOf(int);
@@ -46964,6 +46977,7 @@
method @NonNull public android.text.Layout.Builder setLineSpacingAmount(float);
method @NonNull public android.text.Layout.Builder setLineSpacingMultiplier(@FloatRange(from=0) float);
method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.Layout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]);
method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
@@ -47235,6 +47249,7 @@
method @NonNull public android.text.StaticLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.text.StaticLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.StaticLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method public android.text.StaticLayout.Builder setText(CharSequence);
method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
@@ -49844,6 +49859,7 @@
method public android.view.Display.Mode getMode();
method public String getName();
method @Deprecated public int getOrientation();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") @NonNull public android.hardware.OverlayProperties getOverlaySupport();
method @Deprecated public int getPixelFormat();
method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace();
method public long getPresentationDeadlineNanos();
@@ -50122,13 +50138,6 @@
field public static final int VIRTUAL_KEY_RELEASE = 8; // 0x8
}
- @FlaggedApi("android.view.flags.scroll_feedback_api") public class HapticScrollFeedbackProvider implements android.view.ScrollFeedbackProvider {
- ctor public HapticScrollFeedbackProvider(@NonNull android.view.View);
- method public void onScrollLimit(int, int, int, boolean);
- method public void onScrollProgress(int, int, int, int);
- method public void onSnapToItem(int, int, int);
- }
-
public class InflateException extends java.lang.RuntimeException {
ctor public InflateException();
ctor public InflateException(String, Throwable);
@@ -51295,9 +51304,10 @@
}
@FlaggedApi("android.view.flags.scroll_feedback_api") public interface ScrollFeedbackProvider {
- method public void onScrollLimit(int, int, int, boolean);
- method public void onScrollProgress(int, int, int, int);
- method public void onSnapToItem(int, int, int);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") @NonNull public static android.view.ScrollFeedbackProvider createProvider(@NonNull android.view.View);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollLimit(int, int, int, boolean);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollProgress(int, int, int, int);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onSnapToItem(int, int, int);
}
public class SearchEvent {
@@ -52579,7 +52589,6 @@
method @Deprecated public static int getEdgeSlop();
method @Deprecated public static int getFadingEdgeLength();
method @Deprecated public static long getGlobalActionKeyTimeout();
- method @FlaggedApi("android.view.flags.scroll_feedback_api") public int getHapticScrollFeedbackTickInterval(int, int, int);
method public static int getJumpTapTimeout();
method public static int getKeyRepeatDelay();
method public static int getKeyRepeatTimeout();
@@ -52619,7 +52628,6 @@
method @Deprecated public static int getWindowTouchSlop();
method public static long getZoomControlsTimeout();
method public boolean hasPermanentMenuKey();
- method @FlaggedApi("android.view.flags.scroll_feedback_api") public boolean isHapticScrollFeedbackEnabled(int, int, int);
method public boolean shouldShowMenuShortcutsWhenKeyboardPresent();
}
@@ -54512,8 +54520,8 @@
public class AnimationUtils {
ctor public AnimationUtils();
method public static long currentAnimationTimeMillis();
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeMillis();
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeNanos();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static long getExpectedPresentationTimeMillis();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static long getExpectedPresentationTimeNanos();
method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -59920,6 +59928,7 @@
method public int getMinHeight();
method public int getMinLines();
method public int getMinWidth();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
method public final android.text.method.MovementMethod getMovementMethod();
method public int getOffsetForPosition(float, float);
method public android.text.TextPaint getPaint();
@@ -60056,6 +60065,7 @@
method public void setMinHeight(int);
method public void setMinLines(int);
method public void setMinWidth(int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method public final void setMovementMethod(android.text.method.MovementMethod);
method public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener);
method public void setPaintFlags(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5d1f6dc..1a7810a 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3149,7 +3149,6 @@
public final class AssociationInfo implements android.os.Parcelable {
method @NonNull public String getPackageName();
- method public boolean isSelfManaged();
}
public final class CompanionDeviceManager {
@@ -9538,7 +9537,7 @@
method public int getDeviceType();
method @NonNull public android.os.Bundle getExtras();
method @NonNull public String getModelName();
- method public boolean isBatteryCharging();
+ method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -9552,7 +9551,7 @@
public static final class NetworkProviderInfo.Builder {
ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
- method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+ method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
@@ -9641,6 +9640,7 @@
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 @@
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);
@@ -12744,7 +12744,7 @@
method @NonNull public android.service.voice.HotwordRejectedResult.Builder setConfidenceLevel(int);
}
- public final class HotwordTrainingAudio implements android.os.Parcelable {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public final class HotwordTrainingAudio implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.media.AudioFormat getAudioFormat();
method @NonNull public int getAudioType();
@@ -12755,7 +12755,7 @@
field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
}
- public static final class HotwordTrainingAudio.Builder {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final class HotwordTrainingAudio.Builder {
ctor public HotwordTrainingAudio.Builder(@NonNull byte[], @NonNull android.media.AudioFormat);
method @NonNull public android.service.voice.HotwordTrainingAudio build();
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioFormat(@NonNull android.media.AudioFormat);
@@ -12764,7 +12764,7 @@
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordOffsetMillis(int);
}
- public final class HotwordTrainingData implements android.os.Parcelable {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public final class HotwordTrainingData implements android.os.Parcelable {
method public int describeContents();
method public static int getMaxTrainingDataBytes();
method public int getTimeoutStage();
@@ -12773,7 +12773,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR;
}
- public static final class HotwordTrainingData.Builder {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final class HotwordTrainingData.Builder {
ctor public HotwordTrainingData.Builder();
method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio);
method @NonNull public android.service.voice.HotwordTrainingData build();
@@ -14717,6 +14717,7 @@
field public static final String TYPE_IMS_STRING = "ims";
field public static final String TYPE_MCX_STRING = "mcx";
field public static final String TYPE_MMS_STRING = "mms";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs";
field public static final String TYPE_SUPL_STRING = "supl";
field public static final String TYPE_VSIM_STRING = "vsim";
field public static final String TYPE_XCAP_STRING = "xcap";
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 4fb7b6b..a501031 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -251,10 +251,6 @@
New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioMixingRule.writeToParcel(android.os.Parcel,int)
UnflaggedApi: android.media.audiopolicy.AudioPolicy#updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>):
New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioPolicy.updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>)
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo#isBatteryCharging():
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.isBatteryCharging()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder#setBatteryCharging(boolean):
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder.setBatteryCharging(boolean)
UnflaggedApi: android.nfc.cardemulation.AidGroup#CONTENTS_FILE_DESCRIPTOR:
New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.AidGroup.CONTENTS_FILE_DESCRIPTOR
UnflaggedApi: android.nfc.cardemulation.AidGroup#PARCELABLE_WRITE_RETURN_VALUE:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index df556a8..779777c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -541,7 +541,7 @@
field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
- field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
+ field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
}
public class DevicePolicyManager {
@@ -851,7 +851,7 @@
method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int);
- method @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
+ method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
}
@@ -2117,7 +2117,7 @@
public class SharedConnectivityManager {
method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
- method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+ method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
method @Nullable public android.content.ServiceConnection getServiceConnection();
method public void setService(@Nullable android.os.IInterface);
}
@@ -2148,7 +2148,7 @@
}
public final class BugreportParams {
- field public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+ field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
}
public class Build {
@@ -3622,7 +3622,7 @@
package android.view.animation {
public class AnimationUtils {
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static void lockAnimationClock(long, long);
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static void lockAnimationClock(long, long);
method public static void unlockAnimationClock();
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 93e39d5..105e764 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -257,8 +257,6 @@
New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel)
UnflaggedApi: android.media.soundtrigger.SoundTriggerManager.Model#getSoundModel():
New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.Model.getSoundModel()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.SharedConnectivityManager#getBroadcastReceiver():
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.SharedConnectivityManager.getBroadcastReceiver()
UnflaggedApi: android.os.BatteryManager#BATTERY_PLUGGED_ANY:
New API must be flagged with @FlaggedApi: field android.os.BatteryManager.BATTERY_PLUGGED_ANY
UnflaggedApi: android.os.BugreportParams#BUGREPORT_MODE_MAX_VALUE:
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 0293f66..ddb221f 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,6 +14,15 @@
hdrs: ["android/hardware/HardwareBuffer.aidl"],
}
+// TODO (b/303286040): Remove this once |ENABLE_NFC_MAINLINE_FLAG| is rolled out
+filegroup {
+ name: "framework-core-nfc-infcadapter-sources",
+ srcs: [
+ "android/nfc/INfcAdapter.aidl",
+ ],
+ visibility: ["//frameworks/base/services/core"],
+}
+
filegroup {
name: "framework-core-sources",
srcs: [
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a538247..08c18c8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3483,12 +3483,10 @@
// only do this if the user already has more than one preferred locale
if (r.getConfiguration().getLocales().size() > 1) {
- LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
- mResourcesManager.setLocaleList(lc != null
- && lc.getSupportedLocales() != null
- && !lc.getSupportedLocales().isEmpty()
- ? lc.getSupportedLocales()
- : null);
+ LocaleConfig lc = getUserId() < 0
+ ? LocaleConfig.fromContextIgnoringOverride(this)
+ : new LocaleConfig(this);
+ mResourcesManager.setLocaleConfig(lc);
}
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index a3b82e9..d7d6546 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -161,12 +161,6 @@
*/
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 @@
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/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 1fdc516..369a781 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -16,9 +16,11 @@
package android.app;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -30,6 +32,7 @@
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.R;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -39,7 +42,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -66,6 +69,8 @@
public static final String TAG_LOCALE_CONFIG = "locale-config";
public static final String TAG_LOCALE = "locale";
private LocaleList mLocales;
+
+ private Locale mDefaultLocale;
private int mStatus = STATUS_NOT_SPECIFIED;
/**
@@ -193,8 +198,17 @@
XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
int outerDepth = parser.getDepth();
AttributeSet attrs = Xml.asAttributeSet(parser);
- // LinkedHashSet to preserve insertion order
- Set<String> localeNames = new LinkedHashSet<>();
+
+ String defaultLocale = null;
+ if (android.content.res.Flags.defaultLocale()) {
+ TypedArray att = res.obtainAttributes(
+ attrs, com.android.internal.R.styleable.LocaleConfig);
+ defaultLocale = att.getString(
+ R.styleable.LocaleConfig_defaultLocale);
+ att.recycle();
+ }
+
+ Set<String> localeNames = new HashSet<>();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_LOCALE.equals(parser.getName())) {
final TypedArray attributes = res.obtainAttributes(
@@ -209,6 +223,15 @@
}
mStatus = STATUS_SUCCESS;
mLocales = LocaleList.forLanguageTags(String.join(",", localeNames));
+ if (defaultLocale != null) {
+ if (localeNames.contains(defaultLocale)) {
+ mDefaultLocale = Locale.forLanguageTag(defaultLocale);
+ } else {
+ Slog.w(TAG, "Default locale specified that is not contained in the list: "
+ + defaultLocale);
+ mStatus = STATUS_PARSING_FAILED;
+ }
+ }
}
/**
@@ -224,6 +247,17 @@
}
/**
+ * Returns the default locale if specified, otherwise null
+ *
+ * @return The default Locale or null
+ */
+ @SuppressLint("UseIcu")
+ @FlaggedApi(android.content.res.Flags.FLAG_DEFAULT_LOCALE)
+ public @Nullable Locale getDefaultLocale() {
+ return mDefaultLocale;
+ }
+
+ /**
* Get the status of reading the resource file where the LocaleConfig was stored.
*
* <p>Distinguish "the application didn't provide the resource file" from "the application
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1ecb5d3..6009c29 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,9 +120,9 @@
private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
/**
- * The list of locales the app declares it supports.
+ * The localeConfig of the app.
*/
- private LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+ private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
private static class ApkKey {
public final String path;
@@ -1612,18 +1612,19 @@
}
/**
- * Returns the LocaleList current set
+ * Returns the LocaleConfig current set
*/
- public LocaleList getLocaleList() {
- return mLocaleList;
+ public LocaleConfig getLocaleConfig() {
+ return mLocaleConfig;
}
/**
- * Sets the LocaleList of app's supported locales
+ * Sets the LocaleConfig of the app
*/
- public void setLocaleList(LocaleList localeList) {
- if ((localeList != null) && !localeList.isEmpty()) {
- mLocaleList = localeList;
+ public void setLocaleConfig(LocaleConfig localeConfig) {
+ if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null)
+ && !localeConfig.getSupportedLocales().isEmpty()) {
+ mLocaleConfig = localeConfig;
}
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e7a5b72..d660078 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -313,7 +313,6 @@
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 @@
}
/**
+ * 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 @@
*/
@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 @@
*/
@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 @@
*/
@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 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 @@
}
/**
- * 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/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index ad0af72..84b1ca5 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,10 +16,13 @@
package android.app.admin;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.UserManager;
+
import java.util.Objects;
/**
@@ -164,6 +167,7 @@
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
@TestApi
public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index 113a6dd..7320cea 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -51,7 +51,7 @@
/**
* 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/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
new file mode 100644
index 0000000..5a41c65
--- /dev/null
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -0,0 +1,43 @@
+package: "android.app.admin.flags"
+
+flag {
+ name: "policy_engine_migration_v2_enabled"
+ namespace: "enterprise"
+ description: "V2 of the policy engine migrations for Android V"
+ bug: "289520697"
+}
+
+flag {
+ name: "device_policy_size_tracking_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track the total policy size and have a max threshold."
+ bug: "281543351"
+}
+
+flag {
+ name: "onboarding_bugreport_v2_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports."
+ bug: "302517677"
+}
+
+flag {
+ name: "cross_user_suspension_enabled"
+ namespace: "enterprise"
+ description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
+ bug: "263464464"
+}
+
+flag {
+ name: "dedicated_device_control_enabled"
+ namespace: "enterprise"
+ description: "Allow the device management role holder to control which platform features are available on dedicated devices."
+ bug: "281964214"
+}
+
+flag {
+ name: "security_log_v2_enabled"
+ namespace: "enterprise"
+ description: "Improve access to security logging in the context of Zero Trust."
+ bug: "295324350"
+}
diff --git a/core/java/android/app/contentsuggestions/OWNERS b/core/java/android/app/contentsuggestions/OWNERS
index cf54c2a..5f8de77 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/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index b34f678..06bff5d 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -58,6 +58,11 @@
super(in);
}
+ @Override
+ boolean isActivityLifecycleItem() {
+ return true;
+ }
+
/** A final lifecycle state that an activity should reach. */
@LifecycleState
public abstract int getTargetState();
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 8617386..9c0cd39 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -45,6 +45,13 @@
*/
public class ClientTransaction implements Parcelable, ObjectPoolItem {
+ /**
+ * List of transaction items that should be executed in order. Including both
+ * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}.
+ */
+ @Nullable
+ private List<ClientTransactionItem> mTransactionItems;
+
/** A list of individual callbacks to a client. */
@UnsupportedAppUsage
private List<ClientTransactionItem> mActivityCallbacks;
@@ -64,9 +71,32 @@
}
/**
- * Add a message to the end of the sequence of callbacks.
- * @param activityCallback A single message that can contain a lifecycle request/callback.
+ * Adds a message to the end of the sequence of transaction items.
+ * @param item A single message that can contain a client activity/window request/callback.
+ * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
*/
+ public void addTransactionItem(@NonNull ClientTransactionItem item) {
+ if (mTransactionItems == null) {
+ mTransactionItems = new ArrayList<>();
+ }
+ mTransactionItems.add(item);
+ }
+
+ /**
+ * Gets the list of client window requests/callbacks.
+ * TODO(b/260873529): must be non null after remove the deprecated methods.
+ */
+ @Nullable
+ public List<ClientTransactionItem> getTransactionItems() {
+ return mTransactionItems;
+ }
+
+ /**
+ * Adds a message to the end of the sequence of callbacks.
+ * @param activityCallback A single message that can contain a lifecycle request/callback.
+ * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
+ */
+ @Deprecated
public void addCallback(@NonNull ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
@@ -74,25 +104,35 @@
mActivityCallbacks.add(activityCallback);
}
- /** Get the list of callbacks. */
+ /**
+ * Gets the list of callbacks.
+ * @deprecated use {@link #getTransactionItems()} instead.
+ */
@Nullable
@VisibleForTesting
@UnsupportedAppUsage
+ @Deprecated
public List<ClientTransactionItem> getCallbacks() {
return mActivityCallbacks;
}
- /** Get the target state lifecycle request. */
+ /**
+ * Gets the target state lifecycle request.
+ * @deprecated use {@link #getTransactionItems()} instead.
+ */
@VisibleForTesting(visibility = PACKAGE)
@UnsupportedAppUsage
+ @Deprecated
public ActivityLifecycleItem getLifecycleStateRequest() {
return mLifecycleStateRequest;
}
/**
- * Set the lifecycle state in which the client should be after executing the transaction.
+ * Sets the lifecycle state in which the client should be after executing the transaction.
* @param stateRequest A lifecycle request initialized with right parameters.
+ * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
*/
+ @Deprecated
public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
mLifecycleStateRequest = stateRequest;
}
@@ -103,6 +143,14 @@
* requested by transaction items.
*/
public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) {
+ if (mTransactionItems != null) {
+ final int size = mTransactionItems.size();
+ for (int i = 0; i < size; ++i) {
+ mTransactionItems.get(i).preExecute(clientTransactionHandler);
+ }
+ return;
+ }
+
if (mActivityCallbacks != null) {
final int size = mActivityCallbacks.size();
for (int i = 0; i < size; ++i) {
@@ -147,6 +195,13 @@
@Override
public void recycle() {
+ if (mTransactionItems != null) {
+ int size = mTransactionItems.size();
+ for (int i = 0; i < size; i++) {
+ mTransactionItems.get(i).recycle();
+ }
+ mTransactionItems = null;
+ }
if (mActivityCallbacks != null) {
int size = mActivityCallbacks.size();
for (int i = 0; i < size; i++) {
@@ -165,8 +220,15 @@
// Parcelable implementation
/** Write to Parcel. */
+ @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final.
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
+ final boolean writeTransactionItems = mTransactionItems != null;
+ dest.writeBoolean(writeTransactionItems);
+ if (writeTransactionItems) {
+ dest.writeParcelableList(mTransactionItems, flags);
+ }
+
dest.writeParcelable(mLifecycleStateRequest, flags);
final boolean writeActivityCallbacks = mActivityCallbacks != null;
dest.writeBoolean(writeActivityCallbacks);
@@ -177,11 +239,20 @@
/** Read from Parcel. */
private ClientTransaction(@NonNull Parcel in) {
- mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
+ final boolean readTransactionItems = in.readBoolean();
+ if (readTransactionItems) {
+ mTransactionItems = new ArrayList<>();
+ in.readParcelableList(mTransactionItems, getClass().getClassLoader(),
+ ClientTransactionItem.class);
+ }
+
+ mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(),
+ ActivityLifecycleItem.class);
final boolean readActivityCallbacks = in.readBoolean();
if (readActivityCallbacks) {
mActivityCallbacks = new ArrayList<>();
- in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
+ in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(),
+ ClientTransactionItem.class);
}
}
@@ -209,7 +280,8 @@
return false;
}
final ClientTransaction other = (ClientTransaction) o;
- return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+ return Objects.equals(mTransactionItems, other.mTransactionItems)
+ && Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
&& Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
&& mClient == other.mClient;
}
@@ -217,6 +289,7 @@
@Override
public int hashCode() {
int result = 17;
+ result = 31 * result + Objects.hashCode(mTransactionItems);
result = 31 * result + Objects.hashCode(mActivityCallbacks);
result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
result = 31 * result + Objects.hashCode(mClient);
@@ -227,6 +300,22 @@
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
pw.append(prefix).println("ClientTransaction{");
+ if (mTransactionItems != null) {
+ pw.append(prefix).print(" transactionItems=[");
+ final String itemPrefix = prefix + " ";
+ final int size = mTransactionItems.size();
+ if (size > 0) {
+ pw.println();
+ for (int i = 0; i < size; i++) {
+ mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler);
+ }
+ pw.append(prefix).println(" ]");
+ } else {
+ pw.println("]");
+ }
+ pw.append(prefix).println("}");
+ return;
+ }
pw.append(prefix).print(" callbacks=[");
final String itemPrefix = prefix + " ";
final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index 07e5a7d..f94e22d 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -72,6 +72,13 @@
return null;
}
+ /**
+ * Whether this is a {@link ActivityLifecycleItem}.
+ */
+ boolean isActivityLifecycleItem() {
+ return false;
+ }
+
/** Dumps this transaction item. */
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 066f9fe..9f5e0dc 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -28,6 +28,7 @@
import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName;
import static android.app.servertransaction.TransactionExecutorHelper.getStateName;
import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState;
+import static android.app.servertransaction.TransactionExecutorHelper.shouldExcludeLastLifecycleState;
import static android.app.servertransaction.TransactionExecutorHelper.tId;
import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
@@ -61,6 +62,9 @@
private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
+ /** Keeps track of display ids whose Configuration got updated within a transaction. */
+ private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>();
+
/** Initialize an instance with transaction handler, that will execute all requested actions. */
public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
mTransactionHandler = clientTransactionHandler;
@@ -79,15 +83,52 @@
Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
}
- executeCallbacks(transaction);
- executeLifecycleState(transaction);
+ if (transaction.getTransactionItems() != null) {
+ executeTransactionItems(transaction);
+ } else {
+ // TODO(b/260873529): cleanup after launch.
+ executeCallbacks(transaction);
+ executeLifecycleState(transaction);
+ }
+
+ if (!mConfigUpdatedDisplayIds.isEmpty()) {
+ // Whether this transaction should trigger DisplayListener#onDisplayChanged.
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ final int displayCount = mConfigUpdatedDisplayIds.size();
+ for (int i = 0; i < displayCount; i++) {
+ final int displayId = mConfigUpdatedDisplayIds.valueAt(i);
+ controller.onDisplayChanged(displayId);
+ }
+ mConfigUpdatedDisplayIds.clear();
+ }
mPendingActions.clear();
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
}
- /** Cycle through all states requested by callbacks and execute them at proper times. */
+ /** Cycles through all transaction items and execute them at proper times. */
@VisibleForTesting
+ public void executeTransactionItems(@NonNull ClientTransaction transaction) {
+ final List<ClientTransactionItem> items = transaction.getTransactionItems();
+ final int size = items.size();
+ for (int i = 0; i < size; i++) {
+ final ClientTransactionItem item = items.get(i);
+ if (item.isActivityLifecycleItem()) {
+ executeLifecycleItem(transaction, (ActivityLifecycleItem) item);
+ } else {
+ executeNonLifecycleItem(transaction, item,
+ shouldExcludeLastLifecycleState(items, i));
+ }
+ }
+ }
+
+ /**
+ * Cycle through all states requested by callbacks and execute them at proper times.
+ * @deprecated use {@link #executeTransactionItems} instead.
+ */
+ @VisibleForTesting
+ @Deprecated
public void executeCallbacks(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null || callbacks.isEmpty()) {
@@ -105,83 +146,78 @@
// Index of the last callback that requests some post-execution state.
final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
- // Keep track of display ids whose Configuration got updated with this transaction.
- ArraySet<Integer> configUpdatedDisplays = null;
-
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
- final IBinder token = item.getActivityToken();
- ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
- if (token != null && r == null
- && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
- // The activity has not been created but has been requested to destroy, so all
- // transactions for the token are just like being cancelled.
- Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
- continue;
- }
-
- if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+ // Skip the very last transition and perform it by explicit state request instead.
final int postExecutionState = item.getPostExecutionState();
-
- if (item.shouldHaveDefinedPreExecutionState()) {
- final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
- item.getPostExecutionState());
- if (closestPreExecutionState != UNDEFINED) {
- cycleToPath(r, closestPreExecutionState, transaction);
- }
- }
-
- // Can't read flag from isolated process.
- final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
- && syncWindowConfigUpdateFlag();
- final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
- ? item.getContextToUpdate(mTransactionHandler)
- : null;
- final Configuration preExecutedConfig = configUpdatedContext != null
- ? new Configuration(configUpdatedContext.getResources().getConfiguration())
- : null;
-
- item.execute(mTransactionHandler, mPendingActions);
-
- if (configUpdatedContext != null) {
- final Configuration postExecutedConfig = configUpdatedContext.getResources()
- .getConfiguration();
- if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
- if (configUpdatedDisplays == null) {
- configUpdatedDisplays = new ArraySet<>();
- }
- configUpdatedDisplays.add(configUpdatedContext.getDisplayId());
- }
- }
-
- item.postExecute(mTransactionHandler, mPendingActions);
- if (r == null) {
- // Launch activity request will create an activity record.
- r = mTransactionHandler.getActivityClient(token);
- }
-
- if (postExecutionState != UNDEFINED && r != null) {
- // Skip the very last transition and perform it by explicit state request instead.
- final boolean shouldExcludeLastTransition =
- i == lastCallbackRequestingState && finalState == postExecutionState;
- cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
- }
- }
-
- if (configUpdatedDisplays != null) {
- final ClientTransactionListenerController controller =
- ClientTransactionListenerController.getInstance();
- final int displayCount = configUpdatedDisplays.size();
- for (int i = 0; i < displayCount; i++) {
- final int displayId = configUpdatedDisplays.valueAt(i);
- controller.onDisplayChanged(displayId);
- }
+ final boolean shouldExcludeLastLifecycleState = postExecutionState != UNDEFINED
+ && i == lastCallbackRequestingState && finalState == postExecutionState;
+ executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState);
}
}
- /** Transition to the final state if requested by the transaction. */
+ private void executeNonLifecycleItem(@NonNull ClientTransaction transaction,
+ @NonNull ClientTransactionItem item, boolean shouldExcludeLastLifecycleState) {
+ final IBinder token = item.getActivityToken();
+ ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
+
+ if (token != null && r == null
+ && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
+ // The activity has not been created but has been requested to destroy, so all
+ // transactions for the token are just like being cancelled.
+ Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
+ return;
+ }
+
+ if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+ final int postExecutionState = item.getPostExecutionState();
+
+ if (item.shouldHaveDefinedPreExecutionState()) {
+ final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+ postExecutionState);
+ if (closestPreExecutionState != UNDEFINED) {
+ cycleToPath(r, closestPreExecutionState, transaction);
+ }
+ }
+
+ // Can't read flag from isolated process.
+ final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
+ && syncWindowConfigUpdateFlag();
+ final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+ ? item.getContextToUpdate(mTransactionHandler)
+ : null;
+ final Configuration preExecutedConfig = configUpdatedContext != null
+ ? new Configuration(configUpdatedContext.getResources().getConfiguration())
+ : null;
+
+ item.execute(mTransactionHandler, mPendingActions);
+
+ if (configUpdatedContext != null) {
+ final Configuration postExecutedConfig = configUpdatedContext.getResources()
+ .getConfiguration();
+ if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
+ mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId());
+ }
+ }
+
+ item.postExecute(mTransactionHandler, mPendingActions);
+ if (r == null) {
+ // Launch activity request will create an activity record.
+ r = mTransactionHandler.getActivityClient(token);
+ }
+
+ if (postExecutionState != UNDEFINED && r != null) {
+ cycleToPath(r, postExecutionState, shouldExcludeLastLifecycleState, transaction);
+ }
+ }
+
+ /**
+ * Transition to the final state if requested by the transaction.
+ * @deprecated use {@link #executeTransactionItems} instead
+ */
+ @Deprecated
private void executeLifecycleState(@NonNull ClientTransaction transaction) {
final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
if (lifecycleItem == null) {
@@ -189,6 +225,11 @@
return;
}
+ executeLifecycleItem(transaction, lifecycleItem);
+ }
+
+ private void executeLifecycleItem(@NonNull ClientTransaction transaction,
+ @NonNull ActivityLifecycleItem lifecycleItem) {
final IBinder token = lifecycleItem.getActivityToken();
final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
if (DEBUG_RESOLVER) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 7e89a5b..dfbccb4 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -236,21 +236,39 @@
* index 1 will be returned, because ActivityResult request on position 1 will be the last
* request that moves activity to the RESUMED state where it will eventually end.
*/
- static int lastCallbackRequestingState(ClientTransaction transaction) {
+ static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
- if (callbacks == null || callbacks.size() == 0) {
+ if (callbacks == null || callbacks.isEmpty()
+ || transaction.getLifecycleStateRequest() == null) {
return -1;
}
+ return lastCallbackRequestingStateIndex(callbacks, 0, callbacks.size() - 1,
+ transaction.getLifecycleStateRequest().getActivityToken());
+ }
+ /**
+ * Returns the index of the last callback between the start index and last index that requests
+ * the state for the given activity token in which that activity will be after execution.
+ * If there is a group of callbacks in the end that requests the same specific state or doesn't
+ * request any - we will find the first one from such group.
+ *
+ * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
+ * specific state. If there is a sequence
+ * Configuration - ActivityResult - Configuration - ActivityResult
+ * index 1 will be returned, because ActivityResult request on position 1 will be the last
+ * request that moves activity to the RESUMED state where it will eventually end.
+ */
+ private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items,
+ int startIndex, int lastIndex, @NonNull IBinder activityToken) {
// Go from the back of the list to front, look for the request closes to the beginning that
// requests the state in which activity will end after all callbacks are executed.
int lastRequestedState = UNDEFINED;
int lastRequestingCallback = -1;
- for (int i = callbacks.size() - 1; i >= 0; i--) {
- final ClientTransactionItem callback = callbacks.get(i);
- final int postExecutionState = callback.getPostExecutionState();
- if (postExecutionState != UNDEFINED) {
- // Found a callback that requests some post-execution state.
+ for (int i = lastIndex; i >= startIndex; i--) {
+ final ClientTransactionItem item = items.get(i);
+ final int postExecutionState = item.getPostExecutionState();
+ if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) {
+ // Found a callback that requests some post-execution state for the given activity.
if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
// It's either a first-from-end callback that requests state or it requests
// the same state as the last one. In both cases, we will use it as the new
@@ -266,6 +284,53 @@
return lastRequestingCallback;
}
+ /**
+ * For the transaction item at {@code currentIndex}, if it is requesting post execution state,
+ * whether or not to exclude the last state. This only returns {@code true} when there is a
+ * following explicit {@link ActivityLifecycleItem} requesting the same state for the same
+ * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}.
+ */
+ static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items,
+ int currentIndex) {
+ final ClientTransactionItem item = items.get(currentIndex);
+ final IBinder activityToken = item.getActivityToken();
+ final int postExecutionState = item.getPostExecutionState();
+ if (activityToken == null || postExecutionState == UNDEFINED) {
+ // Not a transaction item requesting post execution state.
+ return false;
+ }
+ final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1,
+ activityToken);
+ if (nextLifecycleItemIndex == -1) {
+ // No following ActivityLifecycleItem for this activity token.
+ return false;
+ }
+ final ActivityLifecycleItem lifecycleItem =
+ (ActivityLifecycleItem) items.get(nextLifecycleItemIndex);
+ if (postExecutionState != lifecycleItem.getTargetState()) {
+ // The explicit ActivityLifecycleItem is not requesting the same state.
+ return false;
+ }
+ // Only exclude for the first non-lifecycle item that requests the same specific state.
+ return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex,
+ nextLifecycleItemIndex - 1, activityToken);
+ }
+
+ /**
+ * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token.
+ */
+ private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items,
+ int startIndex, @NonNull IBinder activityToken) {
+ final int size = items.size();
+ for (int i = startIndex; i < size; i++) {
+ final ClientTransactionItem item = items.get(i);
+ if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
/** Dump transaction to string. */
static String transactionToString(@NonNull ClientTransaction transaction,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index f401a76..4f1c65b 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -13,3 +13,11 @@
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 6393c45..161fa79 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -144,6 +144,7 @@
* @return the tag of this association.
* @see CompanionDeviceManager#setAssociationTag(int, String)
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@Nullable
public String getTag() {
return mTag;
@@ -205,9 +206,8 @@
/**
* @return whether the association is managed by the companion application it belongs to.
* @see AssociationRequest.Builder#setSelfManaged(boolean)
- * @hide
*/
- @SystemApi
+ @SuppressLint("UnflaggedApi") // promoting from @SystemApi
public boolean isSelfManaged() {
return mSelfManaged;
}
@@ -459,6 +459,7 @@
}
/** @hide */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@TestApi
@NonNull
public Builder setTag(@Nullable String tag) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index dbc67fc..70811bb 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1435,6 +1435,7 @@
* of the companion device recorded by CompanionDeviceManager
* @param tag the tag of this association
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
public void setAssociationTag(int associationId, @NonNull String tag) {
Objects.requireNonNull(tag, "tag cannot be null");
@@ -1459,6 +1460,7 @@
* of the companion device recorded by CompanionDeviceManager
* @see CompanionDeviceManager#setAssociationTag(int, String)
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
public void clearAssociationTag(int associationId) {
try {
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 570ecaa..c99a457 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -17,6 +17,7 @@
package android.companion;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -140,24 +141,28 @@
* Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
* with this event if the device comes into BLE range.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_BLE_APPEARED = 0;
/**
* Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
* with this event if the device is no longer in BLE range.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1;
/**
* Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
* with this event when the bluetooth device is connected.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_BT_CONNECTED = 2;
/**
* Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
* with this event if the bluetooth device is disconnected.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_BT_DISCONNECTED = 3;
/**
@@ -165,6 +170,7 @@
* {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its
* own.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4;
/**
@@ -172,6 +178,7 @@
* {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on
* its own.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5;
private final Stub mRemote = new Stub();
@@ -348,6 +355,7 @@
* @param associationInfo A record for the companion device.
* @param event Associated companion device's event.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
@MainThread
public void onDeviceEvent(@NonNull AssociationInfo associationInfo,
@DeviceEvent int event) {
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 1b4234b..6e33dff 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -12,4 +12,18 @@
namespace: "companion"
description: "Grants access to the companion transport apis."
bug: "288297505"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "association_tag"
+ namespace: "companion"
+ description: "Enable Association tag APIs "
+ bug: "289241123"
+}
+
+flag {
+ name: "device_presence"
+ namespace: "companion"
+ description: "Enable device presence APIs"
+ bug: "283000075"
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b765562..7b6bad3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -64,6 +64,7 @@
import android.os.ResultReceiver;
import android.os.ShellCommand;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.provider.ContactsContract.QuickContact;
@@ -2796,6 +2797,8 @@
* started and is no longer considered stopped.
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * <li> {@link #EXTRA_TIME} containing the {@link SystemClock#elapsedRealtime()
+ * elapsed realtime} of when the package was unstopped.
* </ul>
*
* <p class="note">This is a protected intent that can only be sent by the system.
@@ -2869,9 +2872,15 @@
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
+ * <p>
+ * Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, an extra timestamp
+ * {@link #EXTRA_TIME} is included with this broadcast to indicate the exact time the package
+ * was restarted, in {@link SystemClock#elapsedRealtime() elapsed realtime}.
+ * </p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+
/**
* Broadcast Action: The user has cleared the data of a package. This should
* be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
@@ -4283,6 +4292,14 @@
"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
*/
@@ -6578,8 +6595,8 @@
= "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
/**
- * Optional extra specifying a time in milliseconds since the Epoch. The value must be
- * non-negative.
+ * Optional extra specifying a time in milliseconds. The timebase depends on the Intent
+ * including this extra. The value must be non-negative.
* <p>
* Type: long
* </p>
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index c4547b8..df2d7e7 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,6 +16,7 @@
package android.content.om;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -546,6 +547,7 @@
* @param configuration The string representation of the config this overlay is enabled for
*/
@NonNull
+ @FlaggedApi(android.content.res.Flags.FLAG_ASSET_FILE_DESCRIPTOR_FRRO)
public void setResourceValue(
@NonNull String resourceName,
@NonNull AssetFileDescriptor value,
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 563ed7d..e9f419e 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -22,6 +22,7 @@
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 @@
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 dbaa4c9..0cd4358 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.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.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 @@
}
/**
+ * 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/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/core/java/android/content/pm/LauncherUserInfo.aidl
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to core/java/android/content/pm/LauncherUserInfo.aidl
index 0aa6b0b..f875f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/core/java/android/content/pm/LauncherUserInfo.aidl
@@ -11,12 +11,9 @@
* 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.
+ * limitations under the License
*/
-package com.android.systemui.qs.tiles.base.interactor
+package android.content.pm;
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
-)
+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 0000000..214c3e4
--- /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 1b60f8e..b15c9e4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1256,8 +1256,10 @@
public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
/**
- * @hide
+ * Querying flag: always match components of packages in quarantined state.
+ * @see #isPackageQuarantined
*/
+ @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;
/**
@@ -9902,12 +9904,16 @@
/**
* Query if an app is currently quarantined.
+ * A misbehaving app can be quarantined by e.g. a system of another privileged entity.
+ * Quarantined apps are similar to disabled, but still visible in e.g. Launcher.
+ * Only activities of such apps can still be queried, but not services etc.
+ * Quarantined apps can't be bound to, and won't receive broadcasts.
+ * They can't be resolved, unless {@link #MATCH_QUARANTINED_COMPONENTS} specified.
*
* @return {@code true} if the given package is quarantined, {@code false} otherwise
* @throws NameNotFoundException if the package could not be found.
- *
- * @hide
*/
+ @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException {
throw new UnsupportedOperationException("isPackageQuarantined not implemented");
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index db12728..96609ad 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -15,9 +15,9 @@
}
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/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 5cc3b92..c7790bd 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,8 @@
import android.annotation.RawRes;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
+import android.app.LocaleConfig;
+import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -426,38 +428,59 @@
String[] selectedLocales = null;
String defaultLocale = null;
+ LocaleConfig lc = ResourcesManager.getInstance().getLocaleConfig();
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
- String[] availableLocales;
- // The LocaleList has changed. We must query the AssetManager's
- // available Locales and figure out the best matching Locale in the new
- // LocaleList.
- availableLocales = mAssets.getNonSystemLocales();
- if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- // No app defined locales, so grab the system locales.
- availableLocales = mAssets.getLocales();
- if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- availableLocales = null;
+ if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+ Locale[] intersection =
+ locales.getIntersection(lc.getSupportedLocales());
+ mConfiguration.setLocales(new LocaleList(intersection));
+ selectedLocales = new String[intersection.length];
+ for (int i = 0; i < intersection.length; i++) {
+ selectedLocales[i] =
+ adjustLanguageTag(intersection[i].toLanguageTag());
}
- }
+ defaultLocale =
+ adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+ } else {
+ String[] availableLocales;
+ // The LocaleList has changed. We must query the AssetManager's
+ // available Locales and figure out the best matching Locale in the new
+ // LocaleList.
+ availableLocales = mAssets.getNonSystemLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ // No app defined locales, so grab the system locales.
+ availableLocales = mAssets.getLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ availableLocales = null;
+ }
+ }
- if (availableLocales != null) {
- final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
- availableLocales);
- if (bestLocale != null) {
- selectedLocales = new String[]{
- adjustLanguageTag(bestLocale.toLanguageTag())};
- if (!bestLocale.equals(locales.get(0))) {
- mConfiguration.setLocales(
- new LocaleList(bestLocale, locales));
+ if (availableLocales != null) {
+ final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+ availableLocales);
+ if (bestLocale != null) {
+ selectedLocales = new String[]{
+ adjustLanguageTag(bestLocale.toLanguageTag())};
+ if (!bestLocale.equals(locales.get(0))) {
+ mConfiguration.setLocales(
+ new LocaleList(bestLocale, locales));
+ }
}
}
}
}
}
if (selectedLocales == null) {
- selectedLocales = new String[]{
- adjustLanguageTag(locales.get(0).toLanguageTag())};
+ if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+ selectedLocales = new String[locales.size()];
+ for (int i = 0; i < locales.size(); i++) {
+ selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+ }
+ } else {
+ selectedLocales = new String[]{
+ adjustLanguageTag(locales.get(0).toLanguageTag())};
+ }
}
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 0c2c0f4..1b8eb07 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -8,3 +8,10 @@
# fixed_read_only or device wont boot because of permission issues accessing flags during boot
is_fixed_read_only: true
}
+
+flag {
+ name: "asset_file_descriptor_frro"
+ namespace: "resource_manager"
+ description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
+ bug: "304478666"
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 20771af..524afe9 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -153,7 +153,7 @@
mService.getCandidateCredentials(
request,
new GetCandidateCredentialsTransport(executor, callback),
- mContext.getOpPackageName());
+ callingPackage);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index b003e75..8a4f678 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -701,7 +701,7 @@
* }
* </pre>
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public void beginTransactionReadOnly() {
beginTransactionWithListenerReadOnly(null);
}
@@ -785,7 +785,7 @@
* }
* </pre>
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public void beginTransactionWithListenerReadOnly(
@Nullable SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
@@ -2224,7 +2224,7 @@
* @throws IllegalStateException if a transaction is not in progress.
* @throws SQLiteException if the SQL cannot be compiled.
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
@NonNull
public SQLiteRawStatement createRawStatement(@NonNull String sql) {
Objects.requireNonNull(sql);
@@ -2244,7 +2244,7 @@
* @return The ROWID of the last row to be inserted under this connection.
* @throws IllegalStateException if there is no current transaction.
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getLastInsertRowId() {
return getThreadSession().getLastInsertRowId();
}
@@ -2258,7 +2258,7 @@
* @return The number of rows changed by the most recent sql statement
* @throws IllegalStateException if there is no current transaction.
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getLastChangedRowCount() {
return getThreadSession().getLastChangedRowCount();
}
@@ -2286,7 +2286,7 @@
* @return The number of rows changed on the current connection.
* @throws IllegalStateException if there is no current transaction.
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getTotalChangedRowCount() {
return getThreadSession().getTotalChangedRowCount();
}
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 827420f..33f602b 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -71,7 +71,7 @@
*
* @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
*/
-@FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+@FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public final class SQLiteRawStatement implements Closeable {
private static final String TAG = "SQLiteRawStatement";
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 564df03..62a5123 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -1,7 +1,7 @@
package: "android.database.sqlite"
flag {
- name: "sqlite_apis_15"
+ name: "sqlite_apis_35"
namespace: "system_performance"
is_fixed_read_only: true
description: "SQLite APIs held back for Android 15"
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
index 1333f0d..a9742cb 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/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 8bfc2f7..014cf6d 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -16,21 +16,28 @@
package android.hardware;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.hardware.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
import libcore.util.NativeAllocationRegistry;
/**
- * The class provides overlay properties of the device. OverlayProperties
- * exposes some capabilities from HWC e.g. if fp16 can be supported for HWUI.
+ * Provides supported overlay properties of the device.
*
- * In the future, more capabilities can be added, e.g., whether or not
- * per-layer colorspaces are supported.
- *
- * @hide
+ * <p>
+ * Hardware overlay is a technique to composite different buffers directly
+ * to the screen using display hardware rather than the GPU.
+ * The system compositor is able to assign any content managed by a
+ * {@link android.view.SurfaceControl} onto a hardware overlay if possible.
+ * Applications may be interested in the display hardware capabilities exposed
+ * by this class as a hint to determine if their {@link android.view.SurfaceControl}
+ * tree is power-efficient and performant.
+ * </p>
*/
+@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public final class OverlayProperties implements Parcelable {
private static final NativeAllocationRegistry sRegistry =
@@ -38,10 +45,12 @@
nGetDestructor());
private long mNativeObject;
+ // only for virtual displays
+ private static OverlayProperties sDefaultOverlayProperties;
// Invoked on destruction
private Runnable mCloser;
- public OverlayProperties(long nativeObject) {
+ private OverlayProperties(long nativeObject) {
if (nativeObject != 0) {
mCloser = sRegistry.registerNativeAllocation(this, nativeObject);
}
@@ -49,7 +58,20 @@
}
/**
+ * For virtual displays, we provide an overlay properties object
+ * with RGBA 8888 only, sRGB only, true for mixed color spaces.
+ * @hide
+ */
+ public static OverlayProperties getDefault() {
+ if (sDefaultOverlayProperties == null) {
+ sDefaultOverlayProperties = new OverlayProperties(nCreateDefault());
+ }
+ return sDefaultOverlayProperties;
+ }
+
+ /**
* @return True if the device can support fp16, false otherwise.
+ * @hide
*/
public boolean supportFp16ForHdr() {
if (mNativeObject == 0) {
@@ -59,8 +81,13 @@
}
/**
- * @return True if the device can support mixed colorspaces, false otherwise.
+ * Indicates that hw composition of two or more overlays
+ * with different colorspaces is supported on the device.
+ *
+ * @return True if the device can support mixed colorspaces efficiently,
+ * false if GPU composition fallback is otherwise required.
*/
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public boolean supportMixedColorSpaces() {
if (mNativeObject == 0) {
return false;
@@ -68,28 +95,14 @@
return nSupportMixedColorSpaces(mNativeObject);
}
- /**
- * Release the local reference.
- */
- public void release() {
- if (mNativeObject != 0) {
- mCloser.run();
- mNativeObject = 0;
- }
- }
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
@Override
public int describeContents() {
return 0;
}
- /**
- * Flatten this object in to a Parcel.
- *
- * @param dest The Parcel in which the object should be written.
- * @param flags Additional flags about how the object should be written.
- * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
- */
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
if (mNativeObject == 0) {
@@ -100,6 +113,7 @@
nWriteOverlayPropertiesToParcel(mNativeObject, dest);
}
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public static final @NonNull Parcelable.Creator<OverlayProperties> CREATOR =
new Parcelable.Creator<OverlayProperties>() {
public OverlayProperties createFromParcel(Parcel in) {
@@ -115,6 +129,7 @@
};
private static native long nGetDestructor();
+ private static native long nCreateDefault();
private static native boolean nSupportFp16ForHdr(long nativeObject);
private static native boolean nSupportMixedColorSpaces(long nativeObject);
private static native void nWriteOverlayPropertiesToParcel(long nativeObject, Parcel dest);
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 490ff64..7a43286 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -805,7 +805,7 @@
* 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 @@
* 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 @@
* 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 6ac1efb..39fbe83 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -20,6 +20,7 @@
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 @@
/**
* 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 @@
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 @@
* 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 @@
* 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 @@
* 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 @@
* @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 @@
* Get {@link PresentationSession} object.
* @return {@link PresentationSession} object or null if this doesn't contain one.
*/
- public PresentationSession getPresentationSession() {
+ public @Nullable PresentationSession getPresentationSession() {
return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null;
}
/**
- * Get {@link KeyAgreement} object.
+ * Get {@link KeyAgreement} object. A key-agreement protocol is a protocol whereby
+ * two or more parties can agree on a shared secret using public key cryptography.
+ *
* @return {@link KeyAgreement} object or null if this doesn't contain one.
*/
@FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
- public KeyAgreement getKeyAgreement() {
+ public @Nullable KeyAgreement getKeyAgreement() {
return mCrypto instanceof KeyAgreement ? (KeyAgreement) mCrypto : null;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index bf77681..db7055b 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -357,7 +357,7 @@
mCameraRepeatingSurface = mRepeatingRequestImageReader.getSurface();
}
mRepeatingRequestImageCallback = new CameraOutputImageCallback(
- mRepeatingRequestImageReader);
+ mRepeatingRequestImageReader, true /*pruneOlderBuffers*/);
mRepeatingRequestImageReader
.setOnImageAvailableListener(mRepeatingRequestImageCallback, mHandler);
}
@@ -398,7 +398,8 @@
CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT);
}
- mBurstCaptureImageCallback = new CameraOutputImageCallback(mBurstCaptureImageReader);
+ mBurstCaptureImageCallback = new CameraOutputImageCallback(mBurstCaptureImageReader,
+ false /*pruneOlderBuffers*/);
mBurstCaptureImageReader.setOnImageAvailableListener(mBurstCaptureImageCallback,
mHandler);
mCameraBurstSurface = mBurstCaptureImageReader.getSurface();
@@ -1106,7 +1107,9 @@
}
for (Pair<Image, TotalCaptureResult> captureStage : mCaptureStageMap.values()) {
- captureStage.first.close();
+ if (captureStage.first != null) {
+ captureStage.first.close();
+ }
}
mCaptureStageMap.clear();
}
@@ -1207,6 +1210,7 @@
if (mImageProcessor != null) {
if (mCapturePendingMap.indexOfKey(timestamp) >= 0) {
Image img = mCapturePendingMap.get(timestamp).first;
+ mCapturePendingMap.remove(timestamp);
mCaptureStageMap.put(stageId, new Pair<>(img, result));
checkAndFireBurstProcessing();
} else {
@@ -1303,6 +1307,7 @@
reader.detachImage(img);
if (mCapturePendingMap.indexOfKey(timestamp) >= 0) {
Integer stageId = mCapturePendingMap.get(timestamp).second;
+ mCapturePendingMap.remove(timestamp);
Pair<Image, TotalCaptureResult> captureStage =
mCaptureStageMap.get(stageId);
if (captureStage != null) {
@@ -1402,9 +1407,11 @@
private HashMap<Long, Pair<Image, OnImageAvailableListener>> mImageListenerMap =
new HashMap<>();
private boolean mOutOfBuffers = false;
+ private final boolean mPruneOlderBuffers;
- CameraOutputImageCallback(ImageReader imageReader) {
+ CameraOutputImageCallback(ImageReader imageReader, boolean pruneOlderBuffers) {
mImageReader = imageReader;
+ mPruneOlderBuffers = pruneOlderBuffers;
}
@Override
@@ -1447,6 +1454,10 @@
ArrayList<Long> removedTs = new ArrayList<>();
for (long ts : timestamps) {
if (ts < timestamp) {
+ if (!mPruneOlderBuffers) {
+ Log.w(TAG, "Unexpected older image with ts: " + ts);
+ continue;
+ }
Log.e(TAG, "Dropped image with ts: " + ts);
Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts);
if (entry.second != null) {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 8decd50..2b5f5ee 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -220,7 +220,7 @@
registerCallbackIfNeededLocked();
- if (DEBUG || extraLogging()) {
+ if (DEBUG) {
Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
}
return info;
@@ -402,7 +402,7 @@
}
private void maybeLogAllDisplayListeners() {
- if (!sExtraDisplayListenerLogging) {
+ if (!extraLogging()) {
return;
}
@@ -1222,7 +1222,7 @@
private void handleMessage(Message msg) {
if (extraLogging()) {
- Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what)
+ Slog.i(TAG, "DLD(" + eventToString(msg.what)
+ ", display=" + msg.arg1
+ ", mEventsMask=" + Long.toBinaryString(mEventsMask)
+ ", mPackageName=" + mPackageName
@@ -1231,9 +1231,10 @@
}
if (DEBUG) {
Trace.beginSection(
- "DisplayListenerDelegate(" + eventToString(msg.what)
+ TextUtils.trimToSize(
+ "DLD(" + eventToString(msg.what)
+ ", display=" + msg.arg1
- + ", listener=" + mListener.getClass() + ")");
+ + ", listener=" + mListener.getClass() + ")", 127));
}
switch (msg.what) {
case EVENT_DISPLAY_ADDED:
@@ -1422,11 +1423,12 @@
sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
&& EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
}
- return sExtraDisplayListenerLogging;
+ // TODO: b/306170135 - return sExtraDisplayListenerLogging instead
+ return true;
}
private static boolean extraLogging() {
- return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals(
- sCurrentPackageName);
+ // TODO: b/306170135 - return sExtraDisplayListenerLogging & package name check instead
+ return true;
}
}
diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
new file mode 100644
index 0000000..c6a352e
--- /dev/null
+++ b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.hardware.flags"
+
+flag {
+ name: "overlayproperties_class_api"
+ namespace: "core_graphics"
+ description: "public OverlayProperties class, OverlayProperties#supportMixedColorSpaces and Display#getOverlaySupport API"
+ bug: "267234573"
+}
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 861e440..6952e5d 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -2,5 +2,4 @@
include /services/core/java/com/android/server/display/OWNERS
-marvinramin@google.com
-lcnathalie@google.com
+quxiangfang@google.com
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4c2bbc1..ba80811 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3363,6 +3363,13 @@
return true;
}
return false;
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
+ event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) {
+ if (mDecorViewVisible && mWindowVisible) {
+ int direction = (event.getMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+ mPrivOps.switchKeyboardLayoutAsync(direction);
+ return true;
+ }
}
return doMovementKey(keyCode, event, MOVEMENT_DOWN);
}
diff --git a/core/java/android/nfc/Constants.java b/core/java/android/nfc/Constants.java
new file mode 100644
index 0000000..f768330
--- /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 4658630..4a7bd3f 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -24,6 +24,7 @@
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.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 @@
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 958669e..ae3e333 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -34,6 +34,7 @@
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 @@
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 @@
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 18ec914..665b753 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@
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 @@
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 @@
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 @@
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 @@
*/
@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 @@
}
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 4909b08..32c2a1b 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -26,6 +26,7 @@
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 @@
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 ec919e4..33bc169 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -173,7 +173,7 @@
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 @@
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 @@
*/
@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/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 55b0b42..cd50ace 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -13,3 +13,10 @@
description: "Flag for NFC reader option API changes"
bug: "291187960"
}
+
+flag {
+ name: "enable_nfc_user_restriction"
+ namespace: "nfc"
+ description: "Flag for NFC user restriction"
+ bug: "291187960"
+}
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 47ad72f..e8ad303 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -133,6 +134,7 @@
* The maximum value of supported bugreport mode.
* @hide
*/
+ @FlaggedApi(android.os.Flags.FLAG_BUGREPORT_MODE_MAX_VALUE)
@TestApi
public static final int BUGREPORT_MODE_MAX_VALUE = BUGREPORT_MODE_ONBOARDING;
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd28..d7e440b 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -153,21 +153,21 @@
/**
* Find the intersection between this LocaleList and another
- * @return a String array of the Locales in both LocaleLists
+ * @return an array of the Locales in both LocaleLists
* {@hide}
*/
@NonNull
- public String[] getIntersection(@NonNull LocaleList other) {
- List<String> intersection = new ArrayList<>();
+ public Locale[] getIntersection(@NonNull LocaleList other) {
+ List<Locale> intersection = new ArrayList<>();
for (Locale l1 : mList) {
for (Locale l2 : other.mList) {
if (matchesLanguageAndScript(l2, l1)) {
- intersection.add(l1.toLanguageTag());
+ intersection.add(l1);
break;
}
}
}
- return intersection.toArray(new String[0]);
+ return intersection.toArray(new Locale[0]);
}
/**
diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java
index 151a65f..ca1d49a 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 @@
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 4c8ef97..72bc211 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -23,6 +23,7 @@
import android.accounts.AccountManager;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -58,6 +59,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
+import android.nfc.Flags;
import android.provider.Settings;
import android.util.AndroidException;
import android.util.ArraySet;
@@ -1871,6 +1873,7 @@
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_USER_RESTRICTION)
public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO =
"no_near_field_communication_radio";
@@ -3330,7 +3333,10 @@
*
* @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 +3410,10 @@
* @hide
*/
@SystemApi
- @UserHandleAware
- @RequiresPermission(anyOf = {
- "android.permission.INTERACT_ACROSS_USERS",
- "android.permission.MANAGE_USERS"
- })
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public boolean isUserVisible() {
try {
return mService.isUserVisible(mUserId);
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index b7f2e06..c4521c0 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -27,3 +27,10 @@
description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
bug: "299069460"
}
+
+flag {
+ name: "bugreport_mode_max_value"
+ namespace: "telephony"
+ description: "Introduce a constant as maximum value of bugreport mode."
+ bug: "305067125"
+}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a391571..27ad45d 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3196,6 +3196,16 @@
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/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 800149c..94eca3d 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -12,6 +12,7 @@
namespace: "hardware_backed_security"
description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
bug: "296464083"
+ is_fixed_read_only: true
}
flag {
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index a29bf7a..1afe8d9 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -1401,6 +1401,7 @@
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 @@
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 @@
fieldDialogPresentation);
}
builder.setAuthentication(authentication);
+ builder.setAuthenticationExtras(authenticationExtras);
builder.setId(datasetId);
Dataset dataset = builder.build();
dataset.mEligibleReason = eligibleReason;
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c82a4ca..2a4cbaf 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,8 +28,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -59,8 +57,7 @@
* @hide
*/
public NotificationRankingUpdate(Parcel in) {
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
// Recover the ranking map from the SharedMemory and store it in mapParcel.
final Parcel mapParcel = Parcel.obtain();
ByteBuffer buffer = null;
@@ -176,8 +173,7 @@
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
final Parcel mapParcel = Parcel.obtain();
ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>();
Bundle smartActionsBundle = new Bundle();
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
new file mode 100644
index 0000000..2931435
--- /dev/null
+++ b/core/java/android/service/notification/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.notification"
+
+flag {
+ name: "ranking_update_ashmem"
+ namespace: "systemui"
+ description: "This flag controls moving ranking update contents into ashmem"
+ bug: "284297289"
+}
+
diff --git a/core/java/android/service/voice/HotwordTrainingAudio.java b/core/java/android/service/voice/HotwordTrainingAudio.java
index 91e34dc..916fa36b 100644
--- a/core/java/android/service/voice/HotwordTrainingAudio.java
+++ b/core/java/android/service/voice/HotwordTrainingAudio.java
@@ -16,6 +16,7 @@
package android.service.voice;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -23,6 +24,7 @@
import android.media.AudioFormat;
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.voice.flags.Flags;
import com.android.internal.util.DataClass;
@@ -33,6 +35,7 @@
*
* @hide
*/
+@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@DataClass(
genConstructor = false,
genBuilder = true,
@@ -65,12 +68,12 @@
/**
* App-defined identifier to distinguish hotword training audio instances.
- */
+ * <p> Returns -1 if unset. */
@NonNull
private final int mAudioType;
private static int defaultAudioType() {
- return 0;
+ return -1;
}
/**
@@ -152,6 +155,7 @@
/**
* App-defined identifier to distinguish hotword training audio instances.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull int getAudioType() {
@@ -274,6 +278,7 @@
/**
* A builder for {@link HotwordTrainingAudio}
*/
+ @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static final class Builder extends BaseBuilder {
@@ -318,6 +323,7 @@
/**
* App-defined identifier to distinguish hotword training audio instances.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull Builder setAudioType(@NonNull int value) {
@@ -368,7 +374,7 @@
}
@DataClass.Generated(
- time = 1694193905346L,
+ time = 1697827049629L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java",
inputSignatures = "public static final int HOTWORD_OFFSET_UNSET\nprivate final @android.annotation.NonNull byte[] mHotwordAudio\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull int mAudioType\nprivate int mHotwordOffsetMillis\nprivate java.lang.String hotwordAudioToString()\nprivate static int defaultAudioType()\nclass HotwordTrainingAudio extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/service/voice/HotwordTrainingData.java b/core/java/android/service/voice/HotwordTrainingData.java
index 31aeb9c..aa6dab3 100644
--- a/core/java/android/service/voice/HotwordTrainingData.java
+++ b/core/java/android/service/voice/HotwordTrainingData.java
@@ -16,10 +16,12 @@
package android.service.voice;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.voice.flags.Flags;
import android.text.TextUtils;
import com.android.internal.util.DataClass;
@@ -47,6 +49,7 @@
genParcelable = true,
genToString = true)
@SystemApi
+@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
public final class HotwordTrainingData implements Parcelable {
/** Max size for hotword training data in bytes. */
public static int getMaxTrainingDataBytes() {
@@ -63,11 +66,11 @@
}
/** App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset. */
+ * <p> Returns -1 if unset. */
private final int mTimeoutStage;
private static int defaultTimeoutStage() {
- return 0;
+ return -1;
}
private void onConstructed() {
@@ -120,7 +123,7 @@
/**
* App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public int getTimeoutStage() {
@@ -218,6 +221,7 @@
/**
* A builder for {@link HotwordTrainingData}
*/
+ @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static final class Builder {
@@ -251,7 +255,7 @@
/**
* App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull Builder setTimeoutStage(int value) {
@@ -287,7 +291,7 @@
}
@DataClass.Generated(
- time = 1696092128091L,
+ time = 1697826948280L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingData.java",
inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudioList\nprivate final int mTimeoutStage\npublic static int getMaxTrainingDataBytes()\nprivate static java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudioList()\nprivate static int defaultTimeoutStage()\nprivate void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 65a1da6..4c81888 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -190,7 +190,8 @@
@Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
boolean useFallbackLineSpacing) {
return replaceOrMake(source, paint, outerWidth, align, 1.0f, 0.0f, metrics, includePad,
- ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */);
+ ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */,
+ null /* minimumFontMetrics */);
}
/** @hide */
@@ -199,7 +200,8 @@
@NonNull Alignment align, float spacingMultiplier, float spacingAmount,
@NonNull BoringLayout.Metrics metrics, boolean includePad,
@Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
- boolean useFallbackLineSpacing, boolean useBoundsForWidth) {
+ boolean useFallbackLineSpacing, boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
boolean trust;
if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
@@ -270,7 +272,8 @@
spacingAdd, includePad, false /* fallbackLineSpacing */,
outerwidth /* ellipsizedWidth */, null /* ellipsize */, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
- null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+ null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false,
+ null);
mEllipsizedWidth = outerwidth;
mEllipsizedStart = 0;
@@ -343,7 +346,7 @@
ellipsizedWidth, ellipsize, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
null /* rightIndents */, JUSTIFICATION_MODE_NONE,
- LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */);
+ LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, null);
}
/** @hide */
@@ -359,12 +362,13 @@
int ellipsizedWidth,
TextUtils.TruncateAt ellipsize,
Metrics metrics,
- boolean useBoundsForWidth) {
+ boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
this(text, paint, width, align, TextDirectionHeuristics.LTR,
spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth,
ellipsize, 1 /* maxLines */, Layout.BREAK_STRATEGY_SIMPLE,
Layout.HYPHENATION_FREQUENCY_NONE, null, null, Layout.JUSTIFICATION_MODE_NONE,
- LineBreakConfig.NONE, metrics, useBoundsForWidth);
+ LineBreakConfig.NONE, metrics, useBoundsForWidth, minimumFontMetrics);
}
/* package */ BoringLayout(
@@ -387,12 +391,13 @@
int justificationMode,
LineBreakConfig lineBreakConfig,
Metrics metrics,
- boolean useBoundsForWidth) {
+ boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad,
fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy,
hyphenationFrequency, leftIndents, rightIndents, justificationMode,
- lineBreakConfig, useBoundsForWidth);
+ lineBreakConfig, useBoundsForWidth, minimumFontMetrics);
boolean trust;
@@ -548,6 +553,15 @@
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Metrics metrics) {
+ return isBoring(text, paint, textDir, useFallbackLineSpacing, null, metrics);
+ }
+
+ /**
+ * @hide
+ */
+ public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
+ @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
+ @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
final int textLength = text.length();
if (hasAnyInterestingChars(text, textLength)) {
return null; // There are some interesting characters. Not boring.
@@ -570,6 +584,19 @@
fm.reset();
}
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (minimumFontMetrics == null) {
+ paint.getFontMetricsIntForLocale(fm);
+ } else {
+ fm.set(minimumFontMetrics);
+ // Because the font metrics is provided by public APIs, adjust the top/bottom with
+ // ascent/descent: top must be smaller than ascent, bottom must be larger than
+ // descent.
+ fm.top = Math.min(fm.top, fm.ascent);
+ fm.bottom = Math.max(fm.bottom, fm.descent);
+ }
+ }
+
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 46fa501..0421d5a 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -27,14 +27,6 @@
* @hide
*/
public class ClientFlags {
-
- /**
- * @see Flags#deprecateFontsXml()
- */
- public static boolean deprecateFontsXml() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_DEPRECATE_FONTS_XML);
- }
-
/**
* @see Flags#noBreakNoHyphenationSpan()
*/
@@ -55,4 +47,11 @@
public static boolean useBoundsForWidth() {
return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH);
}
+
+ /**
+ * @see Flags#fixLineHeightForLocale()
+ */
+ public static boolean fixLineHeightForLocale() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
+ }
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index a0cd074..7b9cb6a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
@@ -315,6 +316,43 @@
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the
+ * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+ * used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
+ /**
* Build the {@link DynamicLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this method.
@@ -347,6 +385,7 @@
private int mEllipsizedWidth;
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
+ private @Nullable Paint.FontMetrics mMinimumFontMetrics;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -422,7 +461,7 @@
false /* fallbackLineSpacing */, ellipsizedWidth, ellipsize,
Integer.MAX_VALUE /* maxLines */, breakStrategy, hyphenationFrequency,
null /* leftIndents */, null /* rightIndents */, justificationMode,
- lineBreakConfig, false /* useBoundsForWidth */);
+ lineBreakConfig, false /* useBoundsForWidth */, null /* minimumFontMetrics */);
final Builder b = Builder.obtain(base, paint, width)
.setAlignment(align)
@@ -448,7 +487,7 @@
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
Integer.MAX_VALUE /* maxLines */, b.mBreakStrategy, b.mHyphenationFrequency,
null /* leftIndents */, null /* rightIndents */, b.mJustificationMode,
- b.mLineBreakConfig, b.mUseBoundsForWidth);
+ b.mLineBreakConfig, b.mUseBoundsForWidth, b.mMinimumFontMetrics);
mDisplay = b.mDisplay;
mIncludePad = b.mIncludePad;
@@ -476,6 +515,7 @@
mBase = b.mBase;
mFallbackLineSpacing = b.mFallbackLineSpacing;
mUseBoundsForWidth = b.mUseBoundsForWidth;
+ mMinimumFontMetrics = b.mMinimumFontMetrics;
if (b.mEllipsize != null) {
mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
mEllipsizedWidth = b.mEllipsizedWidth;
@@ -672,6 +712,7 @@
.setAddLastLineLineSpacing(!islast)
.setIncludePad(false)
.setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics)
.setCalculateBounds(true);
reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
@@ -1324,6 +1365,7 @@
private Rect mTempRect = new Rect();
private boolean mUseBoundsForWidth;
+ @Nullable Paint.FontMetrics mMinimumFontMetrics;
@UnsupportedAppUsage
private static StaticLayout sStaticLayout = null;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 4f4dea7..47c29d9 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
@@ -287,7 +288,7 @@
this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingMult, spacingAdd, false, false, 0, null, Integer.MAX_VALUE,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null, null,
- JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+ JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, null);
}
/**
@@ -336,7 +337,8 @@
int[] rightIndents,
int justificationMode,
LineBreakConfig lineBreakConfig,
- boolean useBoundsForWidth
+ boolean useBoundsForWidth,
+ Paint.FontMetrics minimumFontMetrics
) {
if (width < 0)
@@ -371,6 +373,7 @@
mJustificationMode = justificationMode;
mLineBreakConfig = lineBreakConfig;
mUseBoundsForWidth = useBoundsForWidth;
+ mMinimumFontMetrics = minimumFontMetrics;
}
/**
@@ -3332,6 +3335,7 @@
private int mJustificationMode;
private LineBreakConfig mLineBreakConfig;
private boolean mUseBoundsForWidth;
+ private @Nullable Paint.FontMetrics mMinimumFontMetrics;
/** @hide */
@IntDef(prefix = { "DIR_" }, value = {
@@ -3787,12 +3791,48 @@
return this;
}
+ /**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left it as default, the font
+ * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
private BoringLayout.Metrics isBoring() {
if (mStart != 0 || mEnd != mText.length()) { // BoringLayout only support entire text.
return null;
}
BoringLayout.Metrics metrics = BoringLayout.isBoring(mText, mPaint, mTextDir,
- mFallbackLineSpacing, null);
+ mFallbackLineSpacing, mMinimumFontMetrics, null);
if (metrics == null) {
return null;
}
@@ -3833,7 +3873,8 @@
mText, mPaint, mWidth, mAlignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad, mFallbackLineSpacing, mEllipsizedWidth, mEllipsize, mMaxLines,
mBreakStrategy, mHyphenationFrequency, mLeftIndents, mRightIndents,
- mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth);
+ mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
}
@@ -3858,6 +3899,7 @@
private int mJustificationMode = JUSTIFICATION_MODE_NONE;
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
+ private Paint.FontMetrics mMinimumFontMetrics;
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -4164,4 +4206,22 @@
public boolean getUseBoundsForWidth() {
return mUseBoundsForWidth;
}
+
+ /**
+ * Get the minimum font metrics used for line spacing.
+ *
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ *
+ * @return a minimum font metrics. {@code null} for using the value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ */
+ @Nullable
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Paint.FontMetrics getMinimumFontMetrics() {
+ return mMinimumFontMetrics;
+ }
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 01279ce..77e616b 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
@@ -460,6 +461,43 @@
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the
+ * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+ * used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
+ /**
* Build the {@link StaticLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this
@@ -520,6 +558,7 @@
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
private boolean mCalculateBounds;
+ @Nullable private Paint.FontMetrics mMinimumFontMetrics;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -550,7 +589,8 @@
null, // rightIndents
JUSTIFICATION_MODE_NONE,
null, // lineBreakConfig,
- false // useBoundsForWidth
+ false, // useBoundsForWidth
+ null // minimumFontMetrics
);
mColumns = COLUMNS_ELLIPSIZE;
@@ -627,7 +667,8 @@
b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
- b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth);
+ b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth,
+ b.mMinimumFontMetrics);
mColumns = columnSize;
if (b.mEllipsize != null) {
@@ -711,6 +752,35 @@
indents = null;
}
+ int defaultTop;
+ int defaultAscent;
+ int defaultDescent;
+ int defaultBottom;
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (b.mMinimumFontMetrics != null) {
+ defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
+ defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
+ defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
+ defaultBottom = (int) Math.ceil(b.mMinimumFontMetrics.bottom);
+ } else {
+ paint.getFontMetricsIntForLocale(fm);
+ defaultTop = fm.top;
+ defaultAscent = fm.ascent;
+ defaultDescent = fm.descent;
+ defaultBottom = fm.bottom;
+ }
+
+ // Because the font metrics is provided by public APIs, adjust the top/bottom with
+ // ascent/descent: top must be smaller than ascent, bottom must be larger than descent.
+ defaultTop = Math.min(defaultTop, defaultAscent);
+ defaultBottom = Math.max(defaultBottom, defaultDescent);
+ } else {
+ defaultTop = 0;
+ defaultAscent = 0;
+ defaultDescent = 0;
+ defaultBottom = 0;
+ }
+
final LineBreaker lineBreaker = new LineBreaker.Builder()
.setBreakStrategy(b.mBreakStrategy)
.setHyphenationFrequency(getBaseHyphenationFrequency(b.mHyphenationFrequency))
@@ -889,7 +959,10 @@
// measuring
int here = paraStart;
- int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmTop = defaultTop;
+ int fmBottom = defaultBottom;
+ int fmAscent = defaultAscent;
+ int fmDescent = defaultDescent;
int fmCacheIndex = 0;
int spanEndCacheIndex = 0;
int breakIndex = 0;
@@ -982,7 +1055,15 @@
&& mLineCount < mMaximumVisibleLineCount) {
final MeasuredParagraph measuredPara =
MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
- paint.getFontMetricsInt(fm);
+ if (ClientFlags.fixLineHeightForLocale()) {
+ fm.top = defaultTop;
+ fm.ascent = defaultAscent;
+ fm.descent = defaultDescent;
+ fm.bottom = defaultBottom;
+ } else {
+ paint.getFontMetricsInt(fm);
+ }
+
v = out(source,
bufEnd, bufEnd, fm.ascent, fm.descent,
fm.top, fm.bottom,
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 9edf298..2466386 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -55,10 +55,10 @@
* 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,
+ Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
};
/**
@@ -67,10 +67,10 @@
* 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(),
+ Flags.fixLineHeightForLocale(),
};
/**
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 52fe883..0000000
--- 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 5362138..0000000
--- 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 b0aa72a..0000000
--- 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 8696bfa..0000000
--- 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 0000000..201f680
--- /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 60f1e88..0000000
--- 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 0e4a69f..0000000
--- 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 c67a21b..0000000
--- 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 d89d5f4..0000000
--- 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/Display.java b/core/java/android/view/Display.java
index 17c7fcc..07dd882 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -18,8 +18,10 @@
import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
+import static android.hardware.flags.Flags.FLAG_OVERLAYPROPERTIES_CLASS_API;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1468,17 +1470,18 @@
}
/**
- * Returns null if it's virtual display.
- * @hide
+ * Returns the {@link OverlayProperties} of the display.
*/
- @Nullable
+ @FlaggedApi(FLAG_OVERLAYPROPERTIES_CLASS_API)
+ @NonNull
public OverlayProperties getOverlaySupport() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.type != TYPE_VIRTUAL) {
+ if (mDisplayInfo.type == TYPE_INTERNAL
+ || mDisplayInfo.type == TYPE_EXTERNAL) {
return mGlobal.getOverlaySupport();
}
- return null;
+ return OverlayProperties.getDefault();
}
}
@@ -2091,7 +2094,8 @@
private final int mModeId;
private final int mWidth;
private final int mHeight;
- private final float mRefreshRate;
+ private final float mPeakRefreshRate;
+ private final float mVsyncRate;
@NonNull
private final float[] mAlternativeRefreshRates;
@NonNull
@@ -2103,7 +2107,15 @@
*/
@TestApi
public Mode(int width, int height, float refreshRate) {
- this(INVALID_MODE_ID, width, height, refreshRate, new float[0], new int[0]);
+ this(INVALID_MODE_ID, width, height, refreshRate, refreshRate, new float[0],
+ new int[0]);
+ }
+
+ /**
+ * @hide
+ */
+ public Mode(int width, int height, float refreshRate, float vsyncRate) {
+ this(INVALID_MODE_ID, width, height, refreshRate, vsyncRate, new float[0], new int[0]);
}
/**
@@ -2111,18 +2123,29 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Mode(int modeId, int width, int height, float refreshRate) {
- this(modeId, width, height, refreshRate, new float[0], new int[0]);
+ this(modeId, width, height, refreshRate, refreshRate, new float[0], new int[0]);
}
/**
* @hide
*/
public Mode(int modeId, int width, int height, float refreshRate,
+ float[] alternativeRefreshRates,
+ @HdrCapabilities.HdrType int[] supportedHdrTypes) {
+ this(modeId, width, height, refreshRate, refreshRate, alternativeRefreshRates,
+ supportedHdrTypes);
+ }
+
+ /**
+ * @hide
+ */
+ public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate,
float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) {
mModeId = modeId;
mWidth = width;
mHeight = height;
- mRefreshRate = refreshRate;
+ mPeakRefreshRate = refreshRate;
+ mVsyncRate = vsyncRate;
mAlternativeRefreshRates =
Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
Arrays.sort(mAlternativeRefreshRates);
@@ -2173,7 +2196,17 @@
* Returns the refresh rate in frames per second.
*/
public float getRefreshRate() {
- return mRefreshRate;
+ return mPeakRefreshRate;
+ }
+
+ /**
+ * Returns the vsync rate in frames per second.
+ * The physical vsync rate may be higher than the refresh rate, as the refresh rate may be
+ * constrained by the system.
+ * @hide
+ */
+ public float getVsyncRate() {
+ return mVsyncRate;
}
/**
@@ -2219,7 +2252,7 @@
public boolean matches(int width, int height, float refreshRate) {
return mWidth == width &&
mHeight == height &&
- Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate);
+ Float.floatToIntBits(mPeakRefreshRate) == Float.floatToIntBits(refreshRate);
}
/**
@@ -2232,9 +2265,9 @@
*
* @hide
*/
- public boolean matchesIfValid(int width, int height, float refreshRate) {
+ public boolean matchesIfValid(int width, int height, float peakRefreshRate) {
if (!isWidthValid(width) && !isHeightValid(height)
- && !isRefreshRateValid(refreshRate)) {
+ && !isRefreshRateValid(peakRefreshRate)) {
return false;
}
if (isWidthValid(width) != isHeightValid(height)) {
@@ -2242,8 +2275,9 @@
}
return (!isWidthValid(width) || mWidth == width)
&& (!isHeightValid(height) || mHeight == height)
- && (!isRefreshRateValid(refreshRate)
- || Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate));
+ && (!isRefreshRateValid(peakRefreshRate)
+ || Float.floatToIntBits(mPeakRefreshRate)
+ == Float.floatToIntBits(peakRefreshRate));
}
/**
@@ -2262,7 +2296,7 @@
* @hide
*/
public boolean isRefreshRateSet() {
- return mRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
+ return mPeakRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
}
/**
@@ -2283,7 +2317,8 @@
return false;
}
Mode that = (Mode) other;
- return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate)
+ return mModeId == that.mModeId
+ && matches(that.mWidth, that.mHeight, that.mPeakRefreshRate)
&& Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates)
&& Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes);
}
@@ -2294,7 +2329,8 @@
hash = hash * 17 + mModeId;
hash = hash * 17 + mWidth;
hash = hash * 17 + mHeight;
- hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
+ hash = hash * 17 + Float.floatToIntBits(mPeakRefreshRate);
+ hash = hash * 17 + Float.floatToIntBits(mVsyncRate);
hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
return hash;
@@ -2306,7 +2342,8 @@
.append("id=").append(mModeId)
.append(", width=").append(mWidth)
.append(", height=").append(mHeight)
- .append(", fps=").append(mRefreshRate)
+ .append(", fps=").append(mPeakRefreshRate)
+ .append(", vsync=").append(mVsyncRate)
.append(", alternativeRefreshRates=")
.append(Arrays.toString(mAlternativeRefreshRates))
.append(", supportedHdrTypes=")
@@ -2321,8 +2358,8 @@
}
private Mode(Parcel in) {
- this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray(),
- in.createIntArray());
+ this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
+ in.createFloatArray(), in.createIntArray());
}
@Override
@@ -2330,7 +2367,8 @@
out.writeInt(mModeId);
out.writeInt(mWidth);
out.writeInt(mHeight);
- out.writeFloat(mRefreshRate);
+ out.writeFloat(mPeakRefreshRate);
+ out.writeFloat(mVsyncRate);
out.writeFloatArray(mAlternativeRefreshRates);
out.writeIntArray(mSupportedHdrTypes);
}
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index 1310b0c..6b354a0 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -16,16 +16,10 @@
package android.view;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.view.flags.Flags;
import com.android.internal.annotations.VisibleForTesting;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* {@link ScrollFeedbackProvider} that performs haptic feedback when scrolling.
*
@@ -36,16 +30,12 @@
* methods in this class. To check if your input device ID, source, and motion axis are valid for
* haptic feedback, you can use the
* {@link ViewConfiguration#isHapticScrollFeedbackEnabled(int, int, int)} API.
+ *
+ * @hide
*/
-@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
private static final String TAG = "HapticScrollFeedbackProvider";
- /** @hide */
- @IntDef(value = {MotionEvent.AXIS_SCROLL})
- @Retention(RetentionPolicy.SOURCE)
- public @interface HapticScrollFeedbackAxis {}
-
private static final int TICK_INTERVAL_NO_TICK = 0;
private static final boolean INITIAL_END_OF_LIST_HAPTICS_ENABLED = false;
@@ -89,8 +79,7 @@
}
@Override
- public void onScrollProgress(
- int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, int deltaInPixels) {
+ public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
@@ -117,8 +106,7 @@
}
@Override
- public void onScrollLimit(
- int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, boolean isStart) {
+ public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
@@ -135,7 +123,7 @@
}
@Override
- public void onSnapToItem(int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis) {
+ public void onSnapToItem(int inputDeviceId, int source, int axis) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index fb24211..90663c7 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1283,7 +1283,6 @@
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if ((types & mTypesBeingCancelled) != 0) {
final boolean monitoredAnimation =
animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
@@ -1295,12 +1294,15 @@
ImeTracker.forLatency().onHideCancelled(statsToken,
PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
}
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
}
throw new IllegalStateException("Cannot start a new insets animation of "
+ Type.toString(types)
+ " while an existing " + Type.toString(mTypesBeingCancelled)
+ " is being cancelled.");
}
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if (types == 0) {
// nothing to animate.
listener.onCancelled(null);
@@ -1309,8 +1311,6 @@
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
return;
}
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
mLastStartedAnimTypes |= types;
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 0ba4148..8a44d4f 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -17,6 +17,7 @@
package android.view;
import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.view.flags.Flags;
/**
@@ -62,23 +63,37 @@
* </ul>
*
* <b>Note</b> that not all valid input device source and motion axis inputs are necessarily
- * supported for scroll feedback. If you are implementing this interface, provide clear
- * documentation in your implementation class about which input device source and motion axis are
- * supported for your specific implementation. If you are using one of the implementations of this
- * interface, please refer to the documentation of the implementation for details on which input
- * device source and axis are supported.
+ * supported for scroll feedback; the implementation may choose to provide no feedback for some
+ * valid input device source and motion axis arguments.
*/
@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public interface ScrollFeedbackProvider {
+
+ /**
+ * Creates a {@link ScrollFeedbackProvider} implementation for this device.
+ *
+ * <p>Use a feedback provider created by this method, unless you intend to use your custom
+ * scroll feedback providing logic. This allows your use cases to generate scroll feedback that
+ * is consistent with the rest of the use cases on the device.
+ *
+ * @param view the {@link View} for which to provide scroll feedback.
+ * @return the default {@link ScrollFeedbackProvider} implementation for the device.
+ */
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
+ @NonNull
+ static ScrollFeedbackProvider createProvider(@NonNull View view) {
+ return new HapticScrollFeedbackProvider(view);
+ }
+
/**
* Call this when the view has snapped to an item.
*
- *
* @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering
* the snap.
* @param source the input source of the motion causing the snap.
* @param axis the axis of {@code event} that caused the item to snap.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onSnapToItem(int inputDeviceId, int source, int axis);
/**
@@ -99,6 +114,7 @@
* "start" for some views may be at the bottom of a scrolling list, while it may
* be at the top of scrolling list for others.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart);
/**
@@ -122,5 +138,6 @@
* @param axis the axis of {@code event} that caused scroll progress.
* @param deltaInPixels the amount of scroll progress, in pixels.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2f3d73a..e22207c 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -70,6 +70,7 @@
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 @@
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 @@
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
- removeFromRegistry();
+ SurfaceControlRegistry.getProcessInstance().remove(this);
} finally {
super.finalize();
}
@@ -1519,6 +1520,10 @@
*/
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 @@
mChoreographer = null;
}
}
- removeFromRegistry();
+ SurfaceControlRegistry.getProcessInstance().remove(this);
}
}
@@ -1791,7 +1796,7 @@
public float yDpi;
// Some modes have peak refresh rate lower than the panel vsync rate.
- public float refreshRate;
+ public float peakRefreshRate;
// Fixed rate of vsync deadlines for the panel.
// This can be higher then the peak refresh rate for some panel technologies
// See: VrrConfig.aidl
@@ -1815,7 +1820,7 @@
+ ", height=" + height
+ ", xDpi=" + xDpi
+ ", yDpi=" + yDpi
- + ", refreshRate=" + refreshRate
+ + ", peakRefreshRate=" + peakRefreshRate
+ ", vsyncRate=" + vsyncRate
+ ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
+ ", presentationDeadlineNanos=" + presentationDeadlineNanos
@@ -1833,7 +1838,7 @@
&& height == that.height
&& Float.compare(that.xDpi, xDpi) == 0
&& Float.compare(that.yDpi, yDpi) == 0
- && Float.compare(that.refreshRate, refreshRate) == 0
+ && Float.compare(that.peakRefreshRate, peakRefreshRate) == 0
&& Float.compare(that.vsyncRate, vsyncRate) == 0
&& appVsyncOffsetNanos == that.appVsyncOffsetNanos
&& presentationDeadlineNanos == that.presentationDeadlineNanos
@@ -1843,7 +1848,7 @@
@Override
public int hashCode() {
- return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, vsyncRate,
+ return Objects.hash(id, width, height, xDpi, yDpi, peakRefreshRate, vsyncRate,
appVsyncOffsetNanos, presentationDeadlineNanos, group,
Arrays.hashCode(supportedHdrTypes));
}
@@ -2765,8 +2770,10 @@
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 @@
applyResizedSurfaces();
notifyReparentedSurfaces();
nativeApplyTransaction(mNativeObject, sync, oneWay);
+
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "apply", this, null, null);
+ }
}
/**
@@ -2920,6 +2932,10 @@
@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 @@
@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 @@
@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 @@
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 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 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 @@
@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 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 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 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 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 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 @@
@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 @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 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 @@
@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 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 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 @@
@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 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 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 @@
@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 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 @@
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 0f35048..52be8f6 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -23,7 +23,9 @@
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 @@
// 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 @@
// 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 @@
}
}
+ @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 @@
* 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 @@
}
/**
+ * 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 @@
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 49b16c7..e9d0e4c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19,8 +19,6 @@
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.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.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 @@
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 @@
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 @@
*/
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 @@
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/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 2cf5d5d..ec96167 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -16,7 +16,6 @@
package android.view;
-import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.TestApi;
@@ -1272,12 +1271,10 @@
* @see InputDevice#getMotionRanges()
* @see InputDevice#getMotionRange(int)
* @see InputDevice#getMotionRange(int, int)
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
- public boolean isHapticScrollFeedbackEnabled(
- int inputDeviceId,
- @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
- int source) {
+ public boolean isHapticScrollFeedbackEnabled(int inputDeviceId, int axis, int source) {
if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return false;
if (source == InputDevice.SOURCE_ROTARY_ENCODER && axis == MotionEvent.AXIS_SCROLL) {
@@ -1318,12 +1315,10 @@
* returns {@code Integer.MAX_VALUE}.
*
* @see #isHapticScrollFeedbackEnabled(int, int, int)
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
- public int getHapticScrollFeedbackTickInterval(
- int inputDeviceId,
- @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
- int source) {
+ public int getHapticScrollFeedbackTickInterval(int inputDeviceId, int axis, int source) {
if (!mRotaryEncoderHapticScrollFeedbackEnabled) {
return NO_HAPTIC_SCROLL_TICK_INTERVAL;
}
@@ -1343,9 +1338,6 @@
* Checks if the View-based haptic scroll feedback implementation is enabled for
* {@link InputDevice#SOURCE_ROTARY_ENCODER}s.
*
- * <p>If this method returns {@code true}, the {@link HapticScrollFeedbackProvider} will be
- * muted for rotary encoders in favor of View's scroll haptics implementation.
- *
* @hide
*/
public boolean isViewBasedRotaryEncoderHapticScrollFeedbackEnabled() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 52b7cb1..dfada58 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -24,8 +24,6 @@
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_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.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 @@
*/
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 @@
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 @@
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
@@ -1051,7 +1020,8 @@
mDisplay = display;
mBasePackageName = context.getBasePackageName();
final String name = DisplayProperties.debug_vri_package().orElse(null);
- mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
+ // TODO: b/306170135 - return to using textutils check on package name.
+ mExtraDisplayListenerLogging = true;
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
@@ -1925,12 +1895,19 @@
}
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();
}
@@ -2038,6 +2015,10 @@
Slog.i(mTag, "DisplayState - old: " + oldDisplayState
+ ", new: " + newDisplayState);
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceCounter(Trace.TRACE_TAG_WINDOW_MANAGER,
+ "vri#screenState[" + mTag + "] state=", newDisplayState);
+ }
if (oldDisplayState != newDisplayState) {
mAttachInfo.mDisplayState = newDisplayState;
pokeDrawLockIfNeeded();
@@ -3287,8 +3268,8 @@
|| 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));
}
@@ -3877,11 +3858,7 @@
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.
@@ -3905,6 +3882,9 @@
}
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) {
@@ -3916,12 +3896,23 @@
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();
@@ -3933,6 +3924,8 @@
mPendingTransaction, mLastPerformDrawSkippedReason);
}
}
+ mWasLastDrawCanceled = cancelAndRedraw;
+ mLastTraversalWasVisible = isViewVisible;
if (mAttachInfo.mContentCaptureEvents != null) {
notifyContentCaptureEvents();
@@ -3953,12 +3946,6 @@
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() {
@@ -4728,10 +4715,7 @@
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);
}
};
@@ -4748,17 +4732,26 @@
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();
@@ -6011,8 +6004,6 @@
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
@@ -6308,32 +6299,6 @@
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;
}
}
}
@@ -7276,7 +7241,6 @@
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.
@@ -7297,22 +7261,6 @@
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;
}
@@ -11512,8 +11460,7 @@
@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);
@@ -11901,93 +11848,10 @@
t.clearTrustedPresentationCallback(getSurfaceControl());
}
- private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
- if (!shouldSetFrameRateCategory()) {
- return;
+ private void logAndTrace(String msg) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
}
-
- 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 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 cc612ed..6888b50 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 {
- 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/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 32256b9..b3359b7 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,8 +16,8 @@
package android.view.animation;
-import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
-import static android.view.flags.Flags.expectedPresentationTimeApi;
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY;
+import static android.view.flags.Flags.expectedPresentationTimeReadOnly;
import android.annotation.AnimRes;
import android.annotation.FlaggedApi;
@@ -67,6 +67,11 @@
@Overridable
public static final long OVERRIDE_ENABLE_EXPECTED_PRSENTATION_TIME = 278730197L;
+ private static boolean sExpectedPresentationTimeFlagValue;
+ static {
+ sExpectedPresentationTimeFlagValue = expectedPresentationTimeReadOnly();
+ }
+
private static class AnimationState {
boolean animationClockLocked;
long currentVsyncTimeMillis;
@@ -108,12 +113,12 @@
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) {
AnimationState state = sAnimationState.get();
state.animationClockLocked = true;
state.currentVsyncTimeMillis = vsyncMillis;
- if (!expectedPresentationTimeApi()) {
+ if (!sExpectedPresentationTimeFlagValue) {
state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
}
}
@@ -158,9 +163,9 @@
* @return the expected presentation time of a frame in the
* {@link System#nanoTime()} time base.
*/
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static long getExpectedPresentationTimeNanos() {
- if (!expectedPresentationTimeApi()) {
+ if (!sExpectedPresentationTimeFlagValue) {
return SystemClock.uptimeMillis();
}
@@ -176,7 +181,7 @@
* @return the expected presentation time of a frame in the
* {@link SystemClock#uptimeMillis()} time base.
*/
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static long getExpectedPresentationTimeMillis() {
return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index a40ff64..96574f5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3392,7 +3392,7 @@
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/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 42b3e38..57011e8 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -364,14 +364,6 @@
"enable_content_protection_receiver";
/**
- * Sets the size of the app blocklist for the content protection flow.
- *
- * @hide
- */
- public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
- "content_protection_apps_blocklist_size";
-
- /**
* Sets the size of the in-memory ring buffer for the content protection flow.
*
* @hide
@@ -440,8 +432,6 @@
/** @hide */
public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
/** @hide */
- public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
- /** @hide */
public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
/** @hide */
public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS =
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index fd96890..2b08eeb 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -22,6 +22,14 @@
}
flag {
+ name: "expected_presentation_time_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for using expected presentation time of the Choreographer"
+ bug: "278730197"
+ is_fixed_read_only: true
+}
+
+flag {
name: "set_frame_rate_callback"
namespace: "core_graphics"
description: "Enable the `setFrameRate` callback"
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f9d8b08..1bc7353 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -172,7 +172,6 @@
PHASE_CLIENT_HANDLE_HIDE_INSETS,
PHASE_CLIENT_APPLY_ANIMATION,
PHASE_CLIENT_CONTROL_ANIMATION,
- PHASE_CLIENT_DISABLED_USER_ANIMATION,
PHASE_CLIENT_COLLECT_SOURCE_CONTROLS,
PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW,
PHASE_CLIENT_REQUEST_IME_SHOW,
@@ -292,9 +291,6 @@
/** Started the IME window insets show animation. */
int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
- /** Checked that the IME is controllable. */
- int PHASE_CLIENT_DISABLED_USER_ANIMATION = ImeProtoEnums.PHASE_CLIENT_DISABLED_USER_ANIMATION;
-
/** Collecting insets source controls. */
int PHASE_CLIENT_COLLECT_SOURCE_CONTROLS = ImeProtoEnums.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS;
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 7eee33f..9f0b31b 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -770,7 +770,7 @@
}
/**
- * Set the font variation settings. Returns null if no variation is specified.
+ * Set the font variation settings. Set {@code null} if no variation is specified.
*
* @see Paint#getFontVariationSettings()
*/
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 103725d..f19a2f9 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -685,8 +685,9 @@
return false;
}
+ /** See {@link RemoteViews#visitUris(Consumer)}. **/
public void visitUris(@NonNull Consumer<Uri> visitor) {
- // Nothing to visit by default
+ // Nothing to visit by default.
}
}
@@ -761,9 +762,11 @@
}
/**
- * Note all {@link Uri} that are referenced internally, with the expectation
- * that Uri permission grants will need to be issued to ensure the recipient
- * of this object is able to render its contents.
+ * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+ * grants will need to be issued to ensure the recipient of this object is able to render its
+ * contents.
+ * See b/281044385 for more context and examples about what happens when this isn't done
+ * correctly.
*
* @hide
*/
@@ -1088,6 +1091,13 @@
public String getUniqueKey() {
return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ for (RemoteViews remoteViews : mList) {
+ remoteViews.visitUris(visitor);
+ }
+ }
}
/**
@@ -1289,6 +1299,12 @@
public String getUniqueKey() {
return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ items.visitUris(visitor);
+ }
}
private class SetRemoteViewsAdapterIntent extends Action {
@@ -1359,6 +1375,13 @@
public int getActionTag() {
return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated
+ // visitUris method in the Intent class, since it can contain other intents. Otherwise,
+ // the basic thing to do here would be just visitor.accept(intent.getData()).
+ }
}
/**
@@ -1434,6 +1457,11 @@
public int getActionTag() {
return SET_ON_CLICK_RESPONSE_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+ }
}
/**
@@ -1504,6 +1532,11 @@
public int getActionTag() {
return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+ }
}
/** @hide **/
@@ -2063,6 +2096,7 @@
final Icon icon = (Icon) getParameterValue(null);
if (icon != null) visitIconUri(icon, visitor);
break;
+ // TODO(b/281044385): Should we do anything about type BUNDLE?
}
}
}
@@ -2812,7 +2846,7 @@
}
@Override
- public final void visitUris(@NonNull Consumer<Uri> visitor) {
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
mNestedViews.visitUris(visitor);
}
}
@@ -7262,6 +7296,15 @@
Math.max(mViewTypeCount, 1));
}
}
+
+ /**
+ * See {@link RemoteViews#visitUris(Consumer)}.
+ */
+ private void visitUris(@NonNull Consumer<Uri> visitor) {
+ for (RemoteViews view : mViews) {
+ view.visitUris(visitor);
+ }
+ }
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 17c82b6..6da6a64 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,6 +28,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.R;
@@ -865,6 +866,7 @@
private final boolean mUseTextPaddingForUiTranslation;
private boolean mUseBoundsForWidth;
+ @Nullable private Paint.FontMetrics mMinimumFontMetrics;
@ViewDebug.ExportedProperty(category = "text")
@UnsupportedAppUsage
@@ -4901,6 +4903,58 @@
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the font
+ * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin script.
+ * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese,
+ * the line spacing for Japanese is reserved if the TextView contains English text. If the
+ * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font
+ * metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value
+ * obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see #getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ }
+
+ /**
+ * Get the minimum font metrics used for line spacing.
+ *
+ * @see #setMinimumFontMetrics(Paint.FontMetrics)
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ *
+ * @return a minimum font metrics. {@code null} for using the value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ */
+ @Nullable
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Paint.FontMetrics getMinimumFontMetrics() {
+ return mMinimumFontMetrics;
+ }
+
+ /**
* @return whether fallback line spacing is enabled, {@code true} by default
*
* @see #setFallbackLineSpacing(boolean)
@@ -9854,7 +9908,10 @@
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);
@@ -10680,7 +10737,8 @@
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mHintBoring);
+ isFallbackLineSpacingForBoringLayout(),
+ mMinimumFontMetrics, mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -10729,7 +10787,8 @@
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10793,12 +10852,13 @@
mLineBreakStyle, mLineBreakWordStyle))
.setUseBoundsForWidth(mUseBoundsForWidth)
.setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
- .setEllipsizedWidth(ellipsisWidth);
+ .setEllipsizedWidth(ellipsisWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
result = builder.build();
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -10812,7 +10872,7 @@
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, null, wantWidth,
isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth);
+ mUseBoundsForWidth, mMinimumFontMetrics);
} else {
result = new BoringLayout(
mTransformed,
@@ -10826,7 +10886,8 @@
wantWidth,
null,
boring,
- mUseBoundsForWidth);
+ mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
if (useSaved) {
@@ -10838,7 +10899,7 @@
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth);
+ mUseBoundsForWidth, mMinimumFontMetrics);
} else {
result = new BoringLayout(
mTransformed,
@@ -10852,7 +10913,8 @@
ellipsisWidth,
effectiveEllipsize,
boring,
- mUseBoundsForWidth);
+ mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
}
}
@@ -10871,7 +10933,8 @@
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10999,7 +11062,7 @@
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -11039,7 +11102,8 @@
if (hintDes < 0) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mHintBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics,
+ mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -11251,7 +11315,8 @@
.setTextDirection(getTextDirectionHeuristic())
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
final StaticLayout layout = layoutBuilder.build();
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7cfd35b..4705dc5 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -8,6 +8,14 @@
}
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"
@@ -15,7 +23,7 @@
}
flag {
- name: "dimmer_refactor"
+ name: "introduce_smoother_dimmer"
namespace: "windowing_frontend"
description: "Refactor dim to fix flickers"
bug: "295291019"
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 4e7bfe5..71bbccb 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -259,7 +259,7 @@
}
return true;
}
- return false;
+ return super.onKeyDown(keyCode,event);
}
@Override
@@ -268,7 +268,7 @@
stopWarp();
return true;
}
- return false;
+ return super.onKeyUp(keyCode,event);
}
private void startWarp() {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index b1d22e0..77e1502 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 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/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index f77e962..65a2f4b 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -46,4 +46,5 @@
in @nullable ImeTracker.Token statsToken);
void onStylusHandwritingReady(int requestId, int pid);
void resetStylusHandwriting(int requestId);
+ void switchKeyboardLayoutAsync(int direction);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 30ebbe2..792388d 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -431,4 +431,20 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Calls {@link IInputMethodPrivilegedOperations#switchKeyboardLayoutAsync(int)}.
+ */
+ @AnyThread
+ public void switchKeyboardLayoutAsync(int direction) {
+ final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+ if (ops == null) {
+ return;
+ }
+ try {
+ ops.switchKeyboardLayoutAsync(direction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e836e0..e6b036c 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -40,6 +40,7 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
@@ -57,6 +58,9 @@
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
@@ -273,7 +277,13 @@
public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
- private static final int LAST_CUJ = CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+ public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+ public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
+ public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
+ public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
+
+ private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
private static final int NO_STATSD_LOGGING = -1;
// Used to convert CujType to InteractionType enum value for statsd logging.
@@ -366,6 +376,13 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
}
private static class InstanceHolder {
@@ -468,6 +485,10 @@
CUJ_IME_INSETS_SHOW_ANIMATION,
CUJ_IME_INSETS_HIDE_ANIMATION,
CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+ CUJ_LAUNCHER_UNFOLD_ANIM,
+ CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+ CUJ_PREDICTIVE_BACK_CROSS_TASK,
+ CUJ_PREDICTIVE_BACK_HOME,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -1101,6 +1122,14 @@
return "IME_INSETS_HIDE_ANIMATION";
case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+ case CUJ_LAUNCHER_UNFOLD_ANIM:
+ return "LAUNCHER_UNFOLD_ANIM";
+ case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
+ return "PREDICTIVE_BACK_CROSS_ACTIVITY";
+ case CUJ_PREDICTIVE_BACK_CROSS_TASK:
+ return "PREDICTIVE_BACK_CROSS_TASK";
+ case CUJ_PREDICTIVE_BACK_HOME:
+ return "PREDICTIVE_BACK_HOME";
}
return "UNKNOWN";
}
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 34ef7b3..5b95ee7 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -85,6 +85,18 @@
return false;
}
+static jlong android_hardware_OverlayProperties_createDefault(JNIEnv* env, jobject thiz) {
+ gui::OverlayProperties* overlayProperties = new gui::OverlayProperties;
+ gui::OverlayProperties::SupportedBufferCombinations combination;
+ combination.pixelFormats = {HAL_PIXEL_FORMAT_RGBA_8888};
+ combination.standards = {HAL_DATASPACE_BT709};
+ combination.transfers = {HAL_DATASPACE_TRANSFER_SRGB};
+ combination.ranges = {HAL_DATASPACE_RANGE_FULL};
+ overlayProperties->combinations.emplace_back(combination);
+ overlayProperties->supportMixedColorSpaces = true;
+ return reinterpret_cast<jlong>(overlayProperties);
+}
+
// ----------------------------------------------------------------------------
// Serialization
// ----------------------------------------------------------------------------
@@ -150,6 +162,7 @@
(void*) android_hardware_OverlayProperties_write },
{ "nReadOverlayPropertiesFromParcel", "(Landroid/os/Parcel;)J",
(void*) android_hardware_OverlayProperties_read },
+ {"nCreateDefault", "()J", (void*) android_hardware_OverlayProperties_createDefault },
};
// clang-format on
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index f97d41b..262f5e8 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -42,13 +42,6 @@
return NULL;
}
- // b/274058082: Pass a copy of the key character map to avoid concurrent
- // access
- std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap();
- if (map != nullptr) {
- map = std::make_shared<KeyCharacterMap>(*map);
- }
-
ScopedLocalRef<jstring> descriptorObj(env,
env->NewStringUTF(deviceInfo.getIdentifier().descriptor.c_str()));
if (!descriptorObj.get()) {
@@ -67,9 +60,14 @@
? layoutInfo->layoutType.c_str()
: NULL));
+ std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap();
+ std::unique_ptr<KeyCharacterMap> mapCopy;
+ if (map != nullptr) {
+ mapCopy = std::make_unique<KeyCharacterMap>(*map);
+ }
ScopedLocalRef<jobject> kcmObj(env,
android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
- map));
+ std::move(mapCopy)));
if (!kcmObj.get()) {
return NULL;
}
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 7f69e22..a79e37a 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -48,7 +48,7 @@
class NativeKeyCharacterMap {
public:
- NativeKeyCharacterMap(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map)
+ NativeKeyCharacterMap(int32_t deviceId, std::unique_ptr<KeyCharacterMap> map)
: mDeviceId(deviceId), mMap(std::move(map)) {}
~NativeKeyCharacterMap() {
@@ -58,16 +58,18 @@
return mDeviceId;
}
- inline const std::shared_ptr<KeyCharacterMap> getMap() const { return mMap; }
+ inline const std::unique_ptr<KeyCharacterMap>& getMap() const {
+ return mMap;
+ }
private:
int32_t mDeviceId;
- std::shared_ptr<KeyCharacterMap> mMap;
+ std::unique_ptr<KeyCharacterMap> mMap;
};
jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
- const std::shared_ptr<KeyCharacterMap> kcm) {
- NativeKeyCharacterMap* nativeMap = new NativeKeyCharacterMap(deviceId, kcm);
+ std::unique_ptr<KeyCharacterMap> kcm) {
+ NativeKeyCharacterMap* nativeMap = new NativeKeyCharacterMap(deviceId, std::move(kcm));
if (!nativeMap) {
return nullptr;
}
@@ -91,7 +93,7 @@
return 0;
}
- std::shared_ptr<KeyCharacterMap> kcm = nullptr;
+ std::unique_ptr<KeyCharacterMap> kcm;
// Check if map is a null character map
if (parcel->readBool()) {
kcm = KeyCharacterMap::readFromParcel(parcel);
@@ -99,7 +101,7 @@
return 0;
}
}
- NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, kcm);
+ NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, std::move(kcm));
return reinterpret_cast<jlong>(map);
}
@@ -230,9 +232,9 @@
}
static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2) {
- const std::shared_ptr<KeyCharacterMap>& map1 =
+ const std::unique_ptr<KeyCharacterMap>& map1 =
(reinterpret_cast<NativeKeyCharacterMap*>(ptr1))->getMap();
- const std::shared_ptr<KeyCharacterMap>& map2 =
+ const std::unique_ptr<KeyCharacterMap>& map2 =
(reinterpret_cast<NativeKeyCharacterMap*>(ptr2))->getMap();
if (map1 == nullptr || map2 == nullptr) {
return map1 == map2;
diff --git a/core/jni/android_view_KeyCharacterMap.h b/core/jni/android_view_KeyCharacterMap.h
index be03353..a8aabd1 100644
--- a/core/jni/android_view_KeyCharacterMap.h
+++ b/core/jni/android_view_KeyCharacterMap.h
@@ -25,7 +25,7 @@
/* Creates a KeyCharacterMap object from the given information. */
extern jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
- const std::shared_ptr<KeyCharacterMap> kcm);
+ std::unique_ptr<KeyCharacterMap> kcm);
} // namespace android
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 178c0d0..9833598 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -126,7 +126,7 @@
jfieldID height;
jfieldID xDpi;
jfieldID yDpi;
- jfieldID refreshRate;
+ jfieldID peakRefreshRate;
jfieldID vsyncRate;
jfieldID appVsyncOffsetNanos;
jfieldID presentationDeadlineNanos;
@@ -1232,7 +1232,7 @@
env->SetFloatField(object, gDisplayModeClassInfo.xDpi, config.xDpi);
env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi);
- env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate);
+ env->SetFloatField(object, gDisplayModeClassInfo.peakRefreshRate, config.peakRefreshRate);
env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate);
env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset);
env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
@@ -2396,7 +2396,7 @@
gDisplayModeClassInfo.height = GetFieldIDOrDie(env, modeClazz, "height", "I");
gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F");
gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F");
- gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F");
+ gDisplayModeClassInfo.peakRefreshRate = GetFieldIDOrDie(env, modeClazz, "peakRefreshRate", "F");
gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F");
gDisplayModeClassInfo.appVsyncOffsetNanos =
GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J");
diff --git a/core/proto/android/nfc/Android.bp b/core/proto/android/nfc/Android.bp
deleted file mode 100644
index 6a62c91..0000000
--- a/core/proto/android/nfc/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
- name: "srcs_nfc_proto",
- srcs: [
- "*.proto",
- ],
-}
-
-// Will be statically linked by `framework-nfc`.
-java_library {
- name: "nfc-proto-java-gen",
- installable: false,
- proto: {
- type: "stream",
- include_dirs: [
- "external/protobuf/src",
- ],
- },
- srcs: [
- ":srcs_nfc_proto",
- ],
- sdk_version: "current",
- min_sdk_version: "current",
-}
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index cd951cb..5037239 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programme wat batterykrag gebruik"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toeganklikheidgebruik"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skerm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans batterykrag"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> programme gebruik tans batterykrag"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tik vir besonderhede oor battery- en datagebruik"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Laat ’n metgeselapp toe om voorgronddienste van agtergrond af te begin"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoon is beskikbaar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoon is geblokkeer"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan nie na skerm weerspieël nie"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik ’n ander kabel en probeer weer"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel steun dalk nie skerms nie"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Jou USB-C-kabel koppel dalk nie behoorlik aan skerms nie"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is aan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans albei skerms om inhoud te wys"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 42ba598..9a8bb62 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ባትሪ በመፍጀት ላይ ያሉ መተግበሪያዎች"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ማጉላት"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"የተደራሽነት አጠቃቀም"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ማሳያ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ባትሪ እየተጠቀመ ነው"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> መተግበሪያዎች ባትሪ እየተጠቀሙ ነው"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"በባትሪ እና ውሂብ አጠቃቀም ላይ ዝርዝሮችን ለማግኘት መታ ያድርጉ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"አጃቢ መተግበሪያ ከዳራ የፊት አገልግሎቶችን እንዲጀምር ያስችላል።"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ማይክሮፎን ይገኛል"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ማይክሮፎን ታግዷል"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ወደ ማሳያ ማንጸባረቅ አልተቻለም"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"የተለየ ገመድ ይጠቀሙ እና እንደገና ይሞክሩ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ገመድ ማሳያዎችን ላይደግፍ ይችላል"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"የእርስዎ USB-C ገመድ ከማሳያዎች ጋር በትክክል ላይገናኝ ይችላል"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ገፅ በርቷል"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ይዘትን ለማሳየት ሁለቱንም ማሳያዎች እየተጠቀመ ነው"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 68d7ff4..f6d7c64 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -294,6 +294,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"التطبيقات التي تستهلك البطارية"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"التكبير"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"استخدام \"أدوات تسهيل الاستخدام\""</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"الشاشة"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"يستخدم تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> البطارية"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"تستخدم <xliff:g id="NUMBER">%1$d</xliff:g> من التطبيقات البطارية"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"انقر للحصول على تفاصيل حول البطارية واستخدام البيانات"</string>
@@ -2337,6 +2338,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"يسمح هذا الإذن للتطبيق المصاحب ببدء الخدمات التي تعمل في المقدّمة من الخلفية."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"الميكروفون متاح."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"تم حظر الميكروفون."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"يتعذّر إجراء نسخ مطابق لمحتوى جهازك إلى الشاشة"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"يُرجى استخدام كابل آخر وإعادة المحاولة."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"قد لا يتوافق الكابل مع الشاشات"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"قد لا يتم توصيل الكابل المزوَّد بمنفذ USB-C بالشاشات بشكل صحيح."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ميزة Dual Screen مفعّلة"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"يستخدم \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" كلتا الشاشتين لعرض المحتوى."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index ba3e756..df4f28a 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"বেটাৰী খৰচ কৰা এপ্সমূহ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"বিবৰ্ধন"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"সাধ্য সুবিধাৰ ব্যৱহাৰ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ডিছপ্লে’"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ বেটাৰী ব্যৱহাৰ কৰি আছে"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>টা এপে বেটাৰী ব্যৱহাৰ কৰি আছে"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"বেটাৰী আৰু ডেটাৰ ব্যৱহাৰৰ বিষয়ে সবিশেষ জানিবলৈ টিপক"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"এটা সহযোগী এপক নেপথ্যৰ পৰা অগ্ৰভূমি সেৱাসমূহ আৰম্ভ কৰিবলৈ দিয়ে।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"মাইক্ৰ’ফ’নটো উপলব্ধ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্ৰ’ফ’নটো অৱৰোধ কৰি থোৱা আছে"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"সংযুক্ত ডিছপ্লে’ উপলব্ধ নহয়"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য এডাল কে’বল ব্যৱহাৰ কৰি পুনৰ চেষ্টা কৰক"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কে’বলে ডিছপ্লে’ সমৰ্থন নকৰিবও পাৰে"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপোনাৰ USB-C কে’বল ডিছপ্লে’ৰ সৈতে সঠিকভাৱে সংযোগ নহ’বও পাৰে"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen অন আছে"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ সমল দেখুৱাবলৈ দুয়োখন ডিছপ্লে’ ব্যৱহাৰ কৰি আছে"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 4124dfa..8bfd8b5 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Batareyadan istifadə edən tətbiqlər"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Böyütmə"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Əlçatımlılıq istifadəsi"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displey"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> batareyadan istifadə edir"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> tətbiq batareyadan istifadə edir"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Batareya və data istifadəsi haqqında ətraflı məlumat üçün klikləyin"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Kompanyon tətbiqinə ön fon xidmətlərini arxa fondan başlatmaq icazəsi verir."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon əlçatandır"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon blok edilib"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeydə əks etdirmək olmur"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Başqa kabel istifadə edin və yenidən cəhd edin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeyləri dəstəkləməyə bilər"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabeli displeylərə düzgün qoşulmaya bilər"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"İkili ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"İkili ekran aktivdir"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> məzmunu göstərmək üçün hər iki displeydən istifadə edir"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 3fd8dcb..48b5c02 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korišćenje Pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikacije (<xliff:g id="NUMBER">%1$d</xliff:g>) koriste bateriju"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o bateriji i potrošnji podataka"</string>
@@ -314,7 +315,7 @@
<string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Muzika i zvuk"</string>
<string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"pristup muzici i audio sadržaju na uređaju"</string>
<string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Slike i video snimci"</string>
- <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i video snimcima na uređaju"</string>
+ <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i videima na uređaju"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"snima zvuk"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Fizičke aktivnosti"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da pokrene usluge u prvom planu iz pozadine."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Preslikavanje na ekran nije moguće"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrebite drugi kabl i probajte ponovo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl ne podržava ekrane"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se ne povezuje pravilno sa ekranima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je uključen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi oba ekrana za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 715aad3..12effa0 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Праграмы, якія выкарыстоўваюць акумулятар"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Павелічэнне"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Выкарыстанне спецыяльных магчымасцей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дысплэй"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> выкарыстоўвае акумулятар"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Наступная колькасць праграм выкарыстоўваюць акумулятар: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Дакраніцеся, каб даведацца пра выкарыстанне трафіка і акумулятара"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Спадарожная праграма зможа запускаць актыўныя сэрвісы з фонавага рэжыму."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Мікрафон даступны"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрафон заблакіраваны"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не ўдалося прадубліраваць змесціва на дысплэі"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Паспрабуйце скарыстаць іншы кабель"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Магчыма, кабель несумяшчальны з дысплэямі"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Магчыма, кабель USB-C не падыходзіць да дысплэяў"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Уключана функцыя Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" выкарыстоўвае абодва экраны для паказу змесціва"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 9dd971c..d18e4bc 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Приложения, използващи батерията"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увеличение"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Използване на услугите за достъпност"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва батерията"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> приложения използват батерията"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Докоснете за информация относно използването на батерията и преноса на данни"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Разрешава на дадено придружаващо приложение да стартира услуги на преден план, докато се изпълнява на заден план."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофонът е налице"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонът е блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се копира огледално на дисплея"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Използвайте друг кабел и опитайте отново"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелът не поддържа дисплеи"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелът ви може да не се свързва правилно с дисплеи"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Функцията Dual Screen е включена"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва и двата екрана, за да показва съдържание"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 6f879e4..859f37d 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"কিছু অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"বড় করে দেখা"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"অ্যাক্সেসিবিলিটি সংক্রান্ত ব্যবহার"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ডিসপ্লে"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপটি ব্যাটারি ব্যবহার করছে"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>টি অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ব্যাটারি এবং ডেটার ব্যবহারের বিশদ বিবরণের জন্য ট্যাপ করুন"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"কম্প্যানিয়ন অ্যাপকে, ব্যাকগ্রাউন্ড থেকে ফোরগ্রাউন্ড পরিষেবা চালু করার অনুমতি দেয়।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"মাইক্রোফোন উপলভ্য আছে"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্রোফোন ব্লক করা হয়েছে"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ডিসপ্লে মিরর করা যাচ্ছে না"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য কোনও কেবল ব্যবহার করে আবার চেষ্টা করুন"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কেবল, ডিসপ্লের সাথে কাজ নাও করতে পারে"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপনার USB-C কেবল, ডিসপ্লেতে সঠিকভাবে কানেক্ট নাও হতে পারে"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen চালু করা আছে"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"কন্টেন্ট দেখানোর জন্য <xliff:g id="APP_NAME">%1$s</xliff:g> দুটি ডিসপ্লে ব্যবহার করছে"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b0f1905..8e9fe9c 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećavanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korištenje pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> troši bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Broj aplikacija koje troše bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o potrošnji baterije i prijenosa podataka"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da iz pozadine pokrene usluge u prvom planu."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nije moguće preslikati na ekran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabl i pokušajte ponovo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl možda neće podržavati ekrane"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se možda neće pravilno povezati s ekranima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je uključen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> koristi oba ekrana za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index be95847..dacdbf1 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicacions que consumeixen bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliació"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Ús de les funcions d\'accessibilitat"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> està consumint bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacions estan consumint bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca per obtenir informació sobre l\'ús de dades i de bateria"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet que una aplicació complementària iniciï serveis en primer pla des d\'un segon pla."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micròfon està disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micròfon està bloquejat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot projectar a la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilitza un altre cable i torna-ho a provar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"És possible que el cable no sigui compatible amb pantalles"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"És possible que el teu cable USB-C no es connecti correctament a les pantalles"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La pantalla dual està activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> està utilitzant les dues pantalles per mostrar contingut"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 32cff76..29e00fa 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikace spotřebovávají baterii"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zvětšení"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využití přístupnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displej"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> využívá baterii"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikace (<xliff:g id="NUMBER">%1$d</xliff:g>) využívají baterii"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Klepnutím zobrazíte podrobnosti o využití baterie a dat"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje doprovodné aplikaci spouštět z pozadí služby v popředí."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupný"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je zablokován"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nelze zrcadlit na displej"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použijte jiný kabel a zkuste to znovu"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možná nepodporuje displeje"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Váš kabel USB-C se možná nedokáže správně připojit k displejům"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Je zapnutá funkce Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> používá k zobrazení obsahu oba displeje"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 75bf365..87703cd 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps, der bruger batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Forstørrelse"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Brug af hjælpefunktioner"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skærm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps bruger batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tryk for at se info om batteri- og dataforbrug"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillader, at en medfølgende app kan starte tjenester i forgrunden via tilladelser til tjenester i baggrunden."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen er tilgængelig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokeret"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det er ikke muligt at spejle til skærmen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Brug et andet kabel, og prøv igen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablet understøtter muligvis ikke skærme"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dit USB-C-kabel kan muligvis ikke sluttes korrekt til skærmene"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen er aktiveret"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger begge skærme til at vise indhold"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index e087fe2..3faf328 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Strom verbrauchende Apps"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergrößerung"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Nutzung der Bedienungshilfen"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> verbraucht Strom"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> Apps verbrauchen Strom"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Für Details zur Akku- und Datennutzung tippen"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ermöglicht einer Companion-App, Dienste im Vordergrund aus dem Hintergrund zu starten."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon ist verfügbar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon ist blockiert"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kann nicht auf das Display gespiegelt werden"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Verwende ein anderes Kabel und versuch es noch einmal"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel unterstützt eventuell keine Bildschirme"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dein USB-C-Kabel ist möglicherweise nicht zum Verbinden von Bildschirmen geeignet"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ist aktiviert"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> nutzt zum Anzeigen von Inhalten beide Displays"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 9b713c99..af53ddf 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Εφαρμογές που καταναλώνουν μπαταρία"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Μεγιστοποίηση"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Χρήση προσβασιμότητας"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Οθόνη"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί μπαταρία"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> εφαρμογές χρησιμοποιούν μπαταρία"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Πατήστε για λεπτομέρειες σχετικά με τη χρήση μπαταρίας και δεδομένων"</string>
@@ -1376,7 +1377,7 @@
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Εντοπίστηκε αναλογικό αξεσουάρ ήχου"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Η συνδεδεμένη συσκευή δεν είναι συμβατή με αυτό το τηλέφωνο. Πατήστε για να μάθετε περισσότερα."</string>
<string name="adb_active_notification_title" msgid="408390247354560331">"Συνδέθηκε ο εντοπ. σφαλμ. USB"</string>
- <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργ. εντοπ./διόρθ. σφαλμ. USB"</string>
+ <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργ. εντοπ. σφαλμ. USB"</string>
<string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Επιλογή για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string>
<string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Συνδέθηκε ο ασύρματος εντοπισμός σφαλμάτων"</string>
<string name="adbwifi_active_notification_message" msgid="930987922852867972">"Πατήστε, για να απενεργοποιήσετε τον ασύρματο εντοπισμό σφαλμάτων"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Επιτρέπει σε μια συνοδευτική εφαρμογή να εκκινεί υπηρεσίες στο προσκήνιο από το παρασκήνιο."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Το μικρόφωνο είναι διαθέσιμο"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Το μικρόφωνο έχει αποκλειστεί"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Δεν είναι δυνατός ο κατοπτρισμός στην οθόνη"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Χρησιμοποιήστε άλλο καλώδιο και δοκιμάστε ξανά"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Το καλώδιο μπορεί να μην υποστηρίζει οθόνες"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Το καλώδιο USB-C που έχετε ίσως να μην μπορεί να συνδεθεί σωστά σε οθόνες"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Διπλή οθόνη"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Η λειτουργία διπλής οθόνης είναι ενεργή"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Η εφαρμ. <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί και τις 2 οθόνες για εμφάνιση περιεχ."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index bc231f5..8917a82 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 29f4c78..5a0ed85 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 5576054..bcf0790 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index ea95a513..7ebffc6 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index c09e6ce..b739768 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 723f833..ca9ff13 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que consumen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidad"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> está consumiendo batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps están consumiendo batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Presiona para obtener información sobre el uso de datos y de la batería"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micrófono está disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede duplicar la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un cable diferente y vuelve a intentarlo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Es posible que el cable no admita pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Es posible que el cable USB-C no se conecte a las pantallas de manera adecuada"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 79c6e26..97b1888 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicaciones que consumen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidad"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando la batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicaciones están usando la batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para ver información detallada sobre el uso de datos y de la batería"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micrófono está disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede proyectar a la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa otro cable y vuelve a intentarlo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"El cable puede no ser compatible con pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Puede que tu cable USB‑C no sea adecuado para conectarse a pantallas"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 67c20f7..4a8666e 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Rakendused kasutavad akutoidet"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Suurendus"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Juurdepääsetavuse kasutus"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekraan"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab akutoidet"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> rakendust kasutab akutoidet"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Aku ja andmekasutuse üksikasjade nägemiseks puudutage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lubab kaasrakendusel taustal käivitada esiplaanil olevaid teenuseid."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon on saadaval"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon on blokeeritud"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ei saa ekraanile peegeldada"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Kasutage teist kaablit ja proovige uuesti"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kaabel ei pruugi ekraane toetada"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Teie USB-C-kaabel ei pruugi ekraanidega õigesti ühendust luua"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screeni režiim on sisse lülitatud"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab sisu kuvamiseks mõlemat ekraani"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2150324..a163ed8 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Bateria kontsumitzen ari diren aplikazioak"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Lupa"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Erabilerraztasun-hobespenak"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantaila"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ari da bateria erabiltzen"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikazio ari dira bateria erabiltzen"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Sakatu bateria eta datu-erabilerari buruzko xehetasunak ikusteko"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Aurreko planoko zerbitzuak atzeko planotik abiarazteko baimena ematen die aplikazio osagarriei."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Erabilgarri dago mikrofonoa"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Blokeatuta dago mikrofonoa"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ezin da islatu pantailan"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Erabili beste kable bat eta saiatu berriro"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Baliteke kablea pantailekin bateragarria ez izatea"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Baliteke USB-C kablea behar bezala ez konektatzea pantailetara"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen aktibatuta dago"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bi pantailak erabiltzen ari da edukia erakusteko"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index de1ba1a..c5a2ee6 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"برنامههای مصرفکننده باتری"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"درشتنمایی"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"کاربرد دسترسپذیری"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"نمایشگر"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال استفاده کردن از باتری است"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> برنامه درحال استفاده کردن از باتری هستند"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"برای جزئیات مربوط به مصرف باتری و داده، ضربه بزنید"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"به برنامه همراه اجازه میدهد سرویسهای پیشنما را از پسزمینه راهاندازی کند."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"میکروفون دردسترس است"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"میکروفون مسدود شد"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"بازتاب دادن به نمایشگر ممکن نبود"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"از کابل دیگری استفاده کنید و دوباره امتحان کنید"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"شاید کابل از نمایشگر پشتیبانی نکند"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"کابل USB-C شما ممکن است بهدرستی به نمایشگرها وصل نشود"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen روشن است"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> از هر دو نمایشگر برای نمایش محتوا استفاده میکند"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 22d48e2..4a08b90 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Akkua kuluttavat sovellukset"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Suurennus"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Esteetön käyttö"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Näyttö"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää akkua."</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> sovellusta käyttää akkua."</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Katso lisätietoja akun ja datan käytöstä napauttamalla."</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Sallii kumppanisovelluksen aloittaa etualan palveluja taustalla."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoni on käytettävissä"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni on estetty"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Näytön peilaaminen ei onnistu"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Käytä eri johtoa ja yritä uudelleen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Johto ei ehkä tue näyttöjä"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-johtosi ei ehkä yhdisty näyttöihin kunnolla"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Kaksoisnäyttö"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Kaksoisnäyttö on päällä"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää molempia näyttöjä sisällön näyttämiseen"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 928bf77..1b637db 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Applications qui sollicitent la pile"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Agrandissement"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Usage des fonctionnalités d\'accessibilité"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Écran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sollicite la pile"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> applications sollicitent la pile"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Touchez pour afficher des détails sur l\'utilisation de la pile et des données"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet à une application compagnon en arrière-plan de lancer des services d\'avant-plan."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Le microphone est accessible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Le microphone est bloqué"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossible de dupliquer l\'écran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un câble différent et réessayez"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble peut ne pas être compatible avec les écrans"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C peut ne pas se connecter correctement aux écrans"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen activé"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher le contenu"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index d7d29a4..4f8de0c 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Applications utilisant la batterie"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Agrandissement"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilisation de l\'accessibilité"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Écran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise la batterie"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> applications utilisent la batterie"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Appuyer pour obtenir des informations sur l\'utilisation de la batterie et des données"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Autorise une application associée à lancer des services de premier plan à partir de l\'arrière-plan."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Le micro est disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Le micro est bloqué"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Duplication impossible"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un autre câble et réessayez"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble n\'est peut-être pas compatible avec les écrans"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C n\'est peut-être pas connecté correctement à l\'écran"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Double écran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Double écran activé"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher du contenu"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index e2073fb..b25cfcb 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicacións que consomen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacións están consumindo batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para obter información sobre o uso de datos e a batería"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que unha aplicación complementaria, desde un segundo plano, inicie servizos en primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O micrófono está dispoñible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Non se pode proxectar contido na pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Cambia de cable e téntao de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Pode que o cable non sexa compatible con pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O teu cable USB-C pode que non se conecte ás pantallas de maneira adecuada"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas as pantallas para mostrar contido"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 82b4a0d..fae5ebc 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ઍપ બૅટરીનો વપરાશ કરી રહ્યાં છે"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"મોટું કરવાની સુવિધા"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ઍક્સેસિબિલિટી વપરાશ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ડિસ્પ્લે"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> બૅટરીનો ઉપયોગ કરી રહ્યું છે"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ઍપ બૅટરીનો ઉપયોગ કરી રહ્યાં છે"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"બૅટરી અને ડેટા વપરાશ વિશેની વિગતો માટે ટૅપ કરો"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"સાથી ઍપને બૅકગ્રાઉન્ડમાંથી ફૉરગ્રાઉન્ડ સેવાઓ શરૂ કરવાની મંજૂરી આપે છે."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"માઇક્રોફોન ઉપલબ્ધ છે"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"માઇક્રોફોનને બ્લૉક કરવામાં આવ્યો છે"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ડિસ્પ્લે પર મિરર કરી શકાતું નથી"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"બીજા કોઈ કેબલનો ઉપયોગ કરો અને ફરી પ્રયાસ કરો"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"શક્ય છે કે કેબલ કદાચ ડિસ્પ્લેને સપોર્ટ ન આપતો હોય"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"તમારો USB-C કેબલ કદાચ ડિસ્પ્લે સાથે યોગ્ય રીતે કનેક્ટ ન થાય"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ચાલુ છે"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"કન્ટેન્ટ બતાવવા માટે <xliff:g id="APP_NAME">%1$s</xliff:g> બન્ને ડિસ્પ્લેનો ઉપયોગ કરી રહી છે"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 1f3a377..d4eb380 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"बैटरी की खपत करने वाले ऐप"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ज़ूम करने की सुविधा"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"सुलभता सुविधाओं का इस्तेमाल"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिसप्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> बैटरी का इस्तेमाल कर रहा है"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ऐप बैटरी का इस्तेमाल कर रहे हैं"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बैटरी और डेटा खर्च की जानकारी के लिए छूएं"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"इससे साथी ऐप्लिकेशन को बैकग्राउंड में फ़ोरग्राउंड सेवाएं चलाने की अनुमति मिलती है."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"माइक्रोफ़ोन इस्तेमाल किया जा सकता है"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफ़ोन को ब्लॉक किया गया है"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिसप्ले का कॉन्टेंट नहीं दिखाया जा सकता"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"कोई दूसरा केबल इस्तेमाल करके फिर से कोशिश करें"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ऐसा हो सकता है कि केबल, डिसप्ले के साथ ठीक से काम न करे"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ऐसा हो सकता है कि यूएसबी-सी केबल, डिसप्ले के साथ ठीक से कनेक्ट न हो पाए"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen की सुविधा चालू है"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>, कॉन्टेंट दिखाने के लिए दोनों स्क्रीन का इस्तेमाल कर रहा है"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 389a956..81ac641 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Povećavanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Upotreba pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Zaslon"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Broj aplikacija koje koriste bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite da biste vidjeli pojedinosti o potrošnji baterije i podatkovnom prometu"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Popratnoj aplikaciji omogućuje da iz pozadine pokrene usluge u prednjem planu."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Zrcaljenje na zaslon nije moguće"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabel i pokušajte ponovno"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možda ne podržava zaslone"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Vaš USB-C kabel možda nije ispravno povezan sa zaslonima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dvostruki zaslon"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Uključen je dvostruki zaslon"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> upotrebljava oba zaslona za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 4bce83b..9a389db 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Akkumulátort használó alkalmazások"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Nagyítás"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Kisegítő lehetőségek használata"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Kijelző"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás használja az akkumulátort"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> alkalmazás használja az akkumulátort"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Koppintson az akkumulátor- és adathasználat részleteinek megtekintéséhez"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lehetővé teszi a társalkalmazások számára, hogy előtérben futó szolgáltatásokat indítsanak a háttérből."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"A mikrofon rendelkezésre áll"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"A mikrofon le van tiltva"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nem lehet tükrözni a kijelzőre"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Használjon másik kábelt, és próbálja újra"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Előfordulhat, hogy a kábel nem támogatja a kijelzőket"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Előfordulhat, hogy az USB-C kábellel nem csatlakoztathatók megfelelően a kijelzők"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A Dual Screen funkció be van kapcsolva"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> mindkét kijelzőt használja a tartalmak megjelenítésére"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index e74ec54..ccea0cc 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Մարտկոցի լիցքը ծախսող հավելվածներ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Խոշորացում"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Հատուկ գործառույթների օգտագործում"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Էկրան"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"«<xliff:g id="APP_NAME">%1$s</xliff:g>» հավելվածը ծախսում է մարտկոցի լիցքը"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> հավելված ծախսում է մարտկոցի լիցքը"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Հպեք՝ մարտկոցի և թրաֆիկի մանրամասները տեսնելու համար"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Թույլատրում է ուղեկցող հավելվածին ակտիվ ծառայություններ գործարկել ֆոնային ռեժիմից։"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Խոսափողը հասանելի է"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Խոսափողն արգելափակված է"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Չհաջողվեց հայելապատճենել էկրանին"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Օգտագործեք այլ մալուխ և նորից փորձեք"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Մալուխը կարող է համատեղելի չլինել էկրանների հետ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Հնարավոր է՝ USB-C մալուխը սխալ է միացված էկրանին"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen-ը միացված է"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն օգտագործում է երկու էկրանները"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index dc0993e..47bbefd 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikasi yang menggunakan baterai"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pembesaran"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Penggunaan aksesibilitas"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Layar"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan baterai"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikasi sedang meggunakan baterai"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ketuk untuk melihat detail penggunaan baterai dan data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Mengizinkan aplikasi pendamping memulai layanan latar depan dari latar belakang."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon tersedia"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon diblokir"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat mencerminkan ke layar"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan coba lagi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak mendukung layar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C mungkin tidak terhubung dengan benar ke layar"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen aktif"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> menggunakan kedua layar untuk menampilkan konten"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 2af8969..f666308 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Forrit sem nota rafhlöðuorku"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Stækkun"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Aðgengisnotkun"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skjár"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> notar rafhlöðuorku"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> forrit nota rafhlöðuorku"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ýttu til að fá upplýsingar um rafhlöðu- og gagnanotkun"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leyfir fylgiforriti að ræsa forgrunnsþjónustur úr bakgrunni."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Hljóðnemi er í boði"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Lokað er fyrir hljóðnemann"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekki er hægt að spegla á skjá"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Notaðu aðra snúru og reyndu aftur"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ekki er víst að snúran styðji skjái"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ekki er víst að USB-C-snúran tengist skjám á réttan hátt"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tveir skjáir"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Kveikt er á tveimur skjám"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> er að nota báða skjái til að sýna efni"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 7ea502c..e6bc552 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"App che consumano la batteria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ingrandimento"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilizzo dell\'accessibilità"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> sta consumando la batteria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> app stanno consumando la batteria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocca per conoscere i dettagli sull\'utilizzo dei dati e della batteria"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Consente a un\'app complementare di avviare servizi in primo piano dal background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfono disponibile"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfono bloccato"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossibile eseguire il mirroring al display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un altro cavo e riprova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Il cavo potrebbe non supportare i display"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Il cavo USB-C potrebbe non collegarsi correttamente ai display"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Doppio schermo"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Doppio schermo attivo"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> sta usando entrambi i display per mostrare contenuti"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 22cbab2..53ec382 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"אפליקציות שמרוקנות את הסוללה"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"הגדלה"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"שימוש בנגישות"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"מסך"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בסוללה"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> אפליקציות משתמשות בסוללה"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"אפשר להקיש כדי לקבל פרטים על צריכה של נתונים וסוללה"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ההרשאה הזו מאפשרת לאפליקציה נלווית להפעיל מהרקע שירותים שפועלים בחזית."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"המיקרופון זמין"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"המיקרופון חסום"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"לא ניתן לשקף למסך"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"צריך להשתמש בכבל שונה ולנסות שוב"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"יכול להיות שהכבל לא תומך במסכים"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"יכול להיות שכבל ה-USB-C לא יתחבר למסכים כמו שצריך"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"מצב שני מסכים"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"מצב שני מסכים מופעל"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בשני המסכים כדי להציג תוכן"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 06b3445..961a8da87 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"電池を消費しているアプリ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"拡大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ユーザー補助の使用"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ディスプレイ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」がバッテリーを使用しています"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個のアプリが電池を使用しています"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"タップしてバッテリーやデータの使用量を確認"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"バックグラウンドからのフォアグラウンド サービスの起動をコンパニオン アプリに許可します。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"マイクを利用できます"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"マイクがブロックされています"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ディスプレイにミラーリングできません"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"別のケーブルでもう一度お試しください"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ケーブルはディスプレイに対応していない可能性があります"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C ケーブルがディスプレイに正しく接続されていない可能性があります"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"デュアル スクリーン"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"デュアル スクリーン: ON"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>は 2 画面でコンテンツを表示しています"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index c8f6621f4..27f596b 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ბატარეის მხარჯავი აპები"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"გადიდება"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"მარტივი წვდომის გამოყენება"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ეკრანი"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ბატარეას"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"ბატარეას <xliff:g id="NUMBER">%1$d</xliff:g> აპი იყენებს"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"შეეხეთ ბატარეისა და მონაცემების მოხმარების შესახებ დეტალური ინფორმაციისთვის"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"საშუალებას აძლევს კომპანიონ აპს, რომ გაუშვას უპირატესი სერვისები ფონური რეჟიმიდან."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"მიკროფონი ხელმისაწვდომია"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"მიკროფონი დაბლოკილია"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ეკრანზე არეკვლა შეუძლებელია"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"გამოიყენეთ სხვა კაბელი და ცადეთ ხელახლა"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"კაბელს შეიძლება არ ჰქონდეს ეკრანების მხარდაჭერა"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"თქვენი USB-C კაბელი შეიძლება სათანადოდ არ უკავშირდებოდეს ეკრანებს"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ორმაგი ეკრანი"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ორმაგი ეკრანი ჩართულია"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ორივე ეკრანს შინაარსის საჩვენებლად"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 1272c24..1faea61 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Батареяны пайдаланып жатқан қолданбалар"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ұлғайту"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Арнайы мүмкіндіктерді қолдану"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> батареяны пайдалануда"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> қолданба батареяны пайдалануда"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батарея мен деректер трафигі туралы білу үшін түртіңіз"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Қосымша қолданбаға экрандық режимдегі қызметтерді фоннан іске қосуға рұқсат беріледі."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон қолжетімді."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон блокталған."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дисплейге көшірмені көрсету мүмкін емес"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Басқа кабельмен әрекетті қайталап көріңіз."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерді қолдамауы мүмкін"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелі дисплейлерге дұрыс жалғанбаған болуы мүмкін."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen функциясы қосулы"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы контентті көрсету үшін екі дисплейді де пайдаланады."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 807761e..470c39e 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"កម្មវិធីដែលកំពុងប្រើថ្ម"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ការពង្រីក"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ការប្រើប្រាស់ភាពងាយស្រួល"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ផ្ទាំងអេក្រង់"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើថ្ម"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"កម្មវិធីចំនួន <xliff:g id="NUMBER">%1$d</xliff:g> កំពុងប្រើថ្ម"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ចុចដើម្បីមើលព័ត៌មានលម្អិតអំពីការប្រើប្រាស់ទិន្នន័យ និងថ្ម"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"អនុញ្ញាតឱ្យកម្មវិធីដៃគូចាប់ផ្តើមសេវាកម្មផ្ទៃខាងមុខពីផ្ទៃខាងក្រោយ។"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"អាចប្រើមីក្រូហ្វូនបាន"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"មីក្រូហ្វូនត្រូវបានទប់ស្កាត់"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"មិនអាចបញ្ចាំងទៅផ្ទាំងអេក្រង់បានទេ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ប្រើខ្សែផ្សេង រួចព្យាយាមម្តងទៀត"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ខ្សែប្រហែលជាមិនអាចប្រើជាមួយផ្ទាំងអេក្រង់បានទេ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ខ្សែ USB-C របស់អ្នកប្រហែលជាមិនអាចភ្ជាប់ផ្ទាំងអេក្រង់បានត្រឹមត្រូវទេ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"អេក្រង់ពីរ"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"អេក្រង់ពីរត្រូវបានបើក"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើផ្ទាំងអេក្រង់ទាំងពីរដើម្បីបង្ហាញខ្លឹមសារ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 742dfbb..e2f24f6 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ಅಪ್ಲಿಕೇಶನ್ಗಳು ಬ್ಯಾಟರಿಯನ್ನು ಉಪಯೋಗಿಸುತ್ತಿವೆ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ಹಿಗ್ಗಿಸುವಿಕೆ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ಪ್ರವೇಶಿಸುವಿಕೆಯ ಬಳಕೆ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ಡಿಸ್ಪ್ಲೇ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್, ಬ್ಯಾಟರಿಯನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ಅಪ್ಲಿಕೇಶನ್ಗಳು ಬ್ಯಾಟರಿ ಬಳಸುತ್ತಿವೆ"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ಬ್ಯಾಟರಿ,ಡೇಟಾ ಬಳಕೆಯ ವಿವರಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಹಿನ್ನೆಲೆಯಿಂದ ಪ್ರಾರಂಭಿಸಲು ಕಂಪ್ಯಾನಿಯನ್ ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ಮೈಕ್ರೊಫೋನ್ ಲಭ್ಯವಿದೆ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ಡಿಸ್ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ಬೇರೆ ಕೇಬಲ್ ಬಳಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ಡಿಸ್ಪ್ಲೇಗಳನ್ನು ಕೇಬಲ್ ಬೆಂಬಲಿಸದಿರಬಹುದು"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ನಿಮ್ಮ USB-C ಕೇಬಲ್ ಡಿಸ್ಪ್ಲೇಗಳಿಗೆ ಸರಿಯಾಗಿ ಕನೆಕ್ಟ್ ಆಗದಿರಬಹುದು"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ಆನ್ ಆಗಿದೆ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ಕಂಟೆಂಟ್ ಅನ್ನು ತೋರಿಸಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಎರಡೂ ಡಿಸ್ಪ್ಲೇಗಳನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index d2b6dc8..0608283 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"배터리를 소모하는 앱"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"확대"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"접근성 사용"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"디스플레이"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 배터리 사용 중"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"앱 <xliff:g id="NUMBER">%1$d</xliff:g>개에서 배터리 사용 중"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"탭하여 배터리 및 데이터 사용량 확인"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"호환 앱이 백그라운드에서 포그라운드 서비스를 시작할 수 있게 허용합니다."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"마이크 사용 가능"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"마이크가 차단됨"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"디스플레이에 미러링할 수 없음"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"다른 케이블을 사용하여 다시 시도해 보세요."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"디스플레이를 지원하지 않는 케이블일 수 있음"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"사용 중인 USB-C 케이블이 디스플레이에 제대로 연결되지 않을 수 있습니다."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen 켜짐"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 두 화면을 모두 사용하여 콘텐츠를 표시합니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 56c0696..853221e 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Колдонмолор батареяңызды коротууда"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Чоңойтуу"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Атайын мүмкүнчүлүктөрдүн колдонулушу"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Экран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу батареяны пайдаланып жатат"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> колдонмо батареяны пайдаланып жатат"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батареянын кубаты жана трафиктин көлөмү жөнүндө билүү үчүн таптап коюңуз"</string>
@@ -1384,7 +1385,7 @@
<string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Сыноо программасынын режими иштетилди"</string>
<string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Сыноо программасынын режимин өчүрүү үчүн баштапкы параметрлерге кайтарыңыз."</string>
<string name="console_running_notification_title" msgid="6087888939261635904">"Сериялык консоль иштетилди"</string>
- <string name="console_running_notification_message" msgid="7892751888125174039">"Майнаптуулугуна таасири тиет. Аны өчүрүү үчүн операциялык тутумду жүктөгүчтү текшериңиз."</string>
+ <string name="console_running_notification_message" msgid="7892751888125174039">"Майнаптуулугуна таасири тиет. Өчүрүү үчүн операциялык тутумду жүктөгүчтү текшериңиз."</string>
<string name="mte_override_notification_title" msgid="4731115381962792944">"Cынамык MTE иштетилди"</string>
<string name="mte_override_notification_message" msgid="2441170442725738942">"Иштин майнаптуулугуна жана туруктуулугуна кедергиси тийиши мүмкүн. Өчүрүү үчүн түзмөктү өчүрүп-күйгүзүңүз. Эгер arm64.memtag.bootctl аркылуу иштетилген болсо, алдын ала \"none\" маанисин орнотуңуз."</string>
<string name="usb_contaminant_detected_title" msgid="4359048603069159678">"USB портунда суюктук же урандылар бар"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Көмөкчү колдонмого активдүү кызматтарды фондо иштетүүгө уруксат берет."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон жеткиликтүү"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон бөгөттөлгөн"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Экранга күзгүдөй чагылдыруу мүмкүн эмес"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Башка кабелди колдонуп, кайра аракет кылыңыз"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерди колдоого албашы мүмкүн"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабели дисплейлерге туура туташпашы мүмкүн"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Кош экран"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen күйүк"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> контентти эки түзмөктө тең көрсөтүүдө"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 8892f60..c743fc0 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ແອັບທີ່ກຳລັງໃຊ້ແບັດເຕີຣີ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ການຂະຫຍາຍ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ການໃຊ້ການຊ່ວຍເຂົ້າເຖິງ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ຈໍສະແດງຜົນ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ແອັບກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ແຕະເພື່ອເບິ່ງລາຍລະອຽດການນຳໃຊ້ແບັດເຕີຣີ ແລະ ອິນເຕີເນັດ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ອະນຸຍາດຈາກເບື້ອງຫຼັງໃຫ້ແອັບຊ່ວຍເຫຼືອເລີ່ມໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າ."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ໄມໂຄຣໂຟນພ້ອມໃຫ້ນຳໃຊ້"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ໄມໂຄຣໂຟນຖືກບລັອກໄວ້"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ບໍ່ສາມາດສະທ້ອນໄປຫາຈໍສະແດງຜົນໄດ້"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ກະລຸນາໃຊ້ສາຍອື່ນແລ້ວລອງໃໝ່"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ສາຍອາດບໍ່ຮອງຮັບຈໍສະແດງຜົນ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ສາຍ USB-C ຂອງທ່ານອາດບໍ່ໄດ້ເຊື່ອມຕໍ່ກັບຈໍສະແດງຜົນຢ່າງຖືກຕ້ອງ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ໜ້າຈໍຄູ່"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ເປີດ Dual Screen ຢູ່"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ຈໍສະແດງຜົນທັງສອງເພື່ອສະແດງເນື້ອຫາ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 8b4ff97..ff665c5 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programos, naudojančios akumuliatoriaus energiją"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Didinimas"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Pritaikomumo funkcijų naudojimas"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekranas"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja akumuliatoriaus energiją"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Programų, naudojančių akumuliatoriaus energiją: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Palieskite ir sužinokite išsamios informacijos apie akumuliatoriaus bei duomenų naudojimą"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leidžiama papildomai programai paleisti priekinio plano paslaugas fone."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonas pasiekiamas"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonas užblokuotas"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Negalima bendrinti ekrano vaizdo ekrane"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Naudokite kitą laiką ir bandykite dar kartą"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Laidas gali nepalaikyti ekranų"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Gali būti, kad USB-C laidu nepavyksta tinkamai prisijungti prie ekranų"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Įjungta „Dual Screen“"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja abu ekranus turiniui rodyti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index c509378..4d369aa 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Lietotnes, kas patērē akumulatora jaudu"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Palielinājums"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Pieejamības lietojums"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displejs"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> izmanto akumulatoru"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> lietotne(-es) izmanto akumulatoru"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Pieskarieties, lai skatītu detalizētu informāciju par akumulatora un datu lietojumu"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ļauj palīglietotnei sākt priekšplāna pakalpojumus no fona."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofons ir pieejams."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofons ir bloķēts."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nevar spoguļot displeju"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Izmantojiet citu vadu un mēģiniet vēlreiz."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Iespējams, vads neatbalsta displejus"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Iespējams, jūsu USB-C vads nevarēs nodrošināt pareizu savienojumu ar displejiem."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen režīms"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Ieslēgts Dual Screen režīms"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> satura rādīšanai izmanto abus displejus."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 109e967..29c9de8 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликации што ја трошат батеријата"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Зголемување"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Користење на пристапноста"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Екран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерија"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> апликации користат батерија"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Допрете за детали за батеријата и потрошениот интернет"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволува придружна апликација да започне услуги во преден план од заднината."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофонот е достапен"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонот е блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се отсликува за прикажување"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Користете друг кабел и обидете се повторно"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелот можеби не поддржува екрани"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Кабелот USB-C можеби нема да се поврзе правилно со екраните"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Вклучен е Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ги користи двата екрани за да прикажува содржини"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 9ccf8b5..46587ee 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"മാഗ്നിഫിക്കേഷൻ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ഉപയോഗസഹായി ഉപയോഗം"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ഡിസ്പ്ലേ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ബാറ്ററി, ഡാറ്റ ഉപയോഗം എന്നിവയുടെ വിശദാംശങ്ങളറിയാൻ ടാപ്പുചെയ്യുക"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"പശ്ചാത്തലത്തിൽ നിന്ന് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ ആരംഭിക്കാൻ സഹകാരി ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"മൈക്രോഫോൺ ലഭ്യമാണ്"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"മൈക്രോഫോൺ ബ്ലോക്ക് ചെയ്തു"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ഡിസ്പ്ലേയിലേക്ക് മിറർ ചെയ്യാനാകില്ല"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"മറ്റൊരു കേബിൾ ഉപയോഗിച്ച് വീണ്ടും ശ്രമിക്കുക"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"കേബിൾ, ഡിസ്പ്ലേകളെ പിന്തുണച്ചേക്കില്ല"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"നിങ്ങളുടെ USB-C കേബിൾ, ഡിസ്പ്ലേകളിലേക്ക് ശരിയായി കണക്റ്റ് ആയേക്കില്ല"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ഡ്യുവൽ സ്ക്രീൻ"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ഡ്യുവൽ സ്ക്രീൻ ഓണാണ്"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ഉള്ളടക്കം കാണിക്കാൻ <xliff:g id="APP_NAME">%1$s</xliff:g> രണ്ട് ഡിസ്പ്ലേകളും ഉപയോഗിക്കുന്നു"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index a9ef358..62a162e 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апп батарей ашиглаж байна"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Томруулах"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Хандалтын ашиглалт"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дэлгэц"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> батарей ашиглаж байна"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> апп батарей ашиглаж байна"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батарей, дата ашиглалтын талаар дэлгэрэнгүйг харахын тулд товшино уу"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дэмжигч аппад нүүрэн талын үйлчилгээнүүдийг ардаас эхлүүлэхийг зөвшөөрнө."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофоныг ашиглах боломжгүй байна"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофоныг блоклосон байна"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дэлгэцэд тусгал үүсгэх боломжгүй"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Өөр кабель ашиглаад, дахин оролдоно уу"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель нь дэлгэцүүдийг дэмждэггүй байж магадгүй"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Таны USB-C кабель дэлгэцүүдэд зохих ёсоор холбогдохгүй байж магадгүй"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen асаалттай байна"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> контент харуулахын тулд хоёр дэлгэцийг хоёуланг нь ашиглаж байна"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 485e9b6..87227ee 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"बॅटरी लवकर संपवणारी अॅप्स"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"मॅग्निफिकेशन"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"अॅक्सेसिबिलिटी वापर"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिस्प्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> बॅटरी वापरत आहे"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> अॅप्स बॅटरी वापरत आहेत"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बॅटरी आणि डेटा वापराच्या तपशीलांसाठी टॅप करा"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"सहयोगी अॅपला बॅकग्राउंडमधून फोरग्राउंड सेवा सुरू करण्याची अनुमती देते."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"मायक्रोफोन उपलब्ध आहे"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"मायक्रोफोन ब्लॉक केलेला आहे"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेवर मिरर करू शकत नाही"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"वेगळी केबल वापरून पुन्हा प्रयत्न करा"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"केबल कदाचित डिस्प्लेना सपोर्ट करणार नाही"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तुमची USB-C केबल कदाचित डिस्प्लेना योग्यरीत्या कनेक्ट होणार नाही"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen सुरू आहे"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"आशय दाखवण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> दोन्ही डिस्प्ले वापरत आहे"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 3d3fc7c..7794150 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apl yang menggunakan bateri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pembesaran"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Penggunaan kebolehaksesan"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Paparan"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan bateri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apl sedang menggunakan bateri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ketik untuk mendapatkan butiran tentang penggunaan kuasa bateri dan data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Benarkan apl rakan memulakan perkhidmatan latar depan dari latar."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon tersedia"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon disekat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat menyegerakkan kepada paparan"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan cuba lagi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak menyokong paparan"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C anda mungkin tidak bersambung kepada paparan dengan betul"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dwiskrin"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dwiskrin dihidupkan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> menggunakan kedua-dua paparan untuk menunjukkan kandungan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 39dd043..3aaaf8b 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"အက်ပ်များက ဘက်ထရီကုန်စေသည်"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ချဲ့ခြင်း"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"အများသုံးစွဲနိုင်မှုကို အသုံးပြုမှု"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ဖန်သားပြင်"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> က ဘက်ထရီကို အသုံးပြုနေသည်"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"အက်ပ် <xliff:g id="NUMBER">%1$d</xliff:g> ခုက ဘက်ထရီကို အသုံးပြုနေသည်"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ဘက်ထရီနှင့် ဒေတာအသုံးပြုမှု အသေးစိတ်ကို ကြည့်ရန် တို့ပါ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"နောက်ခံမှနေ၍ မျက်နှာစာဝန်ဆောင်မှုများ စတင်ရန် တွဲဖက် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"မိုက်ခရိုဖုန်း သုံးနိုင်သည်"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"မိုက်ခရိုဖုန်း ပိတ်ထားသည်"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ဖန်သားပြင်တွင် စကရင်ပွား၍ မရပါ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"အခြားကေဘယ်ကြိုးသုံးပြီး ထပ်စမ်းကြည့်ပါ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ကေဘယ်ကြိုးက ဖန်သားပြင်များကို မပံ့ပိုးခြင်း ဖြစ်နိုင်သည်"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"သင့် USB-C ကေဘယ်ကြိုးသည် ဖန်သားပြင်များနှင့် မှန်ကန်စွာ ချိတ်ဆက်မထားခြင်း ဖြစ်နိုင်သည်"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ဖွင့်ထားသည်"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အကြောင်းအရာကို ပြရန် ဖန်သားပြင်နှစ်ခုစလုံးကို သုံးနေသည်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 5b1f77c..9d6e805 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apper bruker batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Forstørring"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Bruk av Tilgjengelighet"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skjerm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apper bruker batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Trykk for detaljer om batteri- og databruk"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lar en følgeapp starte forgrunnstjenester fra bakgrunnen."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen er tilgjengelig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokkert"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan ikke speile til skjermen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Bruk en annen kabel og prøv igjen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabelen støtter kanskje ikke skjermer"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-kabelen din kobler seg kanskje ikke til skjermer på riktig måte"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen er på"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker begge skjermene til å vise innhold"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 603fad34..3b7de10 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"एपहरूले ब्याट्री खपत गर्दै छन्"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"जुम इन गर्ने सुविधा"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"सर्वसुलभतासम्बन्धी सेवाहरूको प्रयोग"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिस्प्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले ब्याट्री प्रयोग गर्दै छ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> एपहरूले ब्याट्री प्रयोग गर्दै छन्"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ब्याट्री र डेटाका प्रयोग सम्बन्धी विवरणहरूका लागि ट्याप गर्नुहोस्"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"यसले सहयोगी एपलाई ब्याकग्राउन्डमा फोरग्राउन्ड सेवाहरू चलाउने अनुमति दिन्छ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"माइक्रोफोन अनम्युट गरिएको छ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफोन म्युट गरिएको छ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेमा मिरर गर्न सकिएन"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"अर्कै केबल प्रयोग गरी फेरि प्रयास गर्नुहोस्"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"यो केबल डिस्प्लेहरूमा प्रयोग गर्न नमिल्न सक्छ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तपाईंको USB-C केबल डिस्प्लेहरूमा राम्रोसँग नजोडिन सक्छ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen अन छ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले सामग्री देखाउन दुई वटै डिस्प्ले प्रयोग गरिरहेको छ"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 50e261f..ae7d366 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps die de batterij gebruiken"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toegankelijkheidsgebruik"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Scherm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt de batterij"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps gebruiken de batterij"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tik voor batterij- en datagebruik"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hiermee kan een bijbehorende app services op de voorgrond vanuit de achtergrond starten."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfoon is beschikbaar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfoon is geblokkeerd"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan niet spiegelen naar scherm"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik een andere kabel en probeer het opnieuw"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"De kabel ondersteunt misschien geen schermen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Je USB-C-kabel sluit misschien niet goed aan op schermen"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen staat aan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt beide schermen om content te tonen"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 52c9bf2..af76df8 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ଆପ୍ଗୁଡ଼ିକ ବ୍ୟାଟେରୀ ଖର୍ଚ୍ଚ କରିଥା\'ନ୍ତି"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ମେଗ୍ନିଫିକେସନ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ଆକ୍ସେସିବିଲିଟୀ ବ୍ୟବହାର"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ଡିସପ୍ଲେ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବ୍ୟାଟେରୀ ବ୍ୟବହାର କରୁଛି"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>ଟି ଆପ୍ ବ୍ୟାଟେରୀ ବ୍ୟବହାର କରୁଛନ୍ତି"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ବ୍ୟାଟେରୀ ଏବଂ ଡାଟା ବ୍ୟବହାର ଉପରେ ବିବରଣୀ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ପୃଷ୍ଠପଟରୁ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକ ଆରମ୍ଭ କରିବାକୁ ଏକ ସହଯୋଗୀ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ମାଇକ୍ରୋଫୋନ ଉପଲବ୍ଧ ଅଛି"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ମାଇକ୍ରୋଫୋନକୁ ବ୍ଲକ କରାଯାଇଛି"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ଡିସପ୍ଲେ କରିବାକୁ ମିରର କରାଯାଇପାରିବ ନାହିଁ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ଏକ ଭିନ୍ନ କେବୁଲ ବ୍ୟବହାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକୁ ସମର୍ଥନ କରିନପାରେ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ଆପଣଙ୍କ USB-C କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକ ସହ ସଠିକ ଭାବରେ କନେକ୍ଟ ହୋଇନପାରେ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ଚାଲୁ ଅଛି"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ବିଷୟବସ୍ତୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଉଭୟ ଡିସପ୍ଲେକୁ ବ୍ୟବହାର କରୁଛି"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 8034be8..243e3e5 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ਬੈਟਰੀ ਦੀ ਖਪਤ ਕਰਨ ਵਾਲੀਆਂ ਐਪਾਂ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ਵੱਡਦਰਸ਼ੀਕਰਨ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ਪਹੁੰਚਯੋਗਤਾ ਵਰਤੋਂ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ਡਿਸਪਲੇ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ਐਪਾਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀਆਂ ਹਨ"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ਬੈਟਰੀ ਅਤੇ ਡਾਟਾ ਵਰਤੋਂ ਸਬੰਧੀ ਵੇਰਵਿਆਂ ਲਈ ਟੈਪ ਕਰੋ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ਸੰਬੰਧੀ ਐਪ ਨੂੰ ਬੈਕਗ੍ਰਾਊਂਡ ਤੋਂ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਉਪਲਬਧ ਹੈ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ਕੋਈ ਵੱਖਰੀ ਕੇਬਲ ਵਰਤ ਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਦਾ ਸਮਰਥਨ ਨਾ ਕਰੇ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਤੁਹਾਡੀ USB-C ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਨਾਲ ਠੀਕ ਤਰ੍ਹਾਂ ਕਨੈਕਟ ਨਾ ਹੋਵੇ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ਚਾਲੂ ਹੈ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਸਮੱਗਰੀ ਨੂੰ ਦਿਖਾਉਣ ਲਈ ਦੋਵੇਂ ਡਿਸਪਲੇਆਂ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀ ਹੈ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index b66ec02..18bea3f 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacje zużywające baterię"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Powiększenie"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Użycie ułatwień dostępu"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Wyświetlacz"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> zużywa baterię"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Liczba aplikacji zużywających baterię: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Kliknij, by wyświetlić szczegóły wykorzystania baterii i użycia danych"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Zezwala aplikacji towarzyszącej na uruchamianie usług działających na pierwszym planie, podczas gdy sama działa w tle."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon jest dostępny"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon jest zablokowany"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nie można utworzyć odbicia lustrzanego na wyświetlaczu"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Użyj innego kabla i spróbuj ponownie"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel może nie obsługiwać wyświetlaczy"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C może nie łączyć się prawidłowo z wyświetlaczami"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Podwójny ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Włączono podwójny ekran"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> korzysta z obu wyświetlaczy, aby pokazać treści"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 628627d..486318d 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão consumindo a bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Tela"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocar para ver detalhes sobre a bateria e o uso de dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A tela dupla está ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está usando as duas telas para mostrar conteúdo"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index c83491e..784f2a9 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão a consumir bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilização da acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ecrã"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a consumir bateria."</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicações estão a consumir bateria."</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toque para obter detalhes acerca da utilização da bateria e dos dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que uma app associada em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar para o ecrã"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use um cabo diferente e tente novamente"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"O cabo pode não suportar ecrãs"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O cabo USB-C pode não se ligar a ecrãs corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Funcionalidade Dual Screen ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a usar ambos os ecrãs para mostrar conteúdo"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 628627d..486318d 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão consumindo a bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Tela"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocar para ver detalhes sobre a bateria e o uso de dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A tela dupla está ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está usando as duas telas para mostrar conteúdo"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 3389c63..88f5044 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicațiile consumă bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Mărire"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Folosirea accesibilității"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ecran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicații folosesc bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Atinge pentru mai multe detalii privind bateria și utilizarea datelor"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite unei aplicații partenere să inițieze servicii în prim-plan din fundal."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfonul este disponibil"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfonul este blocat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nu se poate oglindi pe ecran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Folosește alt cablu și încearcă din nou"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cablul poate să nu fie compatibil cu ecranele"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Cablul USB-C poate să nu se conecteze corespunzător la ecrane"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Funcția Dual screen este activată"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește ambele ecrane pentru a afișa conținut"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 920168a..7f5e87f 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Приложения, расходующие заряд"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увеличение"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Сервисы специальных возможностей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Экран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" расходует заряд"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Несколько приложений (<xliff:g id="NUMBER">%1$d</xliff:g>) расходуют заряд"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Нажмите, чтобы проверить энергопотребление и трафик"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Сопутствующее приложение сможет запускать активные службы из фонового режима."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон доступен."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон заблокирован."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не удается дублировать на экран"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Используйте другой кабель или повторите попытку."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель может не подходить для подключения к дисплеям"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Возможно, подключение дисплеев с помощью этого кабеля USB-C не поддерживается."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Функция Dual Screen включена"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> использует оба экрана."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 5f743a4..e90f1a2 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"බැටරිය භාවිත කරන යෙදුම්"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"විශාලනය"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ප්රවේශ්යතා භාවිතය"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"සංදර්ශකය"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> බැටරිය භාවිත කරයි"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"යෙදුම් <xliff:g id="NUMBER">%1$d</xliff:g>ක් බැටරිය භාවිත කරයි"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"බැටරි හා දත්ත භාවිතය පිළිබඳව විස්තර සඳහා තට්ටු කරන්න"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"පසුබිමේ සිට පෙරබිම් සේවා ආරම්භ කිරීමට සහායක යෙදුමකට ඉඩ දෙයි."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"මයික්රෆෝනය තිබේ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"මයික්රෆෝනය අවහිර කර ඇත"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"සංදර්ශකයට දර්පණය කළ නොහැක"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"වෙනස් කේබලයක් භාවිතා කර නැවත උත්සාහ කරන්න"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"කේබලය සංදර්ශක වෙත සහාය නොදැක්විය හැක"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ඔබේ USB-C කේබලයට සංදර්ශකවලට නිසි ලෙස සම්බන්ධ නොවිය හැක"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen සක්රීයයි"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"අන්තර්ගතය පෙන්වීමට <xliff:g id="APP_NAME">%1$s</xliff:g> සංදර්ශන දෙකම භාවිත කරයි"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 6bbc110..3da576c 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikácie spotrebúvajúce batériu"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zväčšenie"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využitie dostupnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Obrazovka"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> používa batériu"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikácie (<xliff:g id="NUMBER">%1$d</xliff:g>) používajú batériu"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Klepnutím zobrazíte podrobnosti o batérii a spotrebe dát"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje sprievodnej aplikácii spúšťať služby na popredí z pozadia."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofón je k dispozícii"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofón je blokovaný"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nedá sa zrkadliť do obrazovky"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použite iný kábel a skúste znova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kábel nemusí podporovať obrazovky"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kábel USB‑C sa nemusí dať správne pripojiť k obrazovkám"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Je zapnutá funkcia Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> zobrazuje obsah na oboch obrazovkách"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 3999f9f..5b731b7 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije, ki porabljajo energijo baterije"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Povečava"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uporaba funkcij za dostopnost"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Zaslon"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> porablja energijo baterije"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Toliko aplikacij porablja energijo baterije: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dotaknite se za prikaz podrobnosti porabe baterije in prenosa podatkov"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Spremljevalni aplikaciji dovoljuje, da storitve v ospredju zažene iz ozadja."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je na voljo"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ni mogoče zrcaliti zaslona"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Uporabite drug kabel in poskusite znova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel morda ne podpira zaslonov"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C se morda ne more ustrezno povezati z zasloni."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je vklopljen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> uporablja oba zaslona za prikaz vsebine."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index e746463..a4fd1bf1 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacionet që konsumojnë baterinë"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zmadhimi"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Përdorimi i qasshmërisë"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekrani"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> po përdor baterinë"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikacione po përdorin baterinë"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Trokit për detaje mbi baterinë dhe përdorimin e të dhënave"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lejon një aplikacion shoqërues të fillojë shërbimet në plan të parë nga sfondi."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoni ofrohet"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni është i bllokuar"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nuk mund të pasqyrojë tek ekrani"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Përdor një kabllo tjetër dhe provo përsëri"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablloja nuk mund të mbështetë ekranet"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kablloja jote USB-C mund të mos lidhet siç duhet me ekranet"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen është aktiv"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> po i përdor të dyja ekranet për të shfaqur përmbajtje"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index b5aefaa..e5ae2f7 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликације које троше батерију"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увећање"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Коришћење Приступачности"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Екран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерију"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Апликације (<xliff:g id="NUMBER">%1$d</xliff:g>) користе батерију"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Додирните за детаље о батерији и потрошњи података"</string>
@@ -314,7 +315,7 @@
<string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Музика и звук"</string>
<string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"приступ музици и аудио садржају на уређају"</string>
<string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Слике и видео снимци"</string>
- <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видео снимцима на уређају"</string>
+ <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видеима на уређају"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"снима звук"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Физичке активности"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозвољава пратећој апликацији да покрене услуге у првом плану из позадине."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон је доступан"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон је блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Пресликавање на екран није могуће"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Употребите други кабл и пробајте поново"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабл не подржава екране"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабл се не повезује правилно са екранима"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen је укључен"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи оба екрана за приказивање садржаја"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index a24ca19..f3917fe 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Appar som drar batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Förstoring"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Tillgänglighetsanvändning"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skärm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> drar batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> appar drar batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tryck för information om batteri- och dataanvändning"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillåter att en tillhörande app startar förgrundstjänster i bakgrunden."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen är tillgänglig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen är blockerad"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det går inte spegla till skärmen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Använd en annan kabel och försök igen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabeln kanske inte har stöd för skärmar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Det kanske inte går att ansluta skärmar korrekt med den här USB-C-kabeln"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen är på"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> använder båda skärmarna för att visa innehåll"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 7171486..95c1b25 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programu zinazotumia betri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ukuzaji"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Matumizi ya zana za ufikivu"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skrini"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia betri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Programu <xliff:g id="NUMBER">%1$d</xliff:g> zinatumia betri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Gusa ili upate maelezo kuhusu betri na matumizi ya data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Huruhusu programu oanifu kuanzisha huduma zinazoonekana kwenye skrini kutoka katika huduma zinazoendelea chinichini."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Maikrofoni inapatikana"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Maikrofoni imezuiwa"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Imeshindwa kuakisi kwenye skrini"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Tumia kebo tofauti kisha ujaribu tena"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Huenda kebo haioani na skrini"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Huenda kebo yako ya USB-C isiunganishwe vizuri na skrini"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen imewasha"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia skrini zote kuonyesha maudhui"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 4fe363e..9ad4bd2 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"பேட்டரியைப் பயன்படுத்தும் ஆப்ஸ்"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"பெரிதாக்கல்"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"அணுகல்தன்மை உபயோகம்"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"டிஸ்ப்ளே"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் பேட்டரியைப் பயன்படுத்துகிறது"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ஆப்ஸ் பேட்டரியைப் பயன்படுத்துகின்றன"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"பேட்டரி மற்றும் டேட்டா உபயோக விவரங்களைக் காண, தட்டவும்"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"பின்னணியிலிருந்து முன்புலச் சேவைகளைத் தொடங்க துணைத் தயாரிப்பு ஆப்ஸை அனுமதிக்கும்."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"மைக்ரோஃபோன் இயக்கப்பட்டது"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"மைக்ரோஃபோன் முடக்கப்பட்டுள்ளது"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"டிஸ்ப்ளேயில் பிரதிபலிக்க முடியவில்லை"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"வெவ்வேறு கேபிள்களைப் பயன்படுத்தி மீண்டும் முயலவும்"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"டிஸ்ப்ளேக்களைக் கேபிள் ஆதரிக்காமல் இருக்கக்கூடும்"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"டிஸ்ப்ளேக்களில் உங்கள் USB-C கேபிள் சரியாக இணைக்கப்படாமல் இருக்கக்கூடும்"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"இரட்டைத் திரை"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"இரட்டைத் திரை அம்சம் இயக்கத்தில் உள்ளது"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"உள்ளடக்கத்தைக் காட்டுவதற்கு இரண்டு டிஸ்ப்ளேக்களையும் <xliff:g id="APP_NAME">%1$s</xliff:g> பயன்படுத்துகிறது"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index aa22aea..d1c336d 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"బ్యాటరీని ఉపయోగిస్తున్న యాప్లు"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"మ్యాగ్నిఫికేషన్"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"యాక్సెసిబిలిటీ వినియోగం"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"డిస్ప్లే చేయండి"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> బ్యాటరీని ఉపయోగిస్తోంది"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> యాప్లు బ్యాటరీని ఉపయోగిస్తున్నాయి"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"బ్యాటరీ మరియు డేటా వినియోగ వివరాల కోసం నొక్కండి"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"బ్యాక్గ్రౌండ్ నుండి ఫోర్గ్రౌండ్ సర్వీస్లను ప్రారంభించడానికి సహాయక యాప్ను అనుమతిస్తుంది."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"మైక్రోఫోన్ అందుబాటులో ఉంది"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"మైక్రోఫోన్ బ్లాక్ చేయబడింది"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"డిస్ప్లే చేయడానికి మిర్రర్ చేయడం సాధ్యపడదు"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"వేరే కేబుల్ను ఉపయోగించి, మళ్లీ ట్రై చేయండి"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"డిస్ప్లేలను కేబుల్ సపోర్ట్ చేయకపోవచ్చు"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"మీ USB-C కేబుల్, డిస్ప్లేలకు సరిగ్గా కనెక్ట్ కాకపోవచ్చు"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ఆన్లో ఉంది"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"కంటెంట్ను చూపడం కోసం <xliff:g id="APP_NAME">%1$s</xliff:g> రెండు డిస్ప్లేలనూ ఉపయోగిస్తోంది"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index eb4e2f7..4f53fcd 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"แอปหลายแอปกำลังใช้แบตเตอรี่"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"การขยาย"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"การใช้งานการช่วยเหลือพิเศษ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"จอแสดงผล"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้แบตเตอรี่"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"แอป <xliff:g id="NUMBER">%1$d</xliff:g> แอปกำลังใช้แบตเตอรี่"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"แตะเพื่อดูรายละเอียดเกี่ยวกับแบตเตอรี่และปริมาณการใช้อินเทอร์เน็ต"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"อนุญาตจากเบื้องหลังให้แอปที่ใช้ร่วมกันเริ่มการทำงานของบริการที่ทำงานอยู่เบื้องหน้า"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ไมโครโฟนพร้อมใช้งาน"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ไมโครโฟนถูกบล็อก"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"มิเรอร์ไปยังจอแสดงผลไม่ได้"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"โปรดใช้สายอื่นและลองอีกครั้ง"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"สายสัญญาณอาจไม่รองรับจอแสดงผล"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"สาย USB-C อาจเชื่อมต่อกับจอแสดงผลอย่างไม่ถูกต้อง"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen เปิดอยู่"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้จอแสดงผลทั้งสองจอเพื่อแสดงเนื้อหา"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7249c51..2477698 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Mga app na kumokonsumo ng baterya"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pag-magnify"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Paggamit sa pagiging accessible"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Gumagamit ng baterya ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Gumagamit ng baterya ang <xliff:g id="NUMBER">%1$d</xliff:g> (na) app"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"I-tap para sa mga detalye tungkol sa paggamit ng baterya at data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Nagbibigay-daan sa kasamang app na magsimula ng mga serbisyo sa foreground mula sa background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Available ang mikropono"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Naka-block ang mikropono"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Hindi makapag-mirror sa display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gumamit ng ibang cable at subukan ulit"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Posibleng hindi sinusuportahan ng cable ang mga display"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Posibleng hindi kumonekta nang maayos sa mga display ang iyong USB-C cable"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Naka-on ang dual screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Ginagamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> ang parehong display para magpakita ng content"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8f02efd..2ebfe91 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Pil kullanan uygulamalar"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Büyütme"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Erişilebilirlik kullanımı"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> pil kullanıyor"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> uygulama pil kullanıyor"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Pil ve veri kullanımı ile ilgili ayrıntılar için dokunun"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tamamlayıcı uygulamanın arka plandan ön plan hizmetlerini başlatmasına izin verir."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon kullanılabilir"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon engellenmiş"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekrana yansıtılamıyor"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Farklı kablo kullanarak tekrar deneyin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablo, ekranları desteklemeyebilir"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kablonuz ekranlara doğru şekilde bağlanamayabilir"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen açık"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>, içeriği göstermek için her iki ekranı da kullanıyor"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index d34e976..d4cd207 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Додатки, що використовують заряд акумулятора"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Збільшення"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Використання спеціальних можливостей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує заряд акумулятора"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Додатків, що використовують заряд акумулятора: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Торкніться, щоб перевірити використання акумулятора й трафік"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволяє супутньому додатку запускати активні сервіси у фоновому режимі."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Мікрофон доступний"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрофон заблоковано"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Неможливо дублювати на дисплей"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Скористайтесь іншим кабелем і повторіть спробу"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель може не підтримувати дисплеї"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ваш кабель USB-C може не підключатися до дисплеїв належним чином"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen увімкнено"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує обидва екрани для показу контенту"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 631a573..8634294 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ایپس بیٹری خرچ کر رہی ہیں"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"میگنیفکیشن"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ایکسیسبیلٹی کا استعمال"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ڈسپلے"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> بیٹری کا استعمال کر رہی ہے"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ایپس بیٹری کا استعمال کر رہی ہیں"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"بیٹری اور ڈیٹا استعمال کے بارے میں تفصیلات کے لیے تھپتھپائیں"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ساتھی ایپ کو پس منظر سے پیش منظر کی سروسز شروع کرنے کی اجازت دیتی ہے۔"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"مائیکروفون دستیاب ہے"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"مائیکروفون مسدود ہے"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ڈسپلے پر دو طرفہ مطابقت پذیری ممکن نہیں ہے"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"مختلف کیبل استعمال کریں اور دوبارہ کوشش کریں"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ہو سکتا ہے کہ کیبل ڈسپلیز کو سپورٹ نہ کرے"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ہو سکتا ہے کہ آپ کی USB-C کیبل مناسب طریقے سے ڈسپلیز سے منسلک نہ ہو"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"دوہری اسکرین"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"دوہری اسکرین آن ہے"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"مواد دکھانے کیلئے <xliff:g id="APP_NAME">%1$s</xliff:g> دونوں ڈسپلیز استعمال کر رہی ہے"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 4262342..fdea194 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Batareya quvvatini sarflayotgan ilovalar"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Kattalashtirish"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Qulayliklar ishlatilishi"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displey"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi batareya quvvatini sarflamoqda"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ta ilova batareya quvvatini sarflamoqda"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Batareya va trafik sarfi tafsilotlari uchun ustiga bosing"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hamroh ilovaga faol xizmatlarni fonda ishga tushirishga ruxsat beradi."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon yoqildi"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon bloklandi"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeyga translatsiya qilinmaydi"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Boshqa kabel yordamida qayta urining"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeylar bilan ishlamasligi mumkin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabelingiz displeylarga toʻgʻri ulanmasligi mumkin"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Ikkita ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Ikki ekranli rejim yoniq"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> kontentni ikkala ekranda chiqarmoqda"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 78318b1..12a61ed 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Các ứng dụng tiêu thụ pin"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Phóng to"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Việc sử dụng tính năng hỗ trợ tiếp cận"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Màn hình"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang sử dụng pin"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ứng dụng đang sử dụng pin"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Nhấn để biết chi tiết về mức sử dụng dữ liệu và pin"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Cho phép một ứng dụng đồng hành bắt đầu các dịch vụ trên nền trước từ nền."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Micrô đang hoạt động"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Micrô đang bị chặn"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Không chiếu được nội dung lên màn hình"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Hãy dùng một cáp khác rồi thử lại"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Có thể cáp không hỗ trợ màn hình"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Có thể cáp USB-C của bạn chưa kết nối đúng cách với màn hình"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Chế độ Dual screen bật"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang dùng cả hai màn hình để thể hiện nội dung"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 16c1013..d79a772 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"消耗电量的应用"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大功能"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"无障碍功能使用情况"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"显示屏"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在消耗电量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 个应用正在消耗电量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"点按即可详细了解电量和流量消耗情况"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允许配套应用从后台启动前台服务。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"麦克风可用"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"麦克风已被屏蔽"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"无法镜像到显示屏"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"请改用其他数据线并重试"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"数据线可能不支持显示屏"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"您的 USB-C 数据线可能没有正确连接到显示屏"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"双屏幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"双屏幕功能已开启"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在使用双屏幕显示内容"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 06ec1dd..fe3644e 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"耗用電量的應用程式"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"無障礙功能使用情況"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"顯示屏"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用電量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在使用電量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"輕按即可查看電池和數據用量詳情"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"可以使用麥克風"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"已封鎖麥克風"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他連接線,然後再試一次"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"連接線可能不支援顯示屏"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"你的 USB-C 連接線可能未妥善連接顯示屏"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"已開啟雙螢幕功能"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用雙螢幕顯示內容"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 81bdc37..5b65201 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"正在耗用電量的應用程式"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"無障礙功能使用情形"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"螢幕"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在耗用電量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在耗用電量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"輕觸即可查看電池和數據用量詳情"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"麥克風已可使用"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"麥克風已封鎖"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他傳輸線,然後再試一次"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"傳輸線可能不支援螢幕"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C 傳輸線可能未妥善連接到螢幕"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"雙螢幕功能已啟用"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用雙螢幕顯示內容"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index a2eb545..b701abe 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Izinhlelo zokusebenza ezidla ibhethri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ukukhuliswa"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Ukusetshenziswa kokufinyeleleka"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Bonisa"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa ibhethri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> izinhlelo zokusebenza zisebenzisa ibhethri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Thepha ngemininingwane ekusetshenzisweni kwebhethri nedatha"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ivumela i-app ehambisanayo ukuthi iqale amasevisi angaphambili kusukela ngemuva."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Imakrofoni iyatholakala"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Imakrofoni ivinjiwe"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ayikwazi ukufanisa nesibonisi"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Sebenzisa ikhebuli ehlukile bese uyazama futhi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ikhebuli ingase ingasekeli iziboniso"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ikhebuli yakho ye-USB-C ingase ingaxhumi kahle kuzibonisi"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Isikrini esikabili"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Isikrini esikabili sivuliwe"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa zombili izibonisi ukukhombisa okuqukethwe"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 04fd70a..3496994 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10090,6 +10090,15 @@
<!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->
<attr name="lStar" format="float"/>
+ <!-- The attributes of the {@code <locale-config>} tag. -->
+ <!-- @FlaggedApi("android.content.res.default_locale") -->
+ <declare-styleable name="LocaleConfig">
+ <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
+ the strings in values/strings.xml (the default strings in the directory with no locale
+ qualifier) are in. -->
+ <attr name="defaultLocale" format="string"/>
+ </declare-styleable>
+
<!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. -->
<declare-styleable name="LocaleConfig_Locale">
<!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index adc7fe0..4cd4f63 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -110,6 +110,8 @@
<eat-comment/>
<staging-public-group type="attr" first-id="0x01bd0000">
+ <!-- @FlaggedApi("android.content.res.default_locale") -->
+ <public name="defaultLocale"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 445ddf5..2993a0e 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -65,7 +65,6 @@
"device-time-shell-utils",
"testables",
"com.android.text.flags-aconfig-java",
- "flag-junit",
],
libs: [
@@ -76,7 +75,6 @@
"framework",
"ext",
"framework-res",
- "android.view.flags-aconfig-java",
],
jni_libs: [
"libpowermanagertest_jni",
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index 531404b..d10cf16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -62,4 +62,24 @@
verify(callback2, times(1)).preExecute(clientTransactionHandler);
verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
}
+
+ @Test
+ public void testPreExecuteTransactionItems() {
+ final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+ final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+ final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ final ClientTransactionHandler clientTransactionHandler =
+ mock(ClientTransactionHandler.class);
+
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(stateRequest);
+
+ transaction.preExecute(clientTransactionHandler);
+
+ verify(callback1, times(1)).preExecute(clientTransactionHandler);
+ verify(callback2, times(1)).preExecute(clientTransactionHandler);
+ verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 44a4d58..f2b0f2e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -247,6 +247,31 @@
}
@Test
+ public void testExecuteTransactionItems_transactionResolution() {
+ ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+ when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
+ ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+ when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
+ ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ IBinder token = mock(IBinder.class);
+ when(stateRequest.getActivityToken()).thenReturn(token);
+ when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+ ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(stateRequest);
+
+ transaction.preExecute(mTransactionHandler);
+ mExecutor.execute(transaction);
+
+ InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
+ inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
+ inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
+ inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ }
+
+ @Test
public void testDoNotLaunchDestroyedActivity() {
final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
@@ -279,12 +304,43 @@
}
@Test
+ public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
+ final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
+ when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
+ // Assume launch transaction is still in queue, so there is no client record.
+ when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+
+ // An incoming destroy transaction enters binder thread (preExecute).
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
+ destroyTransaction.addTransactionItem(
+ DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+ destroyTransaction.preExecute(mTransactionHandler);
+ // The activity should be added to to-be-destroyed container.
+ assertEquals(1, activitiesToBeDestroyed.size());
+
+ // A previous queued launch transaction runs on main thread (execute).
+ final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
+ final LaunchActivityItem launchItem =
+ spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+ launchTransaction.addTransactionItem(launchItem);
+ mExecutor.execute(launchTransaction);
+
+ // The launch transaction should not be executed because its token is in the
+ // to-be-destroyed container.
+ verify(launchItem, never()).execute(any(), any());
+
+ // After the destroy transaction has been executed, the token should be removed.
+ mExecutor.execute(destroyTransaction);
+ assertTrue(activitiesToBeDestroyed.isEmpty());
+ }
+
+ @Test
public void testActivityResultRequiredStateResolution() {
when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
- IBinder token = mock(IBinder.class);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
transaction.addCallback(postExecItem);
@@ -300,6 +356,26 @@
}
@Test
+ public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
+ when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+
+ PostExecItem postExecItem = new PostExecItem(ON_RESUME);
+
+ ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(postExecItem);
+
+ // Verify resolution that should get to onPause
+ mClientRecord.setState(ON_RESUME);
+ mExecutor.executeTransactionItems(transaction);
+ verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+
+ // Verify resolution that should get to onStart
+ mClientRecord.setState(ON_STOP);
+ mExecutor.executeTransactionItems(transaction);
+ verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+ }
+
+ @Test
public void testClosestStateResolutionForSameState() {
final int[] allStates = new int[] {
ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
@@ -444,6 +520,18 @@
mExecutor.executeCallbacks(transaction);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
+ final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+ when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(activityItem);
+ when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+
+ mExecutor.executeTransactionItems(transaction);
+ }
+
@Test
public void testActivityItemExecute() {
final IBinder token = mock(IBinder.class);
@@ -464,6 +552,26 @@
inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
}
+ @Test
+ public void testExecuteTransactionItems_activityItemExecute() {
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+ when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+ when(activityItem.getActivityToken()).thenReturn(token);
+ transaction.addTransactionItem(activityItem);
+ final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ transaction.addTransactionItem(stateRequest);
+ when(stateRequest.getActivityToken()).thenReturn(token);
+ when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+ mExecutor.execute(transaction);
+
+ final InOrder inOrder = inOrder(activityItem, stateRequest);
+ inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ }
+
private static int[] shuffledArray(int[] inputArray) {
final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
Collections.shuffle(list);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 7d047c9..4aa62c5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -285,6 +285,10 @@
78 /* configChanges */);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(lifecycleRequest);
+
transaction.addCallback(callback1);
transaction.addCallback(callback2);
transaction.setLifecycleStateRequest(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 3f78396..0d687b2 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 @@
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 @@
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 @@
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 @@
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 @@
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/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 1f00a7a..88fc826 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -81,4 +81,49 @@
// restore the original values
LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
}
+
+ @SmallTest
+ public void testIntersection() {
+ LocaleList localesWithN = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.ITALIAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.JAPAN,
+ Locale.CANADA,
+ Locale.CANADA_FRENCH);
+ LocaleList localesWithE = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.CANADA_FRENCH);
+ LocaleList localesWithNAndE = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.CANADA_FRENCH);
+
+ assertEquals(localesWithNAndE, new LocaleList(localesWithE.getIntersection(localesWithN)));
+ }
}
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index 517aeae..0855268 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -20,8 +20,6 @@
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -42,16 +40,12 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.SharedMemory;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
-
-import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -71,6 +65,9 @@
private NotificationChannel mNotificationChannel;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
// TODO(b/284297289): remove this flag set once resolved.
@Parameterized.Parameters(name = "rankingUpdateAshmem={0}")
public static Boolean[] getRankingUpdateAshmem() {
@@ -424,30 +421,11 @@
mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
NotificationManager.IMPORTANCE_DEFAULT);
- SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() {
- @Override
- public boolean isEnabled(Flag flag) {
- if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
- return mRankingUpdateAshmem;
- }
- return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
- }
-
- @Override
- public int getIntValue(Flag flag) {
- return 0;
- }
-
- @Override
- public String getStringValue(Flag flag) {
- return null;
- }
- };
- }
-
- @After
- public void tearDown() {
- SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+ if (mRankingUpdateAshmem) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+ } else {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+ }
}
/**
@@ -497,8 +475,7 @@
parcel.setDataPosition(0);
NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
// The rankingUpdate file descriptor is only non-null in the new path.
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
assertTrue(nru1.isFdNotNullAndClosed());
}
detailedAssertEquals(nru, nru1);
@@ -636,7 +613,7 @@
@Test
public void testRankingUpdate_writesSmartActionToParcel() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -674,7 +651,7 @@
@Test
public void testRankingUpdate_handlesEmptySmartActionList() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -697,7 +674,7 @@
@Test
public void testRankingUpdate_handlesNullSmartActionList() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
NotificationListenerService.Ranking ranking =
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 2ca9994..23668a4 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -20,4 +20,7 @@
per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
# Stylus
-per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file
+per-file stylus/* = file:/core/java/android/text/OWNERS
+
+# View
+file:/core/java/android/view/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java
new file mode 100644
index 0000000..6acab36
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java
@@ -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 android.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public final class ScrollFeedbackProviderTest {
+ private final Context mContext = InstrumentationRegistry.getContext();
+
+ @Test
+ public void testDefaultProviderType() {
+ View view = new View(mContext);
+
+ ScrollFeedbackProvider provider = ScrollFeedbackProvider.createProvider(view);
+
+ assertThat(provider).isInstanceOf(HapticScrollFeedbackProvider.class);
+ }
+
+ @Test
+ public void testDefaultProvider_createsDistinctProvidesOnMultipleCalls() {
+ View view1 = new View(mContext);
+ View view2 = new View(mContext);
+
+ ScrollFeedbackProvider view1Provider1 = ScrollFeedbackProvider.createProvider(view1);
+ ScrollFeedbackProvider view1Provider2 = ScrollFeedbackProvider.createProvider(view1);
+ ScrollFeedbackProvider view2Provider = ScrollFeedbackProvider.createProvider(view2);
+
+ assertThat(view1Provider1 == view1Provider2).isFalse();
+ assertThat(view1Provider1 == view2Provider).isFalse();
+ }
+}
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index e117051..71bdce4 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 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 @@
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 1a38dec..6a9fc04 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.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 @@
// 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 @@
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 b71aaf3..cc73ece 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2887,6 +2887,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "419378610": {
+ "message": "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"422634333": {
"message": "First draw done in potential wallpaper target %s",
"level": "VERBOSE",
@@ -4339,12 +4345,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "1936800105": {
- "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"1945495497": {
"message": "Focused window didn't have a valid surface drawn.",
"level": "DEBUG",
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index e030dad..9a0a22a 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -2,7 +2,7 @@
flag {
name: "exact_compute_bounds"
- namespace: "framework_graphics"
+ namespace: "core_graphics"
description: "Add a function without unused exact param for computeBounds."
bug: "304478551"
}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 9fde0fd..4eaa013 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2113,6 +2113,31 @@
* The recommended additional space to add between lines of text.
*/
public float leading;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof FontMetrics)) return false;
+ FontMetrics that = (FontMetrics) o;
+ return that.top == top && that.ascent == ascent && that.descent == descent
+ && that.bottom == bottom && that.leading == leading;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(top, ascent, descent, bottom, leading);
+ }
+
+ @Override
+ public String toString() {
+ return "FontMetrics{"
+ + "top=" + top
+ + ", ascent=" + ascent
+ + ", descent=" + descent
+ + ", bottom=" + bottom
+ + ", leading=" + leading
+ + '}';
+ }
}
/**
@@ -2309,6 +2334,33 @@
*/
public int leading;
+ /**
+ * Set values from {@link FontMetricsInt}.
+ * @param fontMetricsInt a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetricsInt fontMetricsInt) {
+ top = fontMetricsInt.top;
+ ascent = fontMetricsInt.ascent;
+ descent = fontMetricsInt.descent;
+ bottom = fontMetricsInt.bottom;
+ leading = fontMetricsInt.leading;
+ }
+
+ /**
+ * Set values from {@link FontMetrics} with rounding accordingly.
+ * @param fontMetrics a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetrics fontMetrics) {
+ // See GraphicsJNI::set_metrics_int method for consistency.
+ top = (int) Math.floor(fontMetrics.top);
+ ascent = Math.round(fontMetrics.ascent);
+ descent = Math.round(fontMetrics.descent);
+ bottom = (int) Math.ceil(fontMetrics.bottom);
+ leading = Math.round(fontMetrics.leading);
+ }
+
@Override public String toString() {
return "FontMetricsInt: top=" + top + " ascent=" + ascent +
" descent=" + descent + " bottom=" + bottom +
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index 6e04a2f..ba5628c 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -182,7 +182,7 @@
// 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 4c75356..685fd82 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 @@
* @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 618aa5b..3ef714ed 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -241,7 +241,7 @@
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 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 569f9b6..7932e33 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 @@
* @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 @@
* @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 @@
* 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 @@
* @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 @@
* @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 0000000..ff49cdc
--- /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 cdfc4c8..49606f0 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.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.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 @@
* 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>
@@ -291,8 +292,8 @@
// Resets the isolated navigation and updates the container.
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.setTaskFragmentIsolatedNavigation(wct,
- containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin,
+ false /* isolated */);
updateContainer(wct, containerToUnpin);
transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
@@ -759,6 +760,8 @@
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) {
@@ -877,19 +880,17 @@
return true;
}
- // Skip resolving if the activity is on a pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
- final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
- if (container != null && taskContainer != null
- && taskContainer.isTaskFragmentContainerPinned(container)) {
+ // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer.
+ if (container != null && container.isIsolatedNavigationEnabled()) {
return true;
}
+ final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
if (!isOnReparent && taskContainer != null
&& taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
!= container) {
// Do not resolve if the launched activity is not the top-most container (excludes
- // the pinned container) in the Task.
+ // the pinned and overlay container) in the Task.
return true;
}
@@ -1007,6 +1008,7 @@
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 +1168,7 @@
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.
@@ -1308,15 +1310,12 @@
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
- // Skip resolving if started from pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
+ // Skip resolving if started from an isolated navigated TaskFragmentContainer.
if (launchingActivity != null) {
final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
launchingActivity);
- final TaskContainer taskContainer =
- taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
- if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
- taskFragmentContainer)) {
+ if (taskFragmentContainer != null
+ && taskFragmentContainer.isIsolatedNavigationEnabled()) {
return null;
}
}
@@ -1408,6 +1407,22 @@
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 +1438,46 @@
// 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 +1497,7 @@
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 +1566,22 @@
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 +1595,14 @@
* @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 +1611,7 @@
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
return container;
}
@@ -1693,31 +1751,6 @@
}
/**
- * Returns the topmost not finished container in Task of given task id.
- */
- @GuardedBy("mLock")
- @Nullable
- TaskFragmentContainer getTopActiveContainer(int taskId) {
- final TaskContainer taskContainer = mTaskContainers.get(taskId);
- if (taskContainer == null) {
- return null;
- }
- final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
- for (int i = containers.size() - 1; i >= 0; i--) {
- final TaskFragmentContainer container = containers.get(i);
- if (!container.isFinished() && (container.getRunningActivityCount() > 0
- // We may be waiting for the top TaskFragment to become non-empty after
- // creation. In that case, we don't want to treat the TaskFragment below it as
- // top active, otherwise it may incorrectly launch placeholder on top of the
- // pending TaskFragment.
- || container.isWaitingActivityAppear())) {
- return container;
- }
- }
- return null;
- }
-
- /**
* Updates the presentation of the container. If the container is part of the split or should
* have a placeholder, it will also update the other part of the split.
*/
@@ -1754,13 +1787,12 @@
* 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}.
*/
@@ -1906,7 +1938,8 @@
/** Whether or not to allow activity in this container to launch placeholder. */
@GuardedBy("mLock")
private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
- final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
+ final TaskFragmentContainer topContainer = container.getTaskContainer()
+ .getTopNonFinishingTaskFragmentContainer();
if (container != topContainer) {
// The container is not the top most.
if (!container.isVisible()) {
@@ -2255,6 +2288,96 @@
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 +2540,16 @@
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 d894487..faf7c39 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.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 @@
}
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);
@@ -390,14 +388,27 @@
return;
}
- setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(),
- !isStacked /* isolatedNav */);
+ setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */);
if (isStacked && !splitPinRule.isSticky()) {
secondaryContainer.getTaskContainer().removeSplitPinContainer();
}
}
/**
+ * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
+ */
+ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer taskFragmentContainer,
+ boolean isolatedNavigationEnabled) {
+ if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+ return;
+ }
+ taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+ setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+ isolatedNavigationEnabled);
+ }
+
+ /**
* Resizes the task fragment if it was already registered. Skips the operation if the container
* creation has not been reported from the server yet.
*/
@@ -618,7 +629,7 @@
@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 +724,15 @@
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 +1081,6 @@
@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 9a0769a..9e53380 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.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 @@
@Nullable
private SplitPinContainer mSplitPinContainer;
+ /** The overlay container in this Task. */
+ @Nullable
+ private TaskFragmentContainer mOverlayContainer;
+
@NonNull
private final Configuration mConfiguration;
@@ -184,11 +191,20 @@
@Nullable
TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
+ return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */);
+ }
+
+ @Nullable
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin,
+ boolean includeOverlay) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = mContainers.get(i);
if (!includePin && isTaskFragmentContainerPinned(container)) {
continue;
}
+ if (!includeOverlay && container.isOverlay()) {
+ continue;
+ }
if (!container.isFinished()) {
return container;
}
@@ -221,6 +237,12 @@
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 +333,8 @@
onTaskFragmentContainerUpdated();
}
- void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
- mContainers.removeAll(taskFragmentContainer);
+ void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) {
+ mContainers.removeAll(taskFragmentContainers);
onTaskFragmentContainerUpdated();
}
@@ -332,6 +354,15 @@
}
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 +375,7 @@
}
// 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 +387,31 @@
}
}
+ 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 +451,15 @@
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 0a694b5..3e7f99b 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 @@
*/
private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
+ @Nullable
+ private final String mOverlayTag;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -157,15 +160,32 @@
*/
private boolean mHasCrossProcessActivities;
+ /** Whether this TaskFragment enable isolated navigation. */
+ private boolean mIsIsolatedNavigationEnabled;
+
+ /**
+ * @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 +194,8 @@
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) {
@@ -786,6 +808,16 @@
mLastCompanionTaskFragment = fragmentToken;
}
+ /** Returns whether to enable isolated navigation or not. */
+ boolean isIsolatedNavigationEnabled() {
+ return mIsIsolatedNavigationEnabled;
+ }
+
+ /** Sets whether to enable isolated navigation or not. */
+ void setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled) {
+ mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
+ }
+
/**
* Adds the pending appeared activity that has requested to be launched in this task fragment.
* @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
@@ -863,6 +895,20 @@
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 +927,7 @@
+ " 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 ed2ff2d..4ddbd13 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@
"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 0000000..405f0b2
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -0,0 +1,451 @@
+/*
+ * 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.EmbeddingTestUtils.createSplitPairRuleBuilder;
+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());
+ }
+
+ @Test
+ public void testGetTopNonFishingTaskFragmentContainerWithOverlay() {
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ // Add a SplitPinContainer, the overlay should be on top
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+
+ final TaskFragmentContainer primaryContainer =
+ createMockTaskFragmentContainer(primaryActivity);
+ final TaskFragmentContainer secondaryContainer =
+ createMockTaskFragmentContainer(secondaryActivity);
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityActivityPair -> true /* activityPairPredicate */,
+ activityIntentPair -> true /* activityIntentPairPredicate */,
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
+ secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes());
+ SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.pinTopActivityStack(TASK_ID, splitPinRule);
+ final TaskFragmentContainer topPinnedContainer = mSplitController.getTaskContainer(TASK_ID)
+ .getSplitPinContainer().getSecondaryContainer();
+
+ // Add a normal container after the overlay, the overlay should still on top,
+ // and the SplitPinContainer should also on top of the normal one.
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+
+ assertThat(taskContainer.getTaskFragmentContainers())
+ .containsExactly(primaryContainer, container, secondaryContainer, overlayContainer)
+ .inOrder();
+
+ assertWithMessage("The pinned container must be returned excluding the overlay")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer())
+ .isEqualTo(topPinnedContainer);
+
+ assertThat(taskContainer.getTopNonFinishingTaskFragmentContainer(false))
+ .isEqualTo(container);
+
+ assertWithMessage("The overlay container must be returned since it's always on top")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer(
+ false /* includePin */, true /* includeOverlay */))
+ .isEqualTo(overlayContainer);
+ }
+
+ /**
+ * 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);
+ }
+
+ /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
+ @NonNull
+ private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
+ setupTaskFragmentInfo(container, activity);
+ return container;
+ }
+
+ @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 d440a3e..96839c5 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
@@ -48,18 +48,18 @@
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.assertWithMessage;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
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;
@@ -175,52 +175,6 @@
}
@Test
- public void testGetTopActiveContainer() {
- final TaskContainer taskContainer = createTestTaskContainer();
- // tf1 has no running activity so is not active.
- final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
- // tf2 has running activity so is active.
- final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
- doReturn(1).when(tf2).getRunningActivityCount();
- taskContainer.addTaskFragmentContainer(tf2);
- // tf3 is finished so is not active.
- final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
- doReturn(true).when(tf3).isFinished();
- doReturn(false).when(tf3).isWaitingActivityAppear();
- taskContainer.addTaskFragmentContainer(tf3);
- mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
-
- assertWithMessage("Must return tf2 because tf3 is not active.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf3);
-
- assertWithMessage("Must return tf2 because tf2 has running activity.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf2);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
- doReturn(new ArrayList<>()).when(info).getActivities();
- doReturn(true).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
- + " creation.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- doReturn(false).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return null because tf1 becomes empty.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
- }
-
- @Test
public void testOnTaskFragmentVanished() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
@@ -305,7 +259,9 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).getTopActiveContainer(TASK_ID);
+ TaskContainer taskContainer = tf.getTaskContainer();
+ spyOn(taskContainer);
+ verify(taskContainer, never()).getTopNonFinishingTaskFragmentContainer();
// Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called.
doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf);
@@ -320,7 +276,7 @@
doReturn(tf).when(splitContainer).getSecondaryContainer();
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
- final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ taskContainer = mSplitController.getTaskContainer(TASK_ID);
taskContainer.addSplitContainer(splitContainer);
// Add a mock SplitContainer on top of splitContainer
final SplitContainer splitContainer2 = mock(SplitContainer.class);
@@ -595,13 +551,12 @@
}
@Test
- public void testResolveStartActivityIntent_skipIfPinned() {
+ public void testResolveStartActivityIntent_skipIfIsolatedNavEnabled() {
final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
- final TaskContainer taskContainer = container.getTaskContainer();
- spyOn(taskContainer);
+ container.setIsolatedNavigationEnabled(true);
+
final Intent intent = new Intent();
setupSplitRule(mActivity, intent);
- doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
mActivity));
}
@@ -634,7 +589,8 @@
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 +752,8 @@
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 +795,8 @@
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());
}
@@ -1565,9 +1523,9 @@
addSplitTaskFragments(primaryActivity, thirdActivity);
// Ensure another SplitContainer is added and the pinned TaskFragment still on top
- assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
- assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
- == secondaryActivity);
+ assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1);
+ assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer()
+ .getTopNonFinishingActivity(), secondaryActivity);
}
/** Creates a mock activity in the organizer process. */
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 18796494..fd4522e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -172,4 +172,5 @@
kotlincflags: ["-Xjvm-default=all"],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
+ use_resource_processor: true,
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 3790f04..d08c573 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.back;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -70,7 +71,6 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -317,7 +317,11 @@
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
(controller) -> controller.registerAnimation(
BackNavigationInfo.TYPE_RETURN_TO_HOME,
- new BackAnimationRunner(callback, runner)));
+ new BackAnimationRunner(
+ callback,
+ runner,
+ controller.mContext,
+ CUJ_PREDICTIVE_BACK_HOME)));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 431df21..dc413b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import android.annotation.NonNull;
+import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
@@ -27,16 +28,22 @@
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.common.InteractionJankMonitorUtils;
+
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
* before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
* trigger the real back behavior.
*/
public class BackAnimationRunner {
+ private static final int NO_CUJ = -1;
private static final String TAG = "ShellBackPreview";
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
+ private final @InteractionJankMonitor.CujType int mCujType;
+ private final Context mContext;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
@@ -45,9 +52,21 @@
private boolean mAnimationCancelled;
public BackAnimationRunner(
- @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner) {
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context,
+ @InteractionJankMonitor.CujType int cujType) {
mCallback = callback;
mRunner = runner;
+ mCujType = cujType;
+ mContext = context;
+ }
+
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context) {
+ this(callback, runner, context, NO_CUJ);
}
/** Returns the registered animation runner */
@@ -70,10 +89,17 @@
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() {
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.endTracing(mCujType);
+ }
finishedCallback.run();
}
};
mWaitingAnimation = false;
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.beginTracing(
+ mCujType, mContext, apps[0].leash, /* tag */ null);
+ }
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
nonApps, callback);
@@ -82,6 +108,10 @@
}
}
+ private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+ return apps.length > 0 && mCujType != NO_CUJ;
+ }
+
void startGesture() {
mWaitingAnimation = true;
mAnimationCancelled = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 114486e..24479d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -19,6 +19,7 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -135,7 +136,8 @@
@Inject
public CrossActivityAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mBackground = background;
mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
mEnteringProgressSpring.setSpring(new SpringForce()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 209d853..fc5ff01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -20,6 +20,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.window.BackEvent.EDGE_RIGHT;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_TASK;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -108,6 +109,7 @@
private final float[] mTmpTranslate = {0, 0, 0};
private final BackAnimationRunner mBackAnimationRunner;
private final BackAnimationBackground mBackground;
+ private final Context mContext;
private RemoteAnimationTarget mEnteringTarget;
private RemoteAnimationTarget mClosingTarget;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -120,8 +122,10 @@
@Inject
public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ mContext = context;
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
mBackground = background;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index aca638c..5254ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -19,6 +19,7 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -97,7 +98,8 @@
SurfaceControl.Transaction transaction, Choreographer choreographer) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackground = background;
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mCustomAnimationLoader = new CustomAnimationLoader(context);
mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dddcbd4..f0da35d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -317,7 +317,8 @@
mBubbleIconFactory = new BubbleIconFactory(context,
context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
@@ -949,7 +950,8 @@
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
@@ -988,7 +990,8 @@
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mStackView.onDisplaySizeChanged();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index dc099d9..22e836a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -113,7 +113,7 @@
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(R.color.important_conversation),
+ res.getColor(com.android.launcher3.icons.R.color.important_conversation),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
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 738c94e..79f306e 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 @@
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 @@
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 0000000..2a44f04
--- /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 aad2683..e487328 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 @@
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 @@
};
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 @@
* 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 0000000..f561aa5
--- /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/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
new file mode 100644
index 0000000..18716c6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -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.wm.shell.transition;
+
+import android.window.RemoteTransition;
+import android.window.TransitionFilter;
+
+/**
+ * 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 cc4d268..644a6a5 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 @@
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 @@
* 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 576bba96..baa9aca 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 @@
}
private ExternalInterfaceBinder createExternalInterface() {
- return new IShellTransitionsImpl(this);
+ return new IShellTransitionsImpl(mContext, getMainExecutor(), this);
}
@Override
@@ -1400,9 +1400,12 @@
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 @@
*/
@Override
public void invalidate() {
+ mHomeTransitionObserver.invalidate();
mTransitions = null;
}
@@ -1434,6 +1438,14 @@
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/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 248e837..bb262d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -116,7 +116,8 @@
this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
}
DesktopModeWindowDecoration(
@@ -133,10 +134,12 @@
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
- windowContainerTransactionSupplier, surfaceControlViewHostFactory);
+ windowContainerTransactionSupplier, surfaceControlSupplier,
+ surfaceControlViewHostFactory);
mHandler = handler;
mChoreographer = choreographer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index d0e647b..044c033 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowInsets.Type.statusBars;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -137,7 +138,8 @@
Configuration windowDecorConfig) {
this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
}
WindowDecoration(
@@ -145,17 +147,18 @@
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface,
+ @NonNull SurfaceControl taskSurface,
Configuration windowDecorConfig,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
mTaskInfo = taskInfo;
- mTaskSurface = taskSurface;
+ mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
@@ -453,6 +456,7 @@
public void close() {
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
releaseViews();
+ mTaskSurface.release();
}
static int loadDimensionPixelSize(Resources resources, int resourceId) {
@@ -469,6 +473,13 @@
return resources.getDimension(resourceId);
}
+ private static SurfaceControl cloneSurfaceControl(SurfaceControl sc,
+ Supplier<SurfaceControl> surfaceControlSupplier) {
+ final SurfaceControl copy = surfaceControlSupplier.get();
+ copy.copyFrom(sc, "WindowDecoration");
+ return copy;
+ }
+
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 7f02072..acfb259 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -39,7 +39,25 @@
}
filegroup {
- name: "WMShellFlickerTestsPip-src",
+ name: "WMShellFlickerTestsPip1-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/pip/A*.kt",
+ "src/com/android/wm/shell/flicker/pip/B*.kt",
+ "src/com/android/wm/shell/flicker/pip/C*.kt",
+ "src/com/android/wm/shell/flicker/pip/D*.kt",
+ "src/com/android/wm/shell/flicker/pip/S*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip2-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/pip/E*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip3-src",
srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
}
@@ -176,7 +194,9 @@
],
exclude_srcs: [
":WMShellFlickerTestsBubbles-src",
- ":WMShellFlickerTestsPip-src",
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPip3-src",
":WMShellFlickerTestsPipCommon-src",
":WMShellFlickerTestsPipApps-src",
":WMShellFlickerTestsSplitScreenGroup1-src",
@@ -200,19 +220,49 @@
}
android_test {
- name: "WMShellFlickerTestsPip",
+ name: "WMShellFlickerTestsPip1",
defaults: ["WMShellFlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestPip.xml"],
package_name: "com.android.wm.shell.flicker.pip",
instrumentation_target_package: "com.android.wm.shell.flicker.pip",
srcs: [
":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsPip-src",
+ ":WMShellFlickerTestsPip1-src",
":WMShellFlickerTestsPipCommon-src",
],
}
android_test {
+ name: "WMShellFlickerTestsPip2",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip3",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip3-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPip2-src",
+ ],
+}
+
+android_test {
name: "WMShellFlickerTestsPipApps",
defaults: ["WMShellFlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestPip.xml"],
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 19c8435..94e3959 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -22,6 +22,7 @@
import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -55,7 +56,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
- EnterPipViaAppUiButtonTest(flicker) {
+ EnterPipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 2cd08a4a..5965805 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -26,6 +26,7 @@
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.statusBarLayerPositionAtEnd
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -66,8 +67,10 @@
standardAppHelper.launchViaIntent(
wmHelper,
NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
- ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME,
- NetflixAppHelper.WATCH_ACTIVITY)
+ ComponentNameMatcher(
+ NetflixAppHelper.PACKAGE_NAME,
+ NetflixAppHelper.WATCH_ACTIVITY
+ )
)
standardAppHelper.waitForVideoPlaying()
}
@@ -99,6 +102,41 @@
super.focusChanges()
}
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ // Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.statusBarLayerPositionAtEnd()
+ }
+
+ @Postsubmit
+ @Test override fun statusBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index e7d0f60..d6141cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -568,8 +568,12 @@
}
private void registerAnimation(int type) {
- mController.registerAnimation(type,
- new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+ mController.registerAnimation(
+ type,
+ new BackAnimationRunner(
+ mAnimatorCallback,
+ mBackAnimationRunner,
+ mContext));
}
private void unregisterAnimation(int type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index a2dbab1..18fcdd0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -111,7 +111,8 @@
mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
- WindowContainerTransaction::new, mMockSurfaceControlViewHostFactory);
+ WindowContainerTransaction::new, SurfaceControl::new,
+ mMockSurfaceControlViewHostFactory);
}
private ActivityManager.RunningTaskInfo createTaskInfo(boolean visible) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8061aa3..8e42f74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -121,6 +121,8 @@
private WindowContainerTransaction mMockWindowContainerTransaction;
@Mock
private SurfaceSyncGroup mMockSurfaceSyncGroup;
+ @Mock
+ private SurfaceControl mMockTaskSurface;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
@@ -189,8 +191,7 @@
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -199,7 +200,7 @@
verify(captionContainerSurfaceBuilder, never()).build();
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
- verify(mMockSurfaceControlFinishT).hide(taskSurface);
+ verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
assertNull(mRelayoutResult.mRootView);
}
@@ -229,12 +230,11 @@
taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+ verify(decorContainerSurfaceBuilder).setParent(mMockTaskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
@@ -262,14 +262,15 @@
}
verify(mMockSurfaceControlFinishT)
- .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+ .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
+ TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setWindowCrop(taskSurface, 300, 100);
- verify(mMockSurfaceControlStartT).setCornerRadius(taskSurface, CORNER_RADIUS);
- verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS);
+ .setWindowCrop(mMockTaskSurface, 300, 100);
+ verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
+ verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlStartT)
- .show(taskSurface);
- verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10);
+ .show(mMockTaskSurface);
+ verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
assertEquals(300, mRelayoutResult.mWidth);
assertEquals(100, mRelayoutResult.mHeight);
@@ -308,8 +309,7 @@
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -346,8 +346,7 @@
.setVisible(true)
.build();
- final TestWindowDecoration windowDecor =
- createWindowDecoration(taskInfo, new SurfaceControl());
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
// It shouldn't show the window decoration when it can't obtain the display instance.
@@ -405,8 +404,7 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
@@ -465,8 +463,7 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -506,8 +503,7 @@
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
@@ -545,12 +541,11 @@
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
taskInfo.isFocused = true;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f});
+ verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f});
mockitoSession.finishMocking();
}
@@ -568,8 +563,7 @@
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(true)
.build();
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars())
.isVisible());
@@ -613,12 +607,11 @@
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.build();
taskInfo.isFocused = true;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(mMockSurfaceControlStartT).unsetColor(taskSurface);
+ verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface);
mockitoSession.finishMocking();
}
@@ -639,8 +632,7 @@
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(true)
.build();
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -650,15 +642,15 @@
eq(0) /* index */, eq(mandatorySystemGestures()));
}
- private TestWindowDecoration createWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
+ private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
- taskInfo, testSurface, mWindowConfiguration,
+ taskInfo, mMockTaskSurface, mWindowConfiguration,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
- () -> mMockWindowContainerTransaction, mMockSurfaceControlViewHostFactory);
+ () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
+ mMockSurfaceControlViewHostFactory);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -697,11 +689,12 @@
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
windowConfiguration, surfaceControlBuilderSupplier,
surfaceControlTransactionSupplier, windowContainerTransactionSupplier,
- surfaceControlViewHostFactory);
+ surfaceControlSupplier, surfaceControlViewHostFactory);
}
@Override
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 7f22693..d056248 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -785,7 +785,7 @@
has_locale = true;
}
- // if we don't have a result yet
+ // if we don't have a result yet
if (!final_result ||
// or this config is better before the locale than the existing result
result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
@@ -863,9 +863,12 @@
// We can skip calling ResTable_config::match() if the caller does not care for the
// configuration to match or if we're using the list of types that have already had their
- // configuration matched.
+ // configuration matched. The exception to this is when the user has multiple locales set
+ // because the filtered list will then have values from multiple locales and we will need to
+ // call match() to make sure the current entry matches the config we are currently checking.
const ResTable_config& this_config = type_entry->config;
- if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+ if (!((use_filtered && (configurations_.size() == 1))
+ || ignore_configuration || this_config.match(desired_config))) {
continue;
}
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index e672b98..e986c38 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -27,3 +27,10 @@
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 1042703..814b682 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -32,13 +32,13 @@
, 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 @@
}
// 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 @@
}
// 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 @@
}
// 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 @@
TraceValue sizeValue = sizeResult->second;
mCurrentValues.clear();
mCurrentValues.insert({key, sizeValue});
- mResults.insert({resourceName, mCurrentValues});
+ mResults.insert({*resourceName, mCurrentValues});
}
}
@@ -139,8 +138,9 @@
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 @@
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 @@
size_t SkiaMemoryTracer::total() {
processElement();
- if (!strcmp("bytes", mTotalSize.units)) {
+ if ("bytes" == mTotalSize.units) {
return mTotalSize.value;
}
return 0;
@@ -166,16 +167,16 @@
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 cba3b04..dbfc86b 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 @@
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 @@
// 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/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b00fc69..7fac0c9 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,6 +139,7 @@
mRenderNodes.clear();
mRenderThread.cacheManager().unregisterCanvasContext(this);
mRenderThread.renderState().removeContextCallback(this);
+ mHintSessionWrapper->destroy();
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -561,7 +562,11 @@
void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
- LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
+ if (grContext->isDeviceLost()) {
+ LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
+ return;
+ }
+ LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
return;
}
}
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 1c3399a..2362331 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -158,7 +158,6 @@
void HintSessionWrapper::sendLoadIncreaseHint() {
if (!init()) return;
mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
- mLastFrameNotification = systemTime();
}
bool HintSessionWrapper::alive() {
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index a14ae1c..10a740a1 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -259,6 +259,31 @@
TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+ EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, report an actual duration
+ mWrapper->reportActualWorkDuration(5_ms);
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
EXPECT_CALL(*sMockBinding,
fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
.Times(1);
@@ -272,16 +297,46 @@
// First schedule the deletion
scheduleDelayedDestroyManaged();
- // Then, send a hint to update the timestamp
+ // Then, send a load_up hint
mWrapper->sendLoadIncreaseHint();
// Then, run the delayed deletion after sending the update
allowDelayedDestructionToStart();
waitForDelayedDestructionToFinish();
- // Ensure it didn't close within the timeframe of the test
+ // Ensure it closed within the timeframe of the test
Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, kill the session
+ mWrapper->destroy();
+
+ // Verify it died
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+ // Then, run the delayed deletion after manually killing the session
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close again and is still dead
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
}
} // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/location/api/current.txt b/location/api/current.txt
index 33effdd..0c23d8c 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -412,7 +412,9 @@
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 637f905..32e636f 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 @@
@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 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 @@
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 @@
* 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 @@
* 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 @@
* <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 @@
* 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 0000000..c471a27
--- /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/Android.bp b/media/Android.bp
index f69dd3c..3493408 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -23,6 +23,10 @@
name: "soundtrigger_middleware-aidl",
unstable: true,
local_include_dir: "aidl",
+ defaults: [
+ "latest_android_media_audio_common_types_import_interface",
+ "latest_android_media_soundtrigger_types_import_interface",
+ ],
backend: {
java: {
sdk_version: "module_current",
@@ -32,8 +36,6 @@
"aidl/android/media/soundtrigger_middleware/*.aidl",
],
imports: [
- "android.media.audio.common.types-V2",
- "android.media.soundtrigger.types-V1",
"media_permission-aidl",
],
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 91fa873..cccf6f1 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -18,6 +18,9 @@
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 @@
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 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 @@
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 159427b..f68eb3d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -52,6 +52,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -310,8 +311,11 @@
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());
}
/**
@@ -380,9 +384,9 @@
* @see #setRouteListingPreference(RouteListingPreference)
*/
@FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void registerRouteListingPreferenceCallback(
+ public void registerRouteListingPreferenceUpdatedCallback(
@NonNull @CallbackExecutor Executor executor,
- @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+ @NonNull Consumer<RouteListingPreference> routeListingPreferenceCallback) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(routeListingPreferenceCallback, "callback must not be null");
@@ -395,15 +399,20 @@
/**
* Unregisters the given callback to not receive {@link RouteListingPreference} change events.
+ *
+ * @see #registerRouteListingPreferenceUpdatedCallback(Executor, Consumer)
*/
@FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void unregisterRouteListingPreferenceCallback(
- @NonNull RouteListingPreferenceCallback callback) {
+ public void unregisterRouteListingPreferenceUpdatedCallback(
+ @NonNull Consumer<RouteListingPreference> callback) {
Objects.requireNonNull(callback, "callback must not be null");
if (!mListingPreferenceCallbackRecords.remove(
new RouteListingPreferenceCallbackRecord(/* executor */ null, callback))) {
- Log.w(TAG, "unregisterRouteListingPreferenceCallback: Ignoring an unknown callback");
+ Log.w(
+ TAG,
+ "unregisterRouteListingPreferenceUpdatedCallback: Ignoring an unknown"
+ + " callback");
}
}
@@ -1116,9 +1125,7 @@
private void notifyRouteListingPreferenceUpdated(@Nullable RouteListingPreference preference) {
for (RouteListingPreferenceCallbackRecord record : mListingPreferenceCallbackRecords) {
record.mExecutor.execute(
- () ->
- record.mRouteListingPreferenceCallback.onRouteListingPreferenceChanged(
- preference));
+ () -> record.mRouteListingPreferenceCallback.accept(preference));
}
}
@@ -1205,22 +1212,6 @@
public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
}
- /** Callback for receiving events related to {@link RouteListingPreference}. */
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public abstract static class RouteListingPreferenceCallback {
-
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public RouteListingPreferenceCallback() {}
-
- /**
- * Called when the {@link RouteListingPreference} changes.
- *
- * @see #getRouteListingPreference
- */
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {}
- }
-
/** Callback for receiving events on media transfer. */
public abstract static class TransferCallback {
/**
@@ -1771,11 +1762,11 @@
private static final class RouteListingPreferenceCallbackRecord {
public final Executor mExecutor;
- public final RouteListingPreferenceCallback mRouteListingPreferenceCallback;
+ public final Consumer<RouteListingPreference> mRouteListingPreferenceCallback;
/* package */ RouteListingPreferenceCallbackRecord(
@NonNull Executor executor,
- @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+ @NonNull Consumer<RouteListingPreference> routeListingPreferenceCallback) {
mExecutor = executor;
mRouteListingPreferenceCallback = routeListingPreferenceCallback;
}
@@ -2057,13 +2048,7 @@
@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 +2413,23 @@
}
/**
+ * 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 31e65eb..10c880d 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -176,4 +176,47 @@
@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/OWNERS b/nfc-extras/OWNERS
new file mode 100644
index 0000000..35e9713
--- /dev/null
+++ b/nfc-extras/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
index ffed804..fe8386e 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 @@
*
* 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";
@@ -72,15 +76,40 @@
private final NfcAdapter mAdapter;
final String mPackageName;
+ private static INfcAdapterExtras
+ getNfcAdapterExtrasInterfaceFromNfcAdapter(NfcAdapter adapter) {
+ try {
+ Method method = NfcAdapter.class.getDeclaredMethod("getNfcAdapterExtrasInterface");
+ method.setAccessible(true);
+ return (INfcAdapterExtras) method.invoke(adapter);
+ } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+ | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+ Log.e(TAG, "Unable to get context from NfcAdapter");
+ }
+ return null;
+ }
+
/** get service handles */
private static void initService(NfcAdapter adapter) {
- final INfcAdapterExtras service = adapter.getNfcAdapterExtrasInterface();
+ final INfcAdapterExtras service = getNfcAdapterExtrasInterfaceFromNfcAdapter(adapter);
if (service != null) {
// Leave stale rather than receive a null value.
sService = service;
}
}
+ 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 +120,7 @@
* @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 +141,7 @@
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 +185,24 @@
}
}
+ 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/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 30d1773..4e4894c 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -27,7 +27,7 @@
<string name="passkey_creation_intro_title" msgid="4251037543787718844">"Geçiş anahtarlarıyla daha yüksek güvenlik"</string>
<string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Geçiş anahtarı kullandığınızda karmaşık şifreler oluşturmanız veya bunları hatırlamanız gerekmez"</string>
<string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Geçiş anahtarları; parmak iziniz, yüzünüz veya ekran kilidinizi kullanarak oluşturduğunuz şifrelenmiş dijital anahtarlardır"</string>
- <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için şifre anahtarları bir şifre yöneticisine kaydedilir"</string>
+ <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için geçiş anahtarları bir şifre yöneticisine kaydedilir"</string>
<string name="more_about_passkeys_title" msgid="7797903098728837795">"Geçiş anahtarları hakkında daha fazla bilgi"</string>
<string name="passwordless_technology_title" msgid="2497513482056606668">"Şifresiz teknoloji"</string>
<string name="passwordless_technology_detail" msgid="6853928846532955882">"Geçiş anahtarları, şifre kullanmadan oturum açmanıza olanak tanır. Kimliğinizi doğrulayıp geçiş anahtarı oluşturmak için parmak iziniz, yüz tanıma özelliği, PIN veya kaydırma deseni kullanmanız yeterlidir."</string>
@@ -39,10 +39,10 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Şifresiz bir geleceğe doğru ilerlerken şifreler, geçiş anahtarlarıyla birlikte kullanılmaya devam edecektir."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> kaydedileceği yeri seçin"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Bilgilerinizi kaydedip bir dahaki sefere daha hızlı oturum açmak için bir şifre yöneticisi seçin"</string>
- <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre anahtarı oluşturulsun mu?"</string>
+ <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için geçiş anahtarı oluşturulsun mu?"</string>
<string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre kaydedilsin mi?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> için oturum açma bilgileri kaydedilsin mi?"</string>
- <string name="passkey" msgid="632353688396759522">"Şifre anahtarı"</string>
+ <string name="passkey" msgid="632353688396759522">"Geçiş anahtarı"</string>
<string name="password" msgid="6738570945182936667">"Şifre"</string>
<string name="passkeys" msgid="5733880786866559847">"Geçiş anahtarlarınızın"</string>
<string name="passwords" msgid="5419394230391253816">"şifreler"</string>
@@ -61,14 +61,14 @@
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> geçiş anahtarı"</string>
<string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> kimlik bilgileri"</string>
- <string name="passkey_before_subtitle" msgid="2448119456208647444">"Şifre anahtarı"</string>
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Geçiş anahtarı"</string>
<string name="another_device" msgid="5147276802037801217">"Başka bir cihaz"</string>
<string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string>
<string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Önceki sayfaya geri dön"</string>
<string name="accessibility_close_button" msgid="1163435587545377687">"Kapat"</string>
<string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Kapat"</string>
- <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifre anahtarınız kullanılsın mı?"</string>
+ <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı geçiş anahtarınız kullanılsın mı?"</string>
<string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifreniz kullanılsın mı?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma bilgileriniz kullanılsın mı?"</string>
<string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma seçeneklerine izin verilsin mi?"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 222b865..495abe6 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -53,7 +53,7 @@
<string name="save_password_on_other_device_title" msgid="5829084591948321207">"在其他设备上保存密码?"</string>
<string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"在其他设备上保存登录凭据?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
- <string name="use_provider_for_all_description" msgid="1998772715863958997">"此 <xliff:g id="USERNAME">%1$s</xliff:g> 密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
+ <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> 的这个密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
<string name="set_as_default" msgid="4415328591568654603">"设为默认项"</string>
<string name="settings" msgid="6536394145760913145">"设置"</string>
<string name="use_once" msgid="9027366575315399714">"使用一次"</string>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 9355517..d35dcb5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -43,6 +43,9 @@
import android.widget.inline.InlinePresentationSpec
import androidx.autofill.inline.v1.InlineSuggestionUi
import com.android.credentialmanager.GetFlowUtils
+import com.android.credentialmanager.getflow.CredentialEntryInfo
+import com.android.credentialmanager.getflow.ProviderDisplayInfo
+import com.android.credentialmanager.getflow.toProviderDisplayInfo
import org.json.JSONObject
import java.util.concurrent.Executors
@@ -114,10 +117,14 @@
val providerList = GetFlowUtils.toProviderList(
getCredResponse.candidateProviderDataList,
this@CredentialAutofillService)
+ if (providerList.isEmpty()) {
+ return null
+ }
var totalEntryCount = 0
providerList.forEach { provider ->
totalEntryCount += provider.credentialEntryList.size
}
+ val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerList)
val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
@@ -129,15 +136,30 @@
var i = 0
val fillResponseBuilder = FillResponse.Builder()
var emptyFillResponse = true
- providerList.forEach {provider ->
- // TODO(b/299321128): Before iterating the list, sort the list so that
- // the relevant entries don't get truncated
- provider.credentialEntryList.forEach entryLoop@ {entry ->
- val autofillId: AutofillId? = entry.fillInIntent?.getParcelableExtra(
- CredentialProviderService.EXTRA_AUTOFILL_ID,
- AutofillId::class.java)
- val pendingIntent = entry.pendingIntent
+
+ providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ {
+ val primaryEntry = it.sortedCredentialEntryList.first()
+ // In regular CredMan bottomsheet, only one primary entry per username is displayed.
+ // But since the credential requests from different fields are allocated into a single
+ // request for autofill, there will be duplicate primary entries, especially for
+ // username/pw autofill fields. These primary entries will be the same entries except
+ // their autofillIds will point to different autofill fields. Process all primary
+ // fields.
+ // TODO(b/307435163): Merge credential options
+ it.sortedCredentialEntryList.forEach entryLoop@ { credentialEntry ->
+ if (!isSameCredentialEntry(primaryEntry, credentialEntry)) {
+ // Encountering different credential entry means all the duplicate primary
+ // entries have been processed.
+ return@usernameLoop
+ }
+ val autofillId: AutofillId? = credentialEntry
+ .fillInIntent
+ ?.getParcelableExtra(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ AutofillId::class.java)
+ val pendingIntent = credentialEntry.pendingIntent
if (autofillId == null || pendingIntent == null) {
+ Log.e(TAG, "AutofillId or pendingIntent was missing from the entry.")
return@entryLoop
}
var inlinePresentation: InlinePresentation? = null
@@ -151,7 +173,7 @@
}
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
- .setTitle(entry.userName)
+ .setTitle(credentialEntry.userName)
inlinePresentation = InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ false)
}
@@ -169,8 +191,8 @@
Field.Builder().setPresentations(
presentationBuilder.build())
.build())
- .setAuthentication(entry.pendingIntent.intentSender)
- .setAuthenticationExtras(entry.fillInIntent.extras)
+ .setAuthentication(pendingIntent.intentSender)
+ .setAuthenticationExtras(credentialEntry.fillInIntent.extras)
.build())
emptyFillResponse = false
}
@@ -292,4 +314,15 @@
}
return result
}
+
+ private fun isSameCredentialEntry(
+ info1: CredentialEntryInfo,
+ info2: CredentialEntryInfo
+ ): Boolean {
+ return info1.providerId == info2.providerId &&
+ info1.lastUsedTimeMillis == info2.lastUsedTimeMillis &&
+ info1.credentialType == info2.credentialType &&
+ info1.displayName == info2.displayName &&
+ info1.userName == info2.userName
+ }
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 716f474..447a9d2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -203,9 +203,14 @@
UNLOCKED_AUTH_ENTRIES_ONLY,
}
-// IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
-// entry exists
-private fun toProviderDisplayInfo(
+
+/**
+ * IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
+ * entry exists
+ *
+ * @hide
+ */
+fun toProviderDisplayInfo(
providerInfoList: List<ProviderInfo>
): ProviderDisplayInfo {
val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
index 8975857..47ce587 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/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index f1e028b..6979825 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -25,6 +25,7 @@
<item name="editTextPreferenceStyle">@style/SettingsEditTextPreference.SettingsLib</item>
<item name="dropdownPreferenceStyle">@style/SettingsDropdownPreference.SettingsLib</item>
<item name="switchPreferenceStyle">@style/SettingsSwitchPreference.SettingsLib</item>
+ <item name="switchPreferenceCompatStyle">@style/SettingsSwitchPreferenceCompat.SettingsLib</item>
<item name="seekBarPreferenceStyle">@style/SettingsSeekbarPreference.SettingsLib</item>
<item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
@@ -67,6 +68,11 @@
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
</style>
+ <style name="SettingsSwitchPreferenceCompat.SettingsLib" parent="@style/Preference.SwitchPreferenceCompat.Material">
+ <item name="layout">@layout/settingslib_preference</item>
+ <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
+ </style>
+
<style name="SettingsSeekbarPreference.SettingsLib" parent="@style/Preference.SeekBarPreference.Material">
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
</style>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index c4b6047..f44b161 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -34,12 +34,17 @@
</style>
<style name="Switch.SettingsLib" parent="@android:style/Widget.Material.CompoundButton.Switch">
- <item name="android:switchMinWidth">52dp</item>
<item name="android:minHeight">@dimen/settingslib_preferred_minimum_touch_target</item>
<item name="android:track">@drawable/settingslib_switch_track</item>
<item name="android:thumb">@drawable/settingslib_switch_thumb</item>
</style>
+ <style name="SwitchCompat.SettingsLib" parent="@style/Widget.AppCompat.CompoundButton.Switch">
+ <item name="android:minHeight">@dimen/settingslib_preferred_minimum_touch_target</item>
+ <item name="track">@drawable/settingslib_switch_track</item>
+ <item name="android:thumb">@drawable/settingslib_switch_thumb</item>
+ </style>
+
<style name="HorizontalProgressBar.SettingsLib"
parent="android:style/Widget.Material.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/settingslib_progress_horizontal</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 69c122c..698f21d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -25,6 +25,7 @@
<item name="android:listPreferredItemPaddingRight">16dp</item>
<item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item>
<item name="android:switchStyle">@style/Switch.SettingsLib</item>
+ <item name="switchStyle">@style/SwitchCompat.SettingsLib</item>
<item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
</style>
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index eaeda3c..009407a 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -38,6 +38,7 @@
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 a428142..d95dd8c 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 @@
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 @@
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 517f67e..840bca8 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.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 @@
}
@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 @@
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/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index afdb92b..9d986f4 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Kies profiel"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoonlik"</string>
<string name="category_work" msgid="4014193632325996115">"Werk"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privaat"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Ontwikkelaaropsies"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktiveer ontwikkelaaropsies"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index ea8491b..b4887b9 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"መገለጫ ይምረጡ"</string>
<string name="category_personal" msgid="6236798763159385225">"የግል"</string>
<string name="category_work" msgid="4014193632325996115">"ሥራ"</string>
+ <string name="category_private" msgid="4244892185452788977">"የግል"</string>
<string name="category_clone" msgid="1554511758987195974">"አባዛ"</string>
<string name="development_settings_title" msgid="140296922921597393">"የገንቢዎች አማራጮች"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"የገንቢዎች አማራጮችን አንቃ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index c29e691..b8f92a3 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"اختيار ملف شخصي"</string>
<string name="category_personal" msgid="6236798763159385225">"التطبيقات الشخصية"</string>
<string name="category_work" msgid="4014193632325996115">"تطبيقات العمل"</string>
+ <string name="category_private" msgid="4244892185452788977">"ملف شخصي"</string>
<string name="category_clone" msgid="1554511758987195974">"استنساخ"</string>
<string name="development_settings_title" msgid="140296922921597393">"خيارات المطورين"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"تفعيل خيارات المطورين"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 8eff4f6..86b29c6 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"প্ৰ’ফাইল বাছনি কৰক"</string>
<string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string>
<string name="category_work" msgid="4014193632325996115">"কৰ্মস্থান-সম্পৰ্কীয়"</string>
+ <string name="category_private" msgid="4244892185452788977">"ব্যক্তিগত"</string>
<string name="category_clone" msgid="1554511758987195974">"ক্ল’ন"</string>
<string name="development_settings_title" msgid="140296922921597393">"বিকাশকৰ্তাৰ বিকল্পসমূহ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"বিকাশকৰ্তা বিষয়ক বিকল্পসমূহ সক্ষম কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index dfd1b7b..6a08a25 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string>
<string name="category_personal" msgid="6236798763159385225">"Şəxsi"</string>
<string name="category_work" msgid="4014193632325996115">"İş"</string>
+ <string name="category_private" msgid="4244892185452788977">"Şəxsi"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonlayın"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer seçimləri"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Developer variantlarını aktiv edin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 528e96e..ad829b9 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Izaberite profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Lično"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonirano"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za programere"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 52614b7..7cd748c 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Выбраць профіль"</string>
<string name="category_personal" msgid="6236798763159385225">"Асабісты"</string>
<string name="category_work" msgid="4014193632325996115">"Працоўны"</string>
+ <string name="category_private" msgid="4244892185452788977">"Прыватныя"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Параметры распрацоўшчыка"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Уключыць параметры распрацоўшчыка"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index b28ae67..93fe481 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Избор на потребителски профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лични"</string>
<string name="category_work" msgid="4014193632325996115">"Служебни"</string>
+ <string name="category_private" msgid="4244892185452788977">"Частен"</string>
<string name="category_clone" msgid="1554511758987195974">"Клониране"</string>
<string name="development_settings_title" msgid="140296922921597393">"Опции за програмисти"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Активиране на опциите за програмисти"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index c91f14c..efdf304 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"প্রোফাইল বেছে নিন"</string>
<string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string>
<string name="category_work" msgid="4014193632325996115">"অফিস"</string>
+ <string name="category_private" msgid="4244892185452788977">"ব্যক্তিগত"</string>
<string name="category_clone" msgid="1554511758987195974">"ক্লোন"</string>
<string name="development_settings_title" msgid="140296922921597393">"ডেভেলপার বিকল্প"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ডেভেলপার বিকল্প সক্ষম করুন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index aa1073d..5d21dea 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Odaberite profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Lično"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonirajte"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za programere"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index ab6393a7..ea4a2fb 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Tria un perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Treball"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Clona"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcions per a desenvolupadors"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activa les opcions per a desenvolupadors"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index e7d5243..648e8dd 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Vyberte profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobní"</string>
<string name="category_work" msgid="4014193632325996115">"Pracovní"</string>
+ <string name="category_private" msgid="4244892185452788977">"Soukromé"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pro vývojáře"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivovat možnosti pro vývojáře"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 1612ac0..500bfc3 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Vælg profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
<string name="category_work" msgid="4014193632325996115">"Arbejde"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Indstillinger for udviklere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivér indstillinger for udviklere"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 73a44f1..df392f3 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -216,6 +216,8 @@
<string name="choose_profile" msgid="343803890897657450">"Profil auswählen"</string>
<string name="category_personal" msgid="6236798763159385225">"Privat"</string>
<string name="category_work" msgid="4014193632325996115">"Geschäftlich"</string>
+ <!-- no translation found for category_private (4244892185452788977) -->
+ <skip />
<string name="category_clone" msgid="1554511758987195974">"Klonen"</string>
<string name="development_settings_title" msgid="140296922921597393">"Entwickleroptionen"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Entwickleroptionen aktivieren"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 7dfd679..a87c0d7 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Επιλογή προφίλ"</string>
<string name="category_personal" msgid="6236798763159385225">"Προσωπικό"</string>
<string name="category_work" msgid="4014193632325996115">"Εργασίας"</string>
+ <string name="category_private" msgid="4244892185452788977">"Ιδιωτικό"</string>
<string name="category_clone" msgid="1554511758987195974">"Κλωνοποίηση"</string>
<string name="development_settings_title" msgid="140296922921597393">"Επιλογές για προγραμματιστές"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ενεργοποίηση επιλογών για προγραμματιστές"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index aead40d..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 46dd47d..11a39b2 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index aead40d..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index aead40d..5193f9b 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index a7c327f76..8a32195 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 291a68b..4276f59 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Elegir perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabajo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opciones para desarrolladores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activar opciones para programador"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index b4bc319..8b7b211 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Seleccionar perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabajo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opciones para desarrolladores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Habilitar opciones para desarrolladores"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index e5cbaf6..9c46b6c 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profiili valimine"</string>
<string name="category_personal" msgid="6236798763159385225">"Isiklik"</string>
<string name="category_work" msgid="4014193632325996115">"Töö"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privaatne"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloonimine"</string>
<string name="development_settings_title" msgid="140296922921597393">"Arendaja valikud"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Arendaja valikute lubamine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 90d45eb..4436d52 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Aukeratu profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Pertsonalak"</string>
<string name="category_work" msgid="4014193632325996115">"Lanekoak"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribatua"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonatu"</string>
<string name="development_settings_title" msgid="140296922921597393">"Garatzaileentzako aukerak"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Gaitu garatzaileen aukerak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index f3f97c4..7ae6b7b 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"انتخاب نمایه"</string>
<string name="category_personal" msgid="6236798763159385225">"شخصی"</string>
<string name="category_work" msgid="4014193632325996115">"کاری"</string>
+ <string name="category_private" msgid="4244892185452788977">"خصوصی"</string>
<string name="category_clone" msgid="1554511758987195974">"مشابهسازی"</string>
<string name="development_settings_title" msgid="140296922921597393">"گزینههای برنامهنویسان"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"فعال کردن گزینههای برنامهنویس"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 7f3d0f2..6822d08 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string>
<string name="category_personal" msgid="6236798763159385225">"Henkilökohtainen"</string>
<string name="category_work" msgid="4014193632325996115">"Työ"</string>
+ <string name="category_private" msgid="4244892185452788977">"Yksityinen"</string>
<string name="category_clone" msgid="1554511758987195974">"Klooni"</string>
<string name="development_settings_title" msgid="140296922921597393">"Kehittäjäasetukset"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ota kehittäjäasetukset käyttöön"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index b7c3a81..12bc78e 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Sélectionnez un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personnel"</string>
<string name="category_work" msgid="4014193632325996115">"Professionnel"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privés"</string>
<string name="category_clone" msgid="1554511758987195974">"Cloner"</string>
<string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index a9e0c6a..527b473 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Sélectionner un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Perso"</string>
<string name="category_work" msgid="4014193632325996115">"Pro"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privé"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 09c2969..00d7b6e 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escoller perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoal"</string>
<string name="category_work" msgid="4014193632325996115">"Traballo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcións para programadores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activar opcións para programadores"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 575c675..b0819fb 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"પ્રોફાઇલ પસંદ કરો"</string>
<string name="category_personal" msgid="6236798763159385225">"વ્યક્તિગત"</string>
<string name="category_work" msgid="4014193632325996115">"ઑફિસ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ખાનગી"</string>
<string name="category_clone" msgid="1554511758987195974">"ક્લોન કરો"</string>
<string name="development_settings_title" msgid="140296922921597393">"ડેવલપરના વિકલ્પો"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"વિકાસકર્તાનાં વિકલ્પો સક્ષમ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 655fac0..99572f3 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफ़ाइल चुनें"</string>
<string name="category_personal" msgid="6236798763159385225">"निजी"</string>
<string name="category_work" msgid="4014193632325996115">"वर्क"</string>
+ <string name="category_private" msgid="4244892185452788977">"निजी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन"</string>
<string name="development_settings_title" msgid="140296922921597393">"डेवलपर के लिए सेटिंग और टूल"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"डेवलपर के लिए सेटिंग और टूल चालू करें"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index f0e16a4..735d3e9 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Odabir profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobno"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloniranje"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za razvojne programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za razvojne programere"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 420e0e9..70b7d581 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil kiválasztása"</string>
<string name="category_personal" msgid="6236798763159385225">"Személyes"</string>
<string name="category_work" msgid="4014193632325996115">"Munkahelyi"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privát"</string>
<string name="category_clone" msgid="1554511758987195974">"Klónozás"</string>
<string name="development_settings_title" msgid="140296922921597393">"Fejlesztői beállítások"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Fejlesztői beállítások engedélyezése"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 705fcf6..9340a3b 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Ընտրեք պրոֆիլ"</string>
<string name="category_personal" msgid="6236798763159385225">"Անձնական"</string>
<string name="category_work" msgid="4014193632325996115">"Աշխատանքային"</string>
+ <string name="category_private" msgid="4244892185452788977">"Անձնական"</string>
<string name="category_clone" msgid="1554511758987195974">"Կլոն"</string>
<string name="development_settings_title" msgid="140296922921597393">"Մշակողի ընտրանքներ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Միացնել մշակողի ընտրանքները"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index e33dd05..8cb3dca 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pribadi"</string>
<string name="category_work" msgid="4014193632325996115">"Kerja"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribadi"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opsi developer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktifkan opsi developer"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index e1fb248..2f8ee7e 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Veldu snið"</string>
<string name="category_personal" msgid="6236798763159385225">"Persónulegt"</string>
<string name="category_work" msgid="4014193632325996115">"Vinna"</string>
+ <string name="category_private" msgid="4244892185452788977">"Lokað"</string>
<string name="category_clone" msgid="1554511758987195974">"Afrit"</string>
<string name="development_settings_title" msgid="140296922921597393">"Forritunarkostir"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Virkja valkosti þróunaraðila"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index b2cfd37..9fe9f30 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Scegli profilo"</string>
<string name="category_personal" msgid="6236798763159385225">"Personale"</string>
<string name="category_work" msgid="4014193632325996115">"Lavoro"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privato"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opzioni sviluppatore"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Attiva Opzioni sviluppatore"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 9ca7477..56ce9649 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"בחירת פרופיל"</string>
<string name="category_personal" msgid="6236798763159385225">"אישי"</string>
<string name="category_work" msgid="4014193632325996115">"עבודה"</string>
+ <string name="category_private" msgid="4244892185452788977">"פרטי"</string>
<string name="category_clone" msgid="1554511758987195974">"שכפול"</string>
<string name="development_settings_title" msgid="140296922921597393">"אפשרויות למפתחים"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"הפעלת אפשרויות למפתחים"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index dcce2b4..50f57ef 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"プロファイルの選択"</string>
<string name="category_personal" msgid="6236798763159385225">"個人用"</string>
<string name="category_work" msgid="4014193632325996115">"仕事用"</string>
+ <string name="category_private" msgid="4244892185452788977">"非公開"</string>
<string name="category_clone" msgid="1554511758987195974">"クローン"</string>
<string name="development_settings_title" msgid="140296922921597393">"開発者向けオプション"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"開発者向けオプションの有効化"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index eb32e1c..46e406d 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"აირჩიეთ პროფილი"</string>
<string name="category_personal" msgid="6236798763159385225">"პირადი"</string>
<string name="category_work" msgid="4014193632325996115">"სამსახური"</string>
+ <string name="category_private" msgid="4244892185452788977">"პირადი"</string>
<string name="category_clone" msgid="1554511758987195974">"კლონის შექმნა"</string>
<string name="development_settings_title" msgid="140296922921597393">"პარამეტრები დეველოპერებისთვის"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"დეველოპერთა პარამეტრების ჩართვა"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 73a8efd..42c8ca7 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профильді таңдау"</string>
<string name="category_personal" msgid="6236798763159385225">"Жеке"</string>
<string name="category_work" msgid="4014193632325996115">"Жұмыс"</string>
+ <string name="category_private" msgid="4244892185452788977">"Жеке"</string>
<string name="category_clone" msgid="1554511758987195974">"Клондау"</string>
<string name="development_settings_title" msgid="140296922921597393">"Әзірлеуші опциялары"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Әзірлеуші параметрлерін қосу"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 1c32e62..9503049 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ជ្រើសរើសកម្រងព័ត៌មាន"</string>
<string name="category_personal" msgid="6236798763159385225">"ផ្ទាល់ខ្លួន"</string>
<string name="category_work" msgid="4014193632325996115">"ការងារ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ឯកជន"</string>
<string name="category_clone" msgid="1554511758987195974">"ក្លូន"</string>
<string name="development_settings_title" msgid="140296922921597393">"ជម្រើសសម្រាប់អ្នកអភិវឌ្ឍន៍"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"បើកដំណើរការជម្រើសអ្នកអភិវឌ្ឍន៍"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ede347d..f9ec91f 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ಪ್ರೊಫೈಲ್ ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="category_personal" msgid="6236798763159385225">"ವೈಯಕ್ತಿಕ"</string>
<string name="category_work" msgid="4014193632325996115">"ಕೆಲಸ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ಖಾಸಗಿ"</string>
<string name="category_clone" msgid="1554511758987195974">"ಕ್ಲೋನ್"</string>
<string name="development_settings_title" msgid="140296922921597393">"ಡೆವಲಪರ್ ಆಯ್ಕೆಗಳು"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ಡೆವಲಪರ್ ಆಯ್ಕೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 78bd616..a180880 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"프로필 선택"</string>
<string name="category_personal" msgid="6236798763159385225">"개인"</string>
<string name="category_work" msgid="4014193632325996115">"직장"</string>
+ <string name="category_private" msgid="4244892185452788977">"비공개"</string>
<string name="category_clone" msgid="1554511758987195974">"복사"</string>
<string name="development_settings_title" msgid="140296922921597393">"개발자 옵션"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"개발자 옵션 사용"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index a0b9123..b47356b 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профиль тандоо"</string>
<string name="category_personal" msgid="6236798763159385225">"Жеке"</string>
<string name="category_work" msgid="4014193632325996115">"Жумуш"</string>
+ <string name="category_private" msgid="4244892185452788977">"Купуя"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Иштеп чыгуучунун параметрлери"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Иштеп чыгуучунун параметрлерин иштетүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 96b2dc1..228eebe 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ເລືອກໂປຣໄຟລ໌"</string>
<string name="category_personal" msgid="6236798763159385225">"ສ່ວນໂຕ"</string>
<string name="category_work" msgid="4014193632325996115">"ບ່ອນເຮັດວຽກ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ສ່ວນຕົວ"</string>
<string name="category_clone" msgid="1554511758987195974">"ໂຄລນ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ຕົວເລືອກນັກພັດທະນາ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ເປີດໃຊ້ຕົວເລືອກນັກພັດທະນາ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 69630f4..5dfd2f4 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profilio pasirinkimas"</string>
<string name="category_personal" msgid="6236798763159385225">"Asmeninės"</string>
<string name="category_work" msgid="4014193632325996115">"Darbo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatus"</string>
<string name="category_clone" msgid="1554511758987195974">"Identiška kopija"</string>
<string name="development_settings_title" msgid="140296922921597393">"Kūrėjo parinktys"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Įgalinti kūrėjo parinktis"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 4f76cee..8b55355 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profila izvēlēšanās"</string>
<string name="category_personal" msgid="6236798763159385225">"Privāts"</string>
<string name="category_work" msgid="4014193632325996115">"Darba"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privāti"</string>
<string name="category_clone" msgid="1554511758987195974">"Klons"</string>
<string name="development_settings_title" msgid="140296922921597393">"Izstrādātāju opcijas"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Izstrādātāju opciju iespējošana"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 1c80c65..72fea47 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Изберете профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лични"</string>
<string name="category_work" msgid="4014193632325996115">"Работа"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватен"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Програмерски опции"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Овозможете ги програмерските опции"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 31e3c83..6308083f 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക"</string>
<string name="category_personal" msgid="6236798763159385225">"വ്യക്തിപരം"</string>
<string name="category_work" msgid="4014193632325996115">"ഔദ്യോഗികം"</string>
+ <string name="category_private" msgid="4244892185452788977">"സ്വകാര്യം"</string>
<string name="category_clone" msgid="1554511758987195974">"ക്ലോൺ ചെയ്യുക"</string>
<string name="development_settings_title" msgid="140296922921597393">"ഡെവലപ്പർ ഓപ്ഷനുകൾ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ഡെവലപ്പർ ഓപ്ഷനുകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 5a636d9..8707f40 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профайл сонгох"</string>
<string name="category_personal" msgid="6236798763159385225">"Хувийн"</string>
<string name="category_work" msgid="4014193632325996115">"Ажил"</string>
+ <string name="category_private" msgid="4244892185452788977">"Хувийн"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Хөгжүүлэгчийн тохиргоо"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Хөгжүүлэгчийн сонголтыг идэвхжүүлэх"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 4d78e57..ab8b88c 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफाइल निवडा"</string>
<string name="category_personal" msgid="6236798763159385225">"वैयक्तिक"</string>
<string name="category_work" msgid="4014193632325996115">"कार्य"</string>
+ <string name="category_private" msgid="4244892185452788977">"खाजगी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन करा"</string>
<string name="development_settings_title" msgid="140296922921597393">"डेव्हलपर पर्याय"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"डेव्हलपर पर्याय सुरू करा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 2ae69bd..2841c74 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Peribadi"</string>
<string name="category_work" msgid="4014193632325996115">"Tempat Kerja"</string>
+ <string name="category_private" msgid="4244892185452788977">"Peribadi"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pilihan pembangun"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Dayakan pilihan pembangun"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 7d6f2a7..d915370 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ပရိုဖိုင်ကို ရွေးရန်"</string>
<string name="category_personal" msgid="6236798763159385225">"ကိုယ်ပိုင်"</string>
<string name="category_work" msgid="4014193632325996115">"အလုပ်"</string>
+ <string name="category_private" msgid="4244892185452788977">"သီးသန့်"</string>
<string name="category_clone" msgid="1554511758987195974">"ပုံတူပွားရန်"</string>
<string name="development_settings_title" msgid="140296922921597393">"ဆော့ဝဲလ်ရေးသူ ရွေးစရာများ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ဆော့ဖ်ဝဲရေးသူအတွက် ရွေးစရာများကို ဖွင့်ပါ"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9e550fb..0a63c4b 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Velg profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
<string name="category_work" msgid="4014193632325996115">"Jobb"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Utvikleralternativer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Slå på utvikleralternativer"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 1f6ad5b..206444d 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफाइल रोज्नुहोस्"</string>
<string name="category_personal" msgid="6236798763159385225">"व्यक्तिगत"</string>
<string name="category_work" msgid="4014193632325996115">"काम"</string>
+ <string name="category_private" msgid="4244892185452788977">"निजी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन"</string>
<string name="development_settings_title" msgid="140296922921597393">"विकासकर्ताका विकल्पहरू"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"विकासकर्ता विकल्प सक्रिया गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 6a6f184..af4797c9 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profiel kiezen"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoonlijk"</string>
<string name="category_work" msgid="4014193632325996115">"Werk"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privé"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonen"</string>
<string name="development_settings_title" msgid="140296922921597393">"Ontwikkelaarsopties"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Opties voor ontwikkelaars aanzetten"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 980a374..bbdafc8 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ପ୍ରୋଫାଇଲ୍ ବାଛନ୍ତୁ"</string>
<string name="category_personal" msgid="6236798763159385225">"ବ୍ୟକ୍ତିଗତ"</string>
<string name="category_work" msgid="4014193632325996115">"ୱାର୍କ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ପ୍ରାଇଭେଟ"</string>
<string name="category_clone" msgid="1554511758987195974">"କ୍ଲୋନ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ଡେଭଲପରଙ୍କ ପାଇଁ ବିକଳ୍ପଗୁଡ଼ିକ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ଡେଭଲପର୍ ବିକଳ୍ପଗୁଡ଼ିକ ସକ୍ଷମ କରନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index dd3fa15..59eec15 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ"</string>
<string name="category_personal" msgid="6236798763159385225">"ਨਿੱਜੀ"</string>
<string name="category_work" msgid="4014193632325996115">"ਕੰਮ ਸੰਬੰਧੀ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ਨਿੱਜੀ"</string>
<string name="category_clone" msgid="1554511758987195974">"ਕਲੋਨ ਕਰੋ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ਵਿਕਾਸਕਾਰ ਚੋਣਾਂ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ਵਿਕਾਸਕਾਰ ਵਿਕਲਪਾਂ ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f4ebd29..d5d37f0 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Wybierz profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobiste"</string>
<string name="category_work" msgid="4014193632325996115">"Służbowe"</string>
+ <string name="category_private" msgid="4244892185452788977">"Prywatne"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcje programisty"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Włącz opcje programisty"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index bb6c7d8..3b0010f28 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Particular"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções do desenvolvedor"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar opções do desenvolvedor"</string>
@@ -231,7 +232,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuração quando a rede Wi‑Fi estiver conectada"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Erro"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração por Wi-Fi"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para acessar e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parear o dispositivo com um código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parear novos dispositivos usando um leitor de código QR"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parear o dispositivo com um código de pareamento"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 6bd5c2a..c817f88 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções de programador"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar as opções de programador"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index bb6c7d8..3b0010f28 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Particular"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções do desenvolvedor"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar opções do desenvolvedor"</string>
@@ -231,7 +232,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuração quando a rede Wi‑Fi estiver conectada"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Erro"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração por Wi-Fi"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para acessar e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parear o dispositivo com um código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parear novos dispositivos usando um leitor de código QR"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parear o dispositivo com um código de pareamento"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index f4399dc..5d855a5 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Alege un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Serviciu"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonează"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opțiuni pentru dezvoltatori"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activează opțiunile pentru dezvoltatori"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 5d45e29..9dac1d3 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Выбор профиля"</string>
<string name="category_personal" msgid="6236798763159385225">"Личный профиль"</string>
<string name="category_work" msgid="4014193632325996115">"Рабочий профиль"</string>
+ <string name="category_private" msgid="4244892185452788977">"Личный профиль"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Для разработчиков"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Включить параметры для разработчиков"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 996507d..4b36694 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"පැතිකඩ තෝරන්න"</string>
<string name="category_personal" msgid="6236798763159385225">"පෞද්ගලික"</string>
<string name="category_work" msgid="4014193632325996115">"කාර්යාලය"</string>
+ <string name="category_private" msgid="4244892185452788977">"පෞද්ගලික"</string>
<string name="category_clone" msgid="1554511758987195974">"ක්ලෝන කරන්න"</string>
<string name="development_settings_title" msgid="140296922921597393">"වර්ධක විකල්ප"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"සංවර්ධක විකල්ප සබල කිරීම"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index a51acd4..30cc9fd 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Výber profilu"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobné"</string>
<string name="category_work" msgid="4014193632325996115">"Pracovné"</string>
+ <string name="category_private" msgid="4244892185452788977">"Súkromné"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonovanie"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pre vývojárov"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Povolenie možností vývojára"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 0d7188d..127c00d 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Izbira profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Osebno"</string>
<string name="category_work" msgid="4014193632325996115">"Delo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Zasebno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Možnosti za razvijalce"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogočanje možnosti za razvijalce"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c2bcce7..302e915 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Zgjidh profilin"</string>
<string name="category_personal" msgid="6236798763159385225">"Personale"</string>
<string name="category_work" msgid="4014193632325996115">"Punë"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Klono"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opsionet e zhvilluesit"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivizo opsionet e zhvilluesit"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 09ff994..4d4ae0b 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Изаберите профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лично"</string>
<string name="category_work" msgid="4014193632325996115">"Посао"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватно"</string>
<string name="category_clone" msgid="1554511758987195974">"Клонирано"</string>
<string name="development_settings_title" msgid="140296922921597393">"Опције за програмере"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Омогући опције за програмере"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index fdd9d8c..8fc6af6 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Välj profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Privat"</string>
<string name="category_work" msgid="4014193632325996115">"Jobb"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Utvecklaralternativ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivera utvecklaralternativ"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index bd41752..f6cb654 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Chagua wasifu"</string>
<string name="category_personal" msgid="6236798763159385225">"Binafsi"</string>
<string name="category_work" msgid="4014193632325996115">"Kazini"</string>
+ <string name="category_private" msgid="4244892185452788977">"Faragha"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloni"</string>
<string name="development_settings_title" msgid="140296922921597393">"Chaguo za wasanidi"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Washa chaguo za wasanidi programu"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index fd379f0..41788bf 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"சுயவிவரத்தைத் தேர்வு செய்க"</string>
<string name="category_personal" msgid="6236798763159385225">"தனிப்பட்டவை"</string>
<string name="category_work" msgid="4014193632325996115">"பணியிடம்"</string>
+ <string name="category_private" msgid="4244892185452788977">"தனிப்பட்டவை"</string>
<string name="category_clone" msgid="1554511758987195974">"குளோன்"</string>
<string name="development_settings_title" msgid="140296922921597393">"டெவெலப்பர் விருப்பங்கள்"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"டெவெலப்பர் விருப்பங்களை இயக்கு"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 3e9d8be..4c71251 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ప్రొఫైల్ను ఎంచుకోండి"</string>
<string name="category_personal" msgid="6236798763159385225">"వ్యక్తిగతం"</string>
<string name="category_work" msgid="4014193632325996115">"వర్క్"</string>
+ <string name="category_private" msgid="4244892185452788977">"ప్రైవేట్"</string>
<string name="category_clone" msgid="1554511758987195974">"క్లోన్ చేయండి"</string>
<string name="development_settings_title" msgid="140296922921597393">"డెవలపర్ ఆప్షన్లు"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"డెవలపర్ ఎంపికలను ప్రారంభించండి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index e67f371..cb6291a 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"เลือกโปรไฟล์"</string>
<string name="category_personal" msgid="6236798763159385225">"ส่วนตัว"</string>
<string name="category_work" msgid="4014193632325996115">"งาน"</string>
+ <string name="category_private" msgid="4244892185452788977">"ส่วนตัว"</string>
<string name="category_clone" msgid="1554511758987195974">"โคลน"</string>
<string name="development_settings_title" msgid="140296922921597393">"ตัวเลือกสำหรับนักพัฒนาแอป"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"เปิดใช้ตัวเลือกสำหรับนักพัฒนาแอป"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 440bbe7..44d5de2 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pumili ng profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabaho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribado"</string>
<string name="category_clone" msgid="1554511758987195974">"I-clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Mga opsyon ng developer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"I-enable ang mga opsyon ng developer"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index b38012f..e33afa1 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -141,7 +141,7 @@
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"İptal"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Eşleme işlemi, bağlantı kurulduğunda kişilerinize ve çağrı geçmişine erişim izni verir."</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya şifre anahtarı yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya geçiş anahtarı yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile iletişim kurulamıyor."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Eşleme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> tarafından reddedildi."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Bilgisayar"</string>
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string>
<string name="category_personal" msgid="6236798763159385225">"Kişisel"</string>
<string name="category_work" msgid="4014193632325996115">"İş"</string>
+ <string name="category_private" msgid="4244892185452788977">"Gizli"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Geliştirici seçenekleri"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Geliştirici seçeneklerini etkinleştir"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 4f08547..319caca 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Вибрати профіль"</string>
<string name="category_personal" msgid="6236798763159385225">"Особисте"</string>
<string name="category_work" msgid="4014193632325996115">"Робоче"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватні"</string>
<string name="category_clone" msgid="1554511758987195974">"Копія профілю"</string>
<string name="development_settings_title" msgid="140296922921597393">"Параметри розробника"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Увімкнути параметри розробника"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index ce67d15..10dacde 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"پروفائل منتخب کریں"</string>
<string name="category_personal" msgid="6236798763159385225">"ذاتی"</string>
<string name="category_work" msgid="4014193632325996115">"دفتر"</string>
+ <string name="category_private" msgid="4244892185452788977">"نجی"</string>
<string name="category_clone" msgid="1554511758987195974">"کلون کریں"</string>
<string name="development_settings_title" msgid="140296922921597393">"ڈویلپر کے اختیارات"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ڈویلپر کے اختیارات فعال کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 77da981..0a85c28 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profilni tanlang"</string>
<string name="category_personal" msgid="6236798763159385225">"Shaxsiy"</string>
<string name="category_work" msgid="4014193632325996115">"Ish"</string>
+ <string name="category_private" msgid="4244892185452788977">"Yopiq"</string>
<string name="category_clone" msgid="1554511758987195974">"Nusxalash"</string>
<string name="development_settings_title" msgid="140296922921597393">"Dasturchi sozlamalari"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Dasturchi sozlamalarini yoqish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index bf510f6..3c34f4d 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Chọn hồ sơ"</string>
<string name="category_personal" msgid="6236798763159385225">"Cá nhân"</string>
<string name="category_work" msgid="4014193632325996115">"Công việc"</string>
+ <string name="category_private" msgid="4244892185452788977">"Riêng tư"</string>
<string name="category_clone" msgid="1554511758987195974">"Nhân bản"</string>
<string name="development_settings_title" msgid="140296922921597393">"Tùy chọn cho nhà phát triển"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Bật tùy chọn nhà phát triển"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 8e3145a..d385e67 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"选择个人资料"</string>
<string name="category_personal" msgid="6236798763159385225">"个人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私享"</string>
<string name="category_clone" msgid="1554511758987195974">"克隆"</string>
<string name="development_settings_title" msgid="140296922921597393">"开发者选项"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"启用开发者选项"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index aa9f21f..adcb4d8 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"選擇設定檔"</string>
<string name="category_personal" msgid="6236798763159385225">"個人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私人"</string>
<string name="category_clone" msgid="1554511758987195974">"複製"</string>
<string name="development_settings_title" msgid="140296922921597393">"開發人員選項"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"啟用開發人員選項"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 3c65a4d..fddef2b 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"選擇設定檔"</string>
<string name="category_personal" msgid="6236798763159385225">"個人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私人"</string>
<string name="category_clone" msgid="1554511758987195974">"複製"</string>
<string name="development_settings_title" msgid="140296922921597393">"開發人員選項"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"啟用開發人員選項"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 08b04cc..82b7306 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Khetha iphrofayela"</string>
<string name="category_personal" msgid="6236798763159385225">"Okomuntu siqu"</string>
<string name="category_work" msgid="4014193632325996115">"Umsebenzi"</string>
+ <string name="category_private" msgid="4244892185452788977">"Okuyimfihlo"</string>
<string name="category_clone" msgid="1554511758987195974">"Yenza i-clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Izinketho Zonjiniyela"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Nika amandla izinketho zonjiniyela"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 57867be..f83e37b 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 @@
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.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 @@
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 @@
}
/**
- * 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/core/instrumentation/SettingsJankMonitor.kt b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
index a5f69ff..02d76304 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
@@ -18,7 +18,7 @@
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.preference.PreferenceGroupAdapter
-import androidx.preference.SwitchPreference
+import androidx.preference.TwoStatePreference
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.jank.InteractionJankMonitor
import java.util.concurrent.Executors
@@ -43,7 +43,10 @@
* @param preference the clicked preference
*/
@JvmStatic
- fun detectSwitchPreferenceClickJank(recyclerView: RecyclerView, preference: SwitchPreference) {
+ fun detectSwitchPreferenceClickJank(
+ recyclerView: RecyclerView,
+ preference: TwoStatePreference,
+ ) {
val adapter = recyclerView.adapter as? PreferenceGroupAdapter ?: return
val adapterPosition = adapter.getPreferenceAdapterPosition(preference)
val viewHolder = recyclerView.findViewHolderForAdapterPosition(adapterPosition) ?: return
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index e38e041..2a28417 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 @@
@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 bf63f5d..5dacba5 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_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 @@
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 @@
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 147412d..8085c99 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_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 @@
mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
return;
}
-
switch (info.getType()) {
case TYPE_GROUP:
mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
@@ -157,6 +158,8 @@
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 a63bbdf..c44f66e 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 @@
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 @@
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 @@
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/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 70956e9..9ab3b47 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -38,6 +38,7 @@
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */
@@ -54,8 +55,11 @@
private final RouteCallback mRouteCallback = new RouteCallback();
private final TransferCallback mTransferCallback = new TransferCallback();
private final ControllerCallback mControllerCallback = new ControllerCallback();
- private final RouteListingPreferenceCallback mRouteListingPreferenceCallback =
- new RouteListingPreferenceCallback();
+ private final Consumer<RouteListingPreference> mRouteListingPreferenceCallback =
+ (preference) -> {
+ notifyRouteListingPreferenceUpdated(preference);
+ refreshDevices();
+ };
// TODO: b/192657812 - Create factory method in InfoMediaManager to return
// RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
@@ -83,7 +87,8 @@
@Override
protected void startScanOnRouter() {
mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
- mRouter.registerRouteListingPreferenceCallback(mExecutor, mRouteListingPreferenceCallback);
+ mRouter.registerRouteListingPreferenceUpdatedCallback(
+ mExecutor, mRouteListingPreferenceCallback);
mRouter.registerTransferCallback(mExecutor, mTransferCallback);
mRouter.registerControllerCallback(mExecutor, mControllerCallback);
mRouter.startScan();
@@ -94,7 +99,7 @@
mRouter.stopScan();
mRouter.unregisterControllerCallback(mControllerCallback);
mRouter.unregisterTransferCallback(mTransferCallback);
- mRouter.unregisterRouteListingPreferenceCallback(mRouteListingPreferenceCallback);
+ mRouter.unregisterRouteListingPreferenceUpdatedCallback(mRouteListingPreferenceCallback);
mRouter.unregisterRouteCallback(mRouteCallback);
}
@@ -308,13 +313,4 @@
refreshDevices();
}
}
-
- private final class RouteListingPreferenceCallback
- extends MediaRouter2.RouteListingPreferenceCallback {
- @Override
- public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {
- notifyRouteListingPreferenceUpdated(preference);
- refreshDevices();
- }
- }
}
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 0000000..c560627
--- /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/Android.bp b/packages/SettingsProvider/Android.bp
index f4ca260..a4a9290 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -17,9 +17,10 @@
],
}
-android_app {
- name: "SettingsProvider",
+android_library {
+ name: "SettingsProviderLib",
defaults: ["platform_app_defaults"],
+ manifest: "AndroidManifestLib.xml",
resource_dirs: ["res"],
srcs: [
"src/**/*.java",
@@ -32,38 +33,37 @@
],
static_libs: [
"device_config_service_flags_java",
- "junit",
"SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayUtils",
],
platform_apis: true,
+}
+
+android_app {
+ name: "SettingsProvider",
+ defaults: ["platform_app_defaults"],
+ resource_dirs: [],
+ static_libs: ["SettingsProviderLib"],
+ platform_apis: true,
certificate: "platform",
privileged: true,
}
android_test {
name: "SettingsProviderTest",
- // Note we statically link several classes to do some unit tests. It's not accessible otherwise
- // because this test is not an instrumentation test. (because the target runs in the system process.)
srcs: [
"test/**/*.java",
- "src/android/provider/settings/backup/*",
- "src/android/provider/settings/validators/*",
- "src/com/android/providers/settings/GenerationRegistry.java",
- "src/com/android/providers/settings/SettingsBackupAgent.java",
- "src/com/android/providers/settings/SettingsState.java",
- "src/com/android/providers/settings/SettingsHelper.java",
- "src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java",
],
static_libs: [
+ // Note we statically link SettingsProviderLib to do some unit tests. It's not accessible otherwise
+ // because this test is not an instrumentation test. (because the target runs in the system process.)
+ "SettingsProviderLib",
+
"androidx.test.rules",
- "device_config_service_flags_java",
"flag-junit",
+ "junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
- "SettingsLibDeviceStateRotationLock",
- "SettingsLibDisplayUtils",
- "platform-test-annotations",
"truth",
],
libs: [
@@ -71,12 +71,7 @@
"android.test.mock",
"unsupportedappusage",
],
- resource_dirs: ["res"],
- aaptflags: [
- "--auto-add-overlay",
- "--extra-packages",
- "com.android.providers.settings",
- ],
+ resource_dirs: [],
platform_apis: true,
certificate: "platform",
test_suites: ["device-tests"],
diff --git a/packages/SettingsProvider/AndroidManifestLib.xml b/packages/SettingsProvider/AndroidManifestLib.xml
new file mode 100644
index 0000000..a20b539
--- /dev/null
+++ b/packages/SettingsProvider/AndroidManifestLib.xml
@@ -0,0 +1,2 @@
+<manifest package="com.android.providers.settings">
+</manifest>
diff --git a/packages/SoundPicker/res/values-af/strings.xml b/packages/SoundPicker/res/values-af/strings.xml
index 7396b76..fd857b1 100644
--- a/packages/SoundPicker/res/values-af/strings.xml
+++ b/packages/SoundPicker/res/values-af/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan nie gepasmaakte luitoon byvoeg nie"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan nie gepasmaakte luitoon uitvee nie"</string>
<string name="app_label" msgid="3091611356093417332">"Klanke"</string>
- <string name="empty_list" msgid="2871978423955821191">"Die lys is leeg"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Klank"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasie"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml
index bd1c24b..85206c0 100644
--- a/packages/SoundPicker/res/values-am/strings.xml
+++ b/packages/SoundPicker/res/values-am/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ብጁ የጥሪ ቅላጼን ማከል አልተቻለም"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ብጁ የጥሪ ቅላጼን መሰረዝ አልተቻለም"</string>
<string name="app_label" msgid="3091611356093417332">"ድምፆች"</string>
- <string name="empty_list" msgid="2871978423955821191">"ዝርዝሩ ባዶ ነው"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ድምፅ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ንዝረት"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ar/strings.xml b/packages/SoundPicker/res/values-ar/strings.xml
index 805c7cf..f8844e9 100644
--- a/packages/SoundPicker/res/values-ar/strings.xml
+++ b/packages/SoundPicker/res/values-ar/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"يتعذر إضافة نغمة رنين مخصصة"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"يتعذر حذف نغمة الرنين المخصصة"</string>
<string name="app_label" msgid="3091611356093417332">"الأصوات"</string>
- <string name="empty_list" msgid="2871978423955821191">"القائمة فارغة."</string>
- <string name="sound_page_title" msgid="2143312098775103522">"الصوت"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"الاهتزاز"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-as/strings.xml b/packages/SoundPicker/res/values-as/strings.xml
index 0a1cd1b..5d6bc5d 100644
--- a/packages/SoundPicker/res/values-as/strings.xml
+++ b/packages/SoundPicker/res/values-as/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন যোগ কৰিব পৰা নগ\'ল"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন মচিব পৰা নগ\'ল"</string>
<string name="app_label" msgid="3091611356093417332">"ধ্বনিসমূহ"</string>
- <string name="empty_list" msgid="2871978423955821191">"সূচীখন খালী আছে"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ধ্বনি"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"কম্পন"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-az/strings.xml b/packages/SoundPicker/res/values-az/strings.xml
index a308329..e32c3eb 100644
--- a/packages/SoundPicker/res/values-az/strings.xml
+++ b/packages/SoundPicker/res/values-az/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Fərdi zəng səsi əlavə etmək mümkün deyil"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Fərdi zəng səsini silmək mümkün deyil"</string>
<string name="app_label" msgid="3091611356093417332">"Səslər"</string>
- <string name="empty_list" msgid="2871978423955821191">"Siyahı boşdur"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Səs"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasiya"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
index 2a7a196..947c85c 100644
--- a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
+++ b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije uspelo"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije uspelo"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-be/strings.xml b/packages/SoundPicker/res/values-be/strings.xml
index 431a301..6f7fc68 100644
--- a/packages/SoundPicker/res/values-be/strings.xml
+++ b/packages/SoundPicker/res/values-be/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Немагчыма дадаць карыстальніцкі рынгтон"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Немагчыма выдаліць карыстальніцкі рынгтон"</string>
<string name="app_label" msgid="3091611356093417332">"Гукі"</string>
- <string name="empty_list" msgid="2871978423955821191">"Спіс пусты"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Гук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вібрацыя"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bg/strings.xml b/packages/SoundPicker/res/values-bg/strings.xml
index 7447af6..4277d28 100644
--- a/packages/SoundPicker/res/values-bg/strings.xml
+++ b/packages/SoundPicker/res/values-bg/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Персонализираната мелодия не може да се добави"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Персонализираната мелодия не може да се изтрие"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"Списъкът е празен"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибриране"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bn/strings.xml b/packages/SoundPicker/res/values-bn/strings.xml
index c31b36a..276594a 100644
--- a/packages/SoundPicker/res/values-bn/strings.xml
+++ b/packages/SoundPicker/res/values-bn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"কাস্টম রিংটোন যোগ করা গেল না"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"কাস্টম রিংটোন মোছা গেল না"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"তালিকায় কিছু নেই"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"সাউন্ড"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ভাইব্রেশন"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bs/strings.xml b/packages/SoundPicker/res/values-bs/strings.xml
index e65b90d..0c8d33f 100644
--- a/packages/SoundPicker/res/values-bs/strings.xml
+++ b/packages/SoundPicker/res/values-bs/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nije moguće dodati prilagođenu melodiju zvona"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nije moguće izbrisati prilagođenu melodiju zvona"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ca/strings.xml b/packages/SoundPicker/res/values-ca/strings.xml
index 33839bc..ed96f70 100644
--- a/packages/SoundPicker/res/values-ca/strings.xml
+++ b/packages/SoundPicker/res/values-ca/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No es pot afegir el so de trucada personalitzat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No es pot suprimir el so de trucada personalitzat"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"La llista és buida"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"So"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibració"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-cs/strings.xml b/packages/SoundPicker/res/values-cs/strings.xml
index 612a6b2..dc67c96 100644
--- a/packages/SoundPicker/res/values-cs/strings.xml
+++ b/packages/SoundPicker/res/values-cs/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Vlastní vyzvánění se nepodařilo přidat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Vlastní vyzvánění se nepodařilo smazat"</string>
<string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
- <string name="empty_list" msgid="2871978423955821191">"Seznam je prázdný"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrace"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-da/strings.xml b/packages/SoundPicker/res/values-da/strings.xml
index 56c4d23..b4437dc 100644
--- a/packages/SoundPicker/res/values-da/strings.xml
+++ b/packages/SoundPicker/res/values-da/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Den tilpassede ringetone kunne ikke tilføjes"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Den tilpassede ringetone kunne ikke slettes"</string>
<string name="app_label" msgid="3091611356093417332">"Lyde"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-de/strings.xml b/packages/SoundPicker/res/values-de/strings.xml
index b004e2a..8be3aaa 100644
--- a/packages/SoundPicker/res/values-de/strings.xml
+++ b/packages/SoundPicker/res/values-de/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Benutzerdefinierter Klingelton konnte nicht hinzugefügt werden"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Benutzerdefinierter Klingelton konnte nicht gelöscht werden"</string>
<string name="app_label" msgid="3091611356093417332">"Töne"</string>
- <string name="empty_list" msgid="2871978423955821191">"Die Liste ist leer"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ton"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-el/strings.xml b/packages/SoundPicker/res/values-el/strings.xml
index bbcb1f5..41e9b0c 100644
--- a/packages/SoundPicker/res/values-el/strings.xml
+++ b/packages/SoundPicker/res/values-el/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Δεν είναι δυνατή η προσθήκη προσαρμοσμένου ήχου κλήσης"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Δεν είναι δυνατή η διαγραφή προσαρμοσμένου ήχου κλήσης"</string>
<string name="app_label" msgid="3091611356093417332">"Ήχοι"</string>
- <string name="empty_list" msgid="2871978423955821191">"Η λίστα είναι κενή"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ήχος"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Δόνηση"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rAU/strings.xml b/packages/SoundPicker/res/values-en-rAU/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rAU/strings.xml
+++ b/packages/SoundPicker/res/values-en-rAU/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rCA/strings.xml b/packages/SoundPicker/res/values-en-rCA/strings.xml
index d887082..b0708356 100644
--- a/packages/SoundPicker/res/values-en-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-en-rCA/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rGB/strings.xml b/packages/SoundPicker/res/values-en-rGB/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rGB/strings.xml
+++ b/packages/SoundPicker/res/values-en-rGB/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rIN/strings.xml b/packages/SoundPicker/res/values-en-rIN/strings.xml
index 5030314..4c237b9 100644
--- a/packages/SoundPicker/res/values-en-rIN/strings.xml
+++ b/packages/SoundPicker/res/values-en-rIN/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rXC/strings.xml b/packages/SoundPicker/res/values-en-rXC/strings.xml
index d26c89b..8397e0b 100644
--- a/packages/SoundPicker/res/values-en-rXC/strings.xml
+++ b/packages/SoundPicker/res/values-en-rXC/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-es-rUS/strings.xml b/packages/SoundPicker/res/values-es-rUS/strings.xml
index 211bfed..5bf73b2 100644
--- a/packages/SoundPicker/res/values-es-rUS/strings.xml
+++ b/packages/SoundPicker/res/values-es-rUS/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se puede agregar el tono personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se puede borrar el tono personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
- <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-es/strings.xml b/packages/SoundPicker/res/values-es/strings.xml
index 3fdad74..a77f656 100644
--- a/packages/SoundPicker/res/values-es/strings.xml
+++ b/packages/SoundPicker/res/values-es/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se ha podido añadir un tono de llamada personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se ha podido eliminar un tono de llamada personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
- <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-et/strings.xml b/packages/SoundPicker/res/values-et/strings.xml
index 3d9ee94..fa680ac 100644
--- a/packages/SoundPicker/res/values-et/strings.xml
+++ b/packages/SoundPicker/res/values-et/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kohandatud helinat ei õnnestu lisada"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kohandatud helinat ei õnnestu kustutada"</string>
<string name="app_label" msgid="3091611356093417332">"Helid"</string>
- <string name="empty_list" msgid="2871978423955821191">"See loend on tühi"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Heli"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibreerimine"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-eu/strings.xml b/packages/SoundPicker/res/values-eu/strings.xml
index 1f929bf..e8e07fe 100644
--- a/packages/SoundPicker/res/values-eu/strings.xml
+++ b/packages/SoundPicker/res/values-eu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ezin da gehitu tonu pertsonalizatua"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ezin da ezabatu tonu pertsonalizatua"</string>
<string name="app_label" msgid="3091611356093417332">"Soinuak"</string>
- <string name="empty_list" msgid="2871978423955821191">"Zerrenda hutsik dago"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Soinua"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Dardara"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fa/strings.xml b/packages/SoundPicker/res/values-fa/strings.xml
index 3b75ac9..769d5d5 100644
--- a/packages/SoundPicker/res/values-fa/strings.xml
+++ b/packages/SoundPicker/res/values-fa/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"افزودن آهنگ زنگ سفارشی ممکن نیست"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حذف آهنگ زنگ سفارشی ممکن نیست"</string>
<string name="app_label" msgid="3091611356093417332">"صداها"</string>
- <string name="empty_list" msgid="2871978423955821191">"فهرست خالی است"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"صدا"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"لرزش"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fi/strings.xml b/packages/SoundPicker/res/values-fi/strings.xml
index 15bf658..fcda098 100644
--- a/packages/SoundPicker/res/values-fi/strings.xml
+++ b/packages/SoundPicker/res/values-fi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Muokatun soittoäänen lisääminen epäonnistui."</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Muokatun soittoäänen poistaminen epäonnistui."</string>
<string name="app_label" msgid="3091611356093417332">"Äänet"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista on tyhjä"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ääni"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Värinä"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fr-rCA/strings.xml b/packages/SoundPicker/res/values-fr-rCA/strings.xml
index 1cc170f..4d4545f 100644
--- a/packages/SoundPicker/res/values-fr-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-fr-rCA/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fr/strings.xml b/packages/SoundPicker/res/values-fr/strings.xml
index ade2c16..9452e70 100644
--- a/packages/SoundPicker/res/values-fr/strings.xml
+++ b/packages/SoundPicker/res/values-fr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-gl/strings.xml b/packages/SoundPicker/res/values-gl/strings.xml
index 83f2b2e..59a9d06 100644
--- a/packages/SoundPicker/res/values-gl/strings.xml
+++ b/packages/SoundPicker/res/values-gl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Non se pode engadir un ton de chamada personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Non se pode eliminar un ton de chamada personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está baleira"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-gu/strings.xml b/packages/SoundPicker/res/values-gu/strings.xml
index 8207512..209769f 100644
--- a/packages/SoundPicker/res/values-gu/strings.xml
+++ b/packages/SoundPicker/res/values-gu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"કસ્ટમ રિંગટોન ઉમેરવામાં અસમર્થ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"કસ્ટમ રિંગટોન કાઢી નાખવામાં અસમર્થ"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"સૂચિ ખાલી છે"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"સાઉન્ડ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"વાઇબ્રેશન"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hi/strings.xml b/packages/SoundPicker/res/values-hi/strings.xml
index 304201f..ab3b7f8 100644
--- a/packages/SoundPicker/res/values-hi/strings.xml
+++ b/packages/SoundPicker/res/values-hi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"आपके मुताबिक रिंगटोन नहीं जोड़ी जा सकी"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आपके मुताबिक रिंगटोन नहीं हटाई जा सकी"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"यह सूची खाली है"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"साउंड"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"वाइब्रेशन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hr/strings.xml b/packages/SoundPicker/res/values-hr/strings.xml
index 642c7d5..3adc500 100644
--- a/packages/SoundPicker/res/values-hr/strings.xml
+++ b/packages/SoundPicker/res/values-hr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije moguće"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije moguće"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Popis je prazan"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hu/strings.xml b/packages/SoundPicker/res/values-hu/strings.xml
index 401da84..32d4ba9 100644
--- a/packages/SoundPicker/res/values-hu/strings.xml
+++ b/packages/SoundPicker/res/values-hu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nem sikerült hozzáadni az egyéni csengőhangot"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nem sikerült törölni az egyéni csengőhangot"</string>
<string name="app_label" msgid="3091611356093417332">"Hangok"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista üres"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Hang"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Rezgés"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hy/strings.xml b/packages/SoundPicker/res/values-hy/strings.xml
index d57345c..da8934f 100644
--- a/packages/SoundPicker/res/values-hy/strings.xml
+++ b/packages/SoundPicker/res/values-hy/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Հնարավոր չէ հատուկ զանգերանգ ավելացնել"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Հնարավոր չէ ջնջել հատուկ զանգերանգը"</string>
<string name="app_label" msgid="3091611356093417332">"Ձայներ"</string>
- <string name="empty_list" msgid="2871978423955821191">"Ցանկը դատարկ է"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ձայն"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Թրթռոց"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-in/strings.xml b/packages/SoundPicker/res/values-in/strings.xml
index 518a09d..86dce64 100644
--- a/packages/SoundPicker/res/values-in/strings.xml
+++ b/packages/SoundPicker/res/values-in/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambahkan nada dering khusus"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat menghapus nada dering khusus"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"Daftar ini kosong"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Suara"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-is/strings.xml b/packages/SoundPicker/res/values-is/strings.xml
index 7f05c79..d0fce78 100644
--- a/packages/SoundPicker/res/values-is/strings.xml
+++ b/packages/SoundPicker/res/values-is/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Get ekki bætt sérsniðnum hringitóni við"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Get ekki eytt sérsniðnum hringitóni"</string>
<string name="app_label" msgid="3091611356093417332">"Hljóð"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listinn er auður"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Hljóð"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Titringur"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-it/strings.xml b/packages/SoundPicker/res/values-it/strings.xml
index e1d8e19..632cb41 100644
--- a/packages/SoundPicker/res/values-it/strings.xml
+++ b/packages/SoundPicker/res/values-it/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossibile aggiungere suoneria personalizzata"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossibile eliminare suoneria personalizzata"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"L\'elenco è vuoto"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Suoneria"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrazione"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-iw/strings.xml b/packages/SoundPicker/res/values-iw/strings.xml
index 238370c9..387b140 100644
--- a/packages/SoundPicker/res/values-iw/strings.xml
+++ b/packages/SoundPicker/res/values-iw/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"לא ניתן להוסיף רינגטון מותאם אישית"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"לא ניתן למחוק רינגטון מותאם אישית"</string>
<string name="app_label" msgid="3091611356093417332">"צלילים"</string>
- <string name="empty_list" msgid="2871978423955821191">"הרשימה ריקה"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"צליל"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"רטט"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ja/strings.xml b/packages/SoundPicker/res/values-ja/strings.xml
index 408e870..7c2aec6 100644
--- a/packages/SoundPicker/res/values-ja/strings.xml
+++ b/packages/SoundPicker/res/values-ja/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"カスタム着信音を追加できません"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"カスタム着信音を削除できません"</string>
<string name="app_label" msgid="3091611356093417332">"サウンド"</string>
- <string name="empty_list" msgid="2871978423955821191">"このリストは空です"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"バイブレーション"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ka/strings.xml b/packages/SoundPicker/res/values-ka/strings.xml
index 83dfc3c..1cfe240 100644
--- a/packages/SoundPicker/res/values-ka/strings.xml
+++ b/packages/SoundPicker/res/values-ka/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"მორგებული ზარის დამატება შეუძლებელია"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"მორგებული ზარის წაშლა შეუძლებელია"</string>
<string name="app_label" msgid="3091611356093417332">"ხმები"</string>
- <string name="empty_list" msgid="2871978423955821191">"სია ცარიელია"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ხმა"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ვიბრაცია"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-kk/strings.xml b/packages/SoundPicker/res/values-kk/strings.xml
index 1815a95..8c4c169 100644
--- a/packages/SoundPicker/res/values-kk/strings.xml
+++ b/packages/SoundPicker/res/values-kk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Арнаулы рингтонды енгізу мүмкін емес"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Арнаулы рингтонды жою мүмкін емес"</string>
<string name="app_label" msgid="3091611356093417332">"Дыбыстар"</string>
- <string name="empty_list" msgid="2871978423955821191">"Тізім бос."</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Дыбыс"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Діріл"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-km/strings.xml b/packages/SoundPicker/res/values-km/strings.xml
index 6ae048f..a334429 100644
--- a/packages/SoundPicker/res/values-km/strings.xml
+++ b/packages/SoundPicker/res/values-km/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"មិនអាចបន្ថែមសំឡេងរោទ៍ផ្ទាល់ខ្លួនបាន"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"មិនអាចលុបសំឡេងរោទ៍ផ្ទាល់ខ្លួនបានទេ"</string>
<string name="app_label" msgid="3091611356093417332">"សំឡេង"</string>
- <string name="empty_list" msgid="2871978423955821191">"បញ្ជីគឺទទេ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"សំឡេង"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ការញ័រ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-kn/strings.xml b/packages/SoundPicker/res/values-kn/strings.xml
index c528866..da90ccb 100644
--- a/packages/SoundPicker/res/values-kn/strings.xml
+++ b/packages/SoundPicker/res/values-kn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ಕಸ್ಟಮ್ ರಿಂಗ್ಟೋನ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ಕಸ್ಟಮ್ ರಿಂಗ್ಟೋನ್ ಅಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="app_label" msgid="3091611356093417332">"ಧ್ವನಿಗಳು"</string>
- <string name="empty_list" msgid="2871978423955821191">"ಪಟ್ಟಿ ಖಾಲಿಯಾಗಿದೆ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ಧ್ವನಿ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ವೈಬ್ರೇಷನ್"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ko/strings.xml b/packages/SoundPicker/res/values-ko/strings.xml
index bcab6b2..70554d6 100644
--- a/packages/SoundPicker/res/values-ko/strings.xml
+++ b/packages/SoundPicker/res/values-ko/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"맞춤 벨소리를 추가할 수 없습니다."</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"맞춤 벨소리를 삭제할 수 없습니다."</string>
<string name="app_label" msgid="3091611356093417332">"소리"</string>
- <string name="empty_list" msgid="2871978423955821191">"목록이 비어 있음"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"소리"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"진동"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ky/strings.xml b/packages/SoundPicker/res/values-ky/strings.xml
index babd8c0..3c95228 100644
--- a/packages/SoundPicker/res/values-ky/strings.xml
+++ b/packages/SoundPicker/res/values-ky/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Жеке рингтон кошулбай жатат"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Жеке рингтон жок кылынбай жатат"</string>
<string name="app_label" msgid="3091611356093417332">"Үндөр"</string>
- <string name="empty_list" msgid="2871978423955821191">"Тизме бош"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Үн"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Дирилдөө"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lo/strings.xml b/packages/SoundPicker/res/values-lo/strings.xml
index 5e496e8..8bcae0d 100644
--- a/packages/SoundPicker/res/values-lo/strings.xml
+++ b/packages/SoundPicker/res/values-lo/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"ສຽງ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ລາຍຊື່ຫວ່າງເປົ່າ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ສຽງ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ການສັ່ນເຕືອນ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lt/strings.xml b/packages/SoundPicker/res/values-lt/strings.xml
index c68cc3b..c7ea369 100644
--- a/packages/SoundPicker/res/values-lt/strings.xml
+++ b/packages/SoundPicker/res/values-lt/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepavyksta pridėti tinkinto skambėjimo tono"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepavyksta ištrinti tinkinto skambėjimo tono"</string>
<string name="app_label" msgid="3091611356093417332">"Garsai"</string>
- <string name="empty_list" msgid="2871978423955821191">"Sąrašas yra tuščias"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Garsas"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibravimas"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lv/strings.xml b/packages/SoundPicker/res/values-lv/strings.xml
index fab7f8c..2a26289 100644
--- a/packages/SoundPicker/res/values-lv/strings.xml
+++ b/packages/SoundPicker/res/values-lv/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nevar pievienot pielāgotu zvana signālu"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nevar izdzēst pielāgotu zvana signālu"</string>
<string name="app_label" msgid="3091611356093417332">"Skaņas"</string>
- <string name="empty_list" msgid="2871978423955821191">"Saraksts ir tukšs"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Skaņa"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrācija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mk/strings.xml b/packages/SoundPicker/res/values-mk/strings.xml
index 4f2322a..545d5ed 100644
--- a/packages/SoundPicker/res/values-mk/strings.xml
+++ b/packages/SoundPicker/res/values-mk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не може да се додаде приспособена мелодија"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не може да се избрише приспособена мелодија"</string>
<string name="app_label" msgid="3091611356093417332">"Звуци"</string>
- <string name="empty_list" msgid="2871978423955821191">"Списокот е празен"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрации"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ml/strings.xml b/packages/SoundPicker/res/values-ml/strings.xml
index 16cf725..21da8e8 100644
--- a/packages/SoundPicker/res/values-ml/strings.xml
+++ b/packages/SoundPicker/res/values-ml/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ഇഷ്ടാനുസൃത റിംഗ്ടോൺ ചേർക്കാനാവില്ല"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ഇഷ്ടാനുസൃത റിംഗ്ടോൺ ഇല്ലാതാക്കാനാവില്ല"</string>
<string name="app_label" msgid="3091611356093417332">"ശബ്ദങ്ങൾ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ലിസ്റ്റിൽ ഒന്നുമില്ല"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ശബ്ദം"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"വൈബ്രേഷൻ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mn/strings.xml b/packages/SoundPicker/res/values-mn/strings.xml
index 6215a41..15f7d12 100644
--- a/packages/SoundPicker/res/values-mn/strings.xml
+++ b/packages/SoundPicker/res/values-mn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Захиалгат хонхны ая нэмэх боломжгүй"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Захиалгат хонхны ая устгах боломжгүй"</string>
<string name="app_label" msgid="3091611356093417332">"Дуу чимээ"</string>
- <string name="empty_list" msgid="2871978423955821191">"Жагсаалт хоосон байна"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Дуу"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Чичиргээ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mr/strings.xml b/packages/SoundPicker/res/values-mr/strings.xml
index fe2fe12..3ddb991 100644
--- a/packages/SoundPicker/res/values-mr/strings.xml
+++ b/packages/SoundPicker/res/values-mr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"कस्टम रिंगटोन जोडण्यात अक्षम"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"कस्टम रिंगटोन हटविण्यात अक्षम"</string>
<string name="app_label" msgid="3091611356093417332">"आवाज"</string>
- <string name="empty_list" msgid="2871978423955821191">"ही सूची रिकामी आहे"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"आवाज"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"व्हायब्रेशन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ms/strings.xml b/packages/SoundPicker/res/values-ms/strings.xml
index 5788f18..9d87d72 100644
--- a/packages/SoundPicker/res/values-ms/strings.xml
+++ b/packages/SoundPicker/res/values-ms/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambah nada dering tersuai"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat memadamkan nada dering tersuai"</string>
<string name="app_label" msgid="3091611356093417332">"Bunyi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Senarai ini kosong"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Bunyi"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-my/strings.xml b/packages/SoundPicker/res/values-my/strings.xml
index 0a1a4cf..62163e9 100644
--- a/packages/SoundPicker/res/values-my/strings.xml
+++ b/packages/SoundPicker/res/values-my/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ထည့်သွင်း၍မရပါ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ဖျက်၍မရပါ"</string>
<string name="app_label" msgid="3091611356093417332">"အသံများ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ဤစာရင်းတွင် မည်သည့်အရာမျှမရှိပါ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"အသံ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"တုန်ခါမှု"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-nb/strings.xml b/packages/SoundPicker/res/values-nb/strings.xml
index c315801..e4e259a 100644
--- a/packages/SoundPicker/res/values-nb/strings.xml
+++ b/packages/SoundPicker/res/values-nb/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan ikke legge til egendefinert ringelyd"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan ikke slette egendefinert ringelyd"</string>
<string name="app_label" msgid="3091611356093417332">"Lyder"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrering"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ne/strings.xml b/packages/SoundPicker/res/values-ne/strings.xml
index b95b70c..0a2bceb 100644
--- a/packages/SoundPicker/res/values-ne/strings.xml
+++ b/packages/SoundPicker/res/values-ne/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"आफू अनुकूल रिङटोन थप्न सकिएन"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आफू अनुकूल रिङटोनलाई मेट्न सकिएन"</string>
<string name="app_label" msgid="3091611356093417332">"ध्वनिहरू"</string>
- <string name="empty_list" msgid="2871978423955821191">"यो सूची खाली छ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ध्वनि"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"भाइब्रेसन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-nl/strings.xml b/packages/SoundPicker/res/values-nl/strings.xml
index 192431b..5b6fb70 100644
--- a/packages/SoundPicker/res/values-nl/strings.xml
+++ b/packages/SoundPicker/res/values-nl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Toevoegen van aangepaste ringtone is mislukt"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Verwijderen van aangepaste ringtone is mislukt"</string>
<string name="app_label" msgid="3091611356093417332">"Geluiden"</string>
- <string name="empty_list" msgid="2871978423955821191">"De lijst is leeg"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Geluid"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Trillen"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-or/strings.xml b/packages/SoundPicker/res/values-or/strings.xml
index 5d82039..45ce594 100644
--- a/packages/SoundPicker/res/values-or/strings.xml
+++ b/packages/SoundPicker/res/values-or/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"କଷ୍ଟମ୍ ରିଙ୍ଗଟୋନ୍ ଯୋଡ଼ିପାରିବ ନାହିଁ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"କଷ୍ଟମ୍ ରିଙ୍ଗଟୋନ୍ ଡିଲିଟ୍ କରିପାରିବ ନାହିଁ"</string>
<string name="app_label" msgid="3091611356093417332">"ସାଉଣ୍ଡ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ତାଲିକା ଖାଲି ଅଛି"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ସାଉଣ୍ଡ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ଭାଇବ୍ରେସନ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pa/strings.xml b/packages/SoundPicker/res/values-pa/strings.xml
index 63ecb9d..1e62f64 100644
--- a/packages/SoundPicker/res/values-pa/strings.xml
+++ b/packages/SoundPicker/res/values-pa/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਸ਼ਾਮਲ ਕਰਨ ਦੇ ਅਯੋਗ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਮਿਟਾਉਣ ਦੇ ਅਯੋਗ"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"ਇਹ ਸੂਚੀ ਖਾਲੀ ਹੈ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ਅਵਾਜ਼"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ਥਰਥਰਾਹਟ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pl/strings.xml b/packages/SoundPicker/res/values-pl/strings.xml
index 310f40f..1b3b5c4 100644
--- a/packages/SoundPicker/res/values-pl/strings.xml
+++ b/packages/SoundPicker/res/values-pl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nie można dodać dzwonka niestandardowego"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nie można usunąć dzwonka niestandardowego"</string>
<string name="app_label" msgid="3091611356093417332">"Dźwięki"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista jest pusta"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Dźwięk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Wibracje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt-rBR/strings.xml b/packages/SoundPicker/res/values-pt-rBR/strings.xml
index 9f58ca0..7b545e1 100644
--- a/packages/SoundPicker/res/values-pt-rBR/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rBR/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt-rPT/strings.xml b/packages/SoundPicker/res/values-pt-rPT/strings.xml
index fd4e69f..5d742f1 100644
--- a/packages/SoundPicker/res/values-pt-rPT/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rPT/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível eliminar o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt/strings.xml b/packages/SoundPicker/res/values-pt/strings.xml
index 9f58ca0..7b545e1 100644
--- a/packages/SoundPicker/res/values-pt/strings.xml
+++ b/packages/SoundPicker/res/values-pt/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ro/strings.xml b/packages/SoundPicker/res/values-ro/strings.xml
index 08d937c..58b5aeb 100644
--- a/packages/SoundPicker/res/values-ro/strings.xml
+++ b/packages/SoundPicker/res/values-ro/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nu se poate adăuga tonul de sonerie personalizat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nu se poate șterge tonul de sonerie personalizat"</string>
<string name="app_label" msgid="3091611356093417332">"Sunete"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista este goală"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sunet"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrație"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ru/strings.xml b/packages/SoundPicker/res/values-ru/strings.xml
index be5495a..0d48ac1 100644
--- a/packages/SoundPicker/res/values-ru/strings.xml
+++ b/packages/SoundPicker/res/values-ru/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не удалось добавить рингтон"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не удалось удалить рингтон"</string>
<string name="app_label" msgid="3091611356093417332">"Звуки"</string>
- <string name="empty_list" msgid="2871978423955821191">"Список пуст"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрация"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-si/strings.xml b/packages/SoundPicker/res/values-si/strings.xml
index 6ba86cb..1872b6b 100644
--- a/packages/SoundPicker/res/values-si/strings.xml
+++ b/packages/SoundPicker/res/values-si/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"අභිරුචි නාද රිද්මය එක් කළ නොහැකිය"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"අභිරුචි නාද රිද්මය මැකිය නොහැකිය"</string>
<string name="app_label" msgid="3091611356093417332">"ශබ්ද"</string>
- <string name="empty_list" msgid="2871978423955821191">"ලැයිස්තුව හිස් ය"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ශබ්දය"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"කම්පනය"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sk/strings.xml b/packages/SoundPicker/res/values-sk/strings.xml
index 8f54350..8ff6d12 100644
--- a/packages/SoundPicker/res/values-sk/strings.xml
+++ b/packages/SoundPicker/res/values-sk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepodarilo sa pridať vlastný tón zvonenia"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepodarilo sa odstrániť vlastný tón zvonenia"</string>
<string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
- <string name="empty_list" msgid="2871978423955821191">"Zoznam je prázdny"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrácie"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sl/strings.xml b/packages/SoundPicker/res/values-sl/strings.xml
index 1a818b2..77a2a2c 100644
--- a/packages/SoundPicker/res/values-sl/strings.xml
+++ b/packages/SoundPicker/res/values-sl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tona zvonjenja po meri ni mogoče dodati"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tona zvonjenja po meri ni mogoče izbrisati"</string>
<string name="app_label" msgid="3091611356093417332">"Zvoki"</string>
- <string name="empty_list" msgid="2871978423955821191">"Seznam je prazen"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvok"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sq/strings.xml b/packages/SoundPicker/res/values-sq/strings.xml
index 4c497bb..e35dd71 100644
--- a/packages/SoundPicker/res/values-sq/strings.xml
+++ b/packages/SoundPicker/res/values-sq/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nuk mund të shtojë ton zileje të personalizuar"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nuk mund të fshijë ton zileje të personalizuar"</string>
<string name="app_label" msgid="3091611356093417332">"Tingujt"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista është bosh"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Me tingull"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Me dridhje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sr/strings.xml b/packages/SoundPicker/res/values-sr/strings.xml
index 2ec1f17..bc573f5 100644
--- a/packages/SoundPicker/res/values-sr/strings.xml
+++ b/packages/SoundPicker/res/values-sr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Додавање прилагођене мелодије звона није успело"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Брисање прилагођене мелодије звона није успело"</string>
<string name="app_label" msgid="3091611356093417332">"Звукови"</string>
- <string name="empty_list" msgid="2871978423955821191">"Листа је празна"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрирање"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sv/strings.xml b/packages/SoundPicker/res/values-sv/strings.xml
index cb1cd3a..c1dd1c2 100644
--- a/packages/SoundPicker/res/values-sv/strings.xml
+++ b/packages/SoundPicker/res/values-sv/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Det gick inte att lägga till en egen ringsignal"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Det gick inte att radera den egna ringsignalen"</string>
<string name="app_label" msgid="3091611356093417332">"Ljud"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listan är tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ljud"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sw/strings.xml b/packages/SoundPicker/res/values-sw/strings.xml
index 8fd6d58..b023450 100644
--- a/packages/SoundPicker/res/values-sw/strings.xml
+++ b/packages/SoundPicker/res/values-sw/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Imeshindwa kuongeza mlio maalum wa simu"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Imeshindwa kufuta mlio maalum wa simu"</string>
<string name="app_label" msgid="3091611356093417332">"Sauti"</string>
- <string name="empty_list" msgid="2871978423955821191">"Orodha hii haina chochote"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sauti"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Mtetemo"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ta/strings.xml b/packages/SoundPicker/res/values-ta/strings.xml
index 692e58a..38e45b7 100644
--- a/packages/SoundPicker/res/values-ta/strings.xml
+++ b/packages/SoundPicker/res/values-ta/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"பிரத்தியேக ரிங்டோனைச் சேர்க்க முடியவில்லை"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"பிரத்தியேக ரிங்டோனை நீக்க முடியவில்லை"</string>
<string name="app_label" msgid="3091611356093417332">"ஒலிகள்"</string>
- <string name="empty_list" msgid="2871978423955821191">"பட்டியல் காலியாக உள்ளது"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ஒலி"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"அதிர்வு"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-te/strings.xml b/packages/SoundPicker/res/values-te/strings.xml
index ce13e53..2d03ac0 100644
--- a/packages/SoundPicker/res/values-te/strings.xml
+++ b/packages/SoundPicker/res/values-te/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"అనుకూల రింగ్టోన్ను జోడించలేకపోయింది"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"అనుకూల రింగ్టోన్ను తొలగించలేకపోయింది"</string>
<string name="app_label" msgid="3091611356093417332">"ధ్వనులు"</string>
- <string name="empty_list" msgid="2871978423955821191">"మీ లిస్ట్ ఖాళీగా ఉంది"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"సౌండ్"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"వైబ్రేషన్"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-th/strings.xml b/packages/SoundPicker/res/values-th/strings.xml
index d5dc9b7..cc2e43f 100644
--- a/packages/SoundPicker/res/values-th/strings.xml
+++ b/packages/SoundPicker/res/values-th/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ไม่สามารถเพิ่มเสียงเรียกเข้าที่กำหนดเอง"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ไม่สามารถลบเสียงเรียกเข้าที่กำหนดเอง"</string>
<string name="app_label" msgid="3091611356093417332">"เสียง"</string>
- <string name="empty_list" msgid="2871978423955821191">"รายการว่างเปล่า"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"เสียง"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"การสั่น"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-tl/strings.xml b/packages/SoundPicker/res/values-tl/strings.xml
index 4e3bf7e..c0c1712 100644
--- a/packages/SoundPicker/res/values-tl/strings.xml
+++ b/packages/SoundPicker/res/values-tl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Hindi maidagdag ang custom na ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Hindi ma-delete ang custom na ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Mga Tunog"</string>
- <string name="empty_list" msgid="2871978423955821191">"Walang laman ang listahan"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Tunog"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Pag-vibrate"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-tr/strings.xml b/packages/SoundPicker/res/values-tr/strings.xml
index 51ac541..955c23f 100644
--- a/packages/SoundPicker/res/values-tr/strings.xml
+++ b/packages/SoundPicker/res/values-tr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Özel zil sesi eklenemiyor"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Özel zil sesi silinemiyor"</string>
<string name="app_label" msgid="3091611356093417332">"Sesler"</string>
- <string name="empty_list" msgid="2871978423955821191">"Liste boş"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ses"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Titreşim"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-uk/strings.xml b/packages/SoundPicker/res/values-uk/strings.xml
index a905b95..42dbfb0 100644
--- a/packages/SoundPicker/res/values-uk/strings.xml
+++ b/packages/SoundPicker/res/values-uk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не вдалося додати користувацький сигнал дзвінка"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не вдалося видалити користувацький сигнал дзвінка"</string>
<string name="app_label" msgid="3091611356093417332">"Звуки"</string>
- <string name="empty_list" msgid="2871978423955821191">"Список пустий"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вібрація"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ur/strings.xml b/packages/SoundPicker/res/values-ur/strings.xml
index 9cae118..58141d6 100644
--- a/packages/SoundPicker/res/values-ur/strings.xml
+++ b/packages/SoundPicker/res/values-ur/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"حسب ضرورت رنگ ٹون شامل کرنے سے قاصر ہے"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حسب ضرورت رنگ ٹون حذف کرنے سے قاصر ہے"</string>
<string name="app_label" msgid="3091611356093417332">"آوازیں"</string>
- <string name="empty_list" msgid="2871978423955821191">"فہرست خالی ہے"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"آواز"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"وائبریشن"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-uz/strings.xml b/packages/SoundPicker/res/values-uz/strings.xml
index e6231f2..c39db5f 100644
--- a/packages/SoundPicker/res/values-uz/strings.xml
+++ b/packages/SoundPicker/res/values-uz/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Maxsus rington qo‘shib bo‘lmadi"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Maxsus ringtonni o‘chirib bo‘lmadi"</string>
<string name="app_label" msgid="3091611356093417332">"Tovushlar"</string>
- <string name="empty_list" msgid="2871978423955821191">"Roʻyxat boʻsh"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Tovush"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Tebranish"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-vi/strings.xml b/packages/SoundPicker/res/values-vi/strings.xml
index 070ac16..bed0e96 100644
--- a/packages/SoundPicker/res/values-vi/strings.xml
+++ b/packages/SoundPicker/res/values-vi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Không thể thêm nhạc chuông tùy chỉnh"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Không thể xóa nhạc chuông tùy chỉnh"</string>
<string name="app_label" msgid="3091611356093417332">"Âm thanh"</string>
- <string name="empty_list" msgid="2871978423955821191">"Danh sách này đang trống"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Âm thanh"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Rung"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rCN/strings.xml b/packages/SoundPicker/res/values-zh-rCN/strings.xml
index 0420fc9..864aaae 100644
--- a/packages/SoundPicker/res/values-zh-rCN/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rCN/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"无法添加自定义铃声"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"无法删除自定义铃声"</string>
<string name="app_label" msgid="3091611356093417332">"声音"</string>
- <string name="empty_list" msgid="2871978423955821191">"列表为空"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"声音"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"振动"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rHK/strings.xml b/packages/SoundPicker/res/values-zh-rHK/strings.xml
index 60d9a52..4cde32d 100644
--- a/packages/SoundPicker/res/values-zh-rHK/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rHK/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法加入自訂鈴聲"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
<string name="app_label" msgid="3091611356093417332">"音效"</string>
- <string name="empty_list" msgid="2871978423955821191">"清單中沒有內容"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rTW/strings.xml b/packages/SoundPicker/res/values-zh-rTW/strings.xml
index c173c0a..df8a66a 100644
--- a/packages/SoundPicker/res/values-zh-rTW/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rTW/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法新增自訂鈴聲"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
<string name="app_label" msgid="3091611356093417332">"音效"</string>
- <string name="empty_list" msgid="2871978423955821191">"清單中沒有任何項目"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zu/strings.xml b/packages/SoundPicker/res/values-zu/strings.xml
index 978e925..29a8ffe 100644
--- a/packages/SoundPicker/res/values-zu/strings.xml
+++ b/packages/SoundPicker/res/values-zu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ayikwazi ukwengeza ithoni yokukhala yangokwezifiso"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ayikwazi ukususa ithoni yokukhala yangokwezifiso"</string>
<string name="app_label" msgid="3091611356093417332">"Imisindo"</string>
- <string name="empty_list" msgid="2871978423955821191">"Uhlu alunalutho"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Umsindo"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Ukudlidliza"</string>
</resources>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9d3200d..e6a82e8 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -214,6 +214,7 @@
lint: {
extra_check_modules: ["SystemUILintChecker"],
+ warning_checks: ["MissingApacheLicenseDetector"],
},
}
@@ -254,7 +255,6 @@
srcs: [
/* Keyguard converted tests */
// data
- "tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
@@ -402,10 +402,19 @@
"tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt",
"tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt",
"tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt",
+
],
path: "tests/src",
}
+filegroup {
+ name: "SystemUI-tests-multivalent",
+ srcs: [
+ "multivalentTests/src/**/*.kt",
+ ],
+ path: "multivalentTests/src",
+}
+
java_library {
name: "SystemUI-tests-concurrency",
srcs: [
@@ -494,6 +503,7 @@
"src/**/*.java",
"src/**/I*.aidl",
":ReleaseJavaFiles",
+ ":SystemUI-tests-multivalent",
":SystemUI-tests-utils",
],
static_libs: [
@@ -572,6 +582,7 @@
":SystemUI-tests-utils",
":SystemUI-test-fakes",
":SystemUI-tests-robolectric-pilots",
+ ":SystemUI-tests-multivalent",
],
static_libs: [
"androidx.test.uiautomator_uiautomator",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 03f7c99..de73b77 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -32,22 +32,6 @@
]
},
{
- "name": "SystemUIGoogleScreenshotTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.Postsubmit"
- }
- ],
- // The test doesn't run on AOSP Cuttlefish
- "keywords": ["internal"]
- },
- {
// TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
"name": "SystemUIGoogleBiometricsScreenshotTests",
"options": [
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 0f55f35..eadcd7c 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/accessibility/accessibilitymenu/res/values-hy/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
index e06787f..66e37a1 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
@@ -10,7 +10,7 @@
<string name="power_utterance" msgid="7444296686402104807">"Սնուցման կոճակի ընտրանքներ"</string>
<string name="recent_apps_label" msgid="6583276995616385847">"Վերջին հավելվածներ"</string>
<string name="lockscreen_label" msgid="648347953557887087">"Կողպէկրան"</string>
- <string name="quick_settings_label" msgid="2999117381487601865">"Արագ\\nկարգավորումներ"</string>
+ <string name="quick_settings_label" msgid="2999117381487601865">"Արագ կարգավորումներ"</string>
<string name="notifications_label" msgid="6829741046963013567">"Ծանուցումներ"</string>
<string name="screenshot_label" msgid="863978141223970162">"Սքրինշոթ"</string>
<string name="screenshot_utterance" msgid="1430760563401895074">"Ստանալ սքրինշոթը"</string>
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 8841967..bcf1535 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 211af90..0567528 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -31,6 +31,13 @@
}
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/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 4ea57a8..ab4db45 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -290,9 +290,10 @@
controller: Controller?,
animate: Boolean = true,
packageName: String? = null,
+ showOverLockscreen: Boolean = false,
intentStarter: PendingIntentStarter
) {
- startIntentWithAnimation(controller, animate, packageName) {
+ startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) {
intentStarter.startPendingIntent(it)
}
}
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 6c4b695..af35ea4 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 @@
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 @@
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 @@
// 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/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
new file mode 100644
index 0000000..46125be
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
@@ -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 com.android.internal.systemui.lint
+
+import com.android.ide.common.blame.SourcePosition
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.time.Year
+import org.jetbrains.uast.UComment
+import org.jetbrains.uast.UFile
+
+/**
+ * Checks if every AOSP Java/Kotlin source code file is starting with Apache license information.
+ */
+class MissingApacheLicenseDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableUastTypes() = listOf(UFile::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? {
+ return object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ val firstComment = node.allCommentsInFile.firstOrNull()
+ // Normally we don't need to explicitly handle suppressing case and just return
+ // error as usual with indicating node and lint will ignore it for us. But here
+ // suppressing will be applied on top of comment that doesn't exist so we don't have
+ // node to return - it's a bit of corner case
+ if (firstComment != null && firstComment.isSuppressingComment()) {
+ return
+ }
+ if (firstComment == null || !firstComment.isLicenseComment()) {
+ val firstLineOfFile =
+ Location.create(
+ context.file,
+ SourcePosition(/* lineNumber= */ 1, /* column= */ 1, /* offset= */ 0)
+ )
+ context.report(
+ issue = ISSUE,
+ location = firstLineOfFile,
+ message =
+ "License header is missing\n" +
+ "Please add the following copyright and license header to the" +
+ " beginning of the file:\n\n" +
+ copyrightHeader
+ )
+ }
+ }
+ }
+ }
+
+ private fun UComment.isSuppressingComment(): Boolean {
+ val suppressingComment =
+ "//noinspection ${MissingApacheLicenseDetector::class.java.simpleName}"
+ return text.contains(suppressingComment)
+ }
+
+ private fun UComment.isLicenseComment(): Boolean {
+ // We probably don't want to compare full copyright header in case there are some small
+ // discrepancies in already existing files, e.g. year. We could do regexp but it should be
+ // good enough if this detector deals with missing
+ // license header instead of incorrect license header
+ return text.contains("Apache License")
+ }
+
+ private val copyrightHeader: String
+ get() =
+ """
+ /*
+ * Copyright (C) ${Year.now().value} 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.
+ */
+ """
+ .trimIndent()
+ .trim()
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "MissingApacheLicenseDetector",
+ briefDescription = "File is missing Apache license information",
+ explanation =
+ """
+ Every source code file should have copyright and license information \
+ attached at the beginning.""",
+ category = Category.COMPLIANCE,
+ priority = 8,
+ // ignored by default and then explicitly overridden in SysUI's soong configuration
+ severity = Severity.IGNORE,
+ implementation =
+ Implementation(MissingApacheLicenseDetector::class.java, Scope.JAVA_FILE_SCOPE),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 520c888..e09aa42 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -42,6 +42,7 @@
StaticSettingsProviderDetector.ISSUE,
DemotingTestWithoutBugDetector.ISSUE,
TestFunctionNameViolationDetector.ISSUE,
+ MissingApacheLicenseDetector.ISSUE,
)
override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
new file mode 100644
index 0000000..78e133f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import java.time.Year
+import org.junit.Test
+
+class MissingApacheLicenseDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector {
+ return MissingApacheLicenseDetector()
+ }
+
+ override fun getIssues(): List<Issue> {
+ return listOf(
+ MissingApacheLicenseDetector.ISSUE,
+ )
+ }
+
+ @Test
+ fun testHasCopyright() {
+ lint()
+ .files(
+ kotlin(
+ """
+ /*
+ * Copyright (C) ${Year.now().value} 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 test.pkg.name
+
+ class MyTest
+ """
+ .trimIndent()
+ )
+ )
+ .issues(MissingApacheLicenseDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testDoesntHaveCopyright() {
+ lint()
+ .files(
+ kotlin(
+ """
+ package test.pkg.name
+
+ class MyTest
+ """
+ .trimIndent()
+ )
+ )
+ // skipping mode SUPPRESSIBLE because lint tries to add @Suppress to class which
+ // probably doesn't make much sense for license header (which is far above it) and for
+ // kotlin files that can have several classes. If someone really wants to omit header
+ // they can do it with //noinspection
+ .skipTestModes(TestMode.SUPPRESSIBLE)
+ .issues(MissingApacheLicenseDetector.ISSUE)
+ .run()
+ .expectContains("License header is missing")
+ }
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
index 4ed78b3..33024f7 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.layout.ui.compose
+import android.util.SizeF
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -54,7 +55,14 @@
Row(
modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
) {
- cardInfo.card.Content(Modifier.fillMaxSize())
+ cardInfo.card.Content(
+ modifier = Modifier.fillMaxSize(),
+ size =
+ SizeF(
+ layoutConfig.cardWidth.value,
+ layoutConfig.cardHeight(cardInfo.size).value,
+ ),
+ )
}
}
}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
index ac8aa67..4b2a156 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.layout.ui.compose.config
+import android.util.SizeF
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -26,8 +27,11 @@
*
* To host non-Compose views, see
* https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
+ *
+ * @param size The size given to the card. Content of the card should fill all this space, given
+ * that margins and paddings have been taken care of by the layout.
*/
- @Composable abstract fun Content(modifier: Modifier)
+ @Composable abstract fun Content(modifier: Modifier, size: SizeF)
/**
* Sizes supported by the card.
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
index fdf65f5..c1974ca 100644
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.communal.layout
+import android.util.SizeF
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -91,7 +92,7 @@
override val supportedSizes = listOf(size)
@Composable
- override fun Content(modifier: Modifier) {
+ override fun Content(modifier: Modifier, size: SizeF) {
Card(modifier = modifier, content = {})
}
}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index c6e429a..ddd1c67 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -72,6 +72,10 @@
throwComposeUnavailableError()
}
+ override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+ throwComposeUnavailableError()
+ }
+
private fun throwComposeUnavailableError(): Nothing {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 1722685..eeda6c6 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,6 +30,7 @@
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
@@ -104,6 +105,12 @@
}
}
+ override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
+ }
+ }
+
// TODO(b/298525212): remove once Compose exposes window inset bounds.
private fun displayCutoutFromWindowInsets(
scope: CoroutineScope,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
new file mode 100644
index 0000000..46d418a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -0,0 +1,123 @@
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+object Scenes {
+ val Blank = SceneKey(name = "blank")
+ val Communal = SceneKey(name = "communal")
+}
+
+object Communal {
+ object Elements {
+ val Content = ElementKey("CommunalContent")
+ }
+}
+
+val sceneTransitions = transitions {
+ from(Scenes.Blank, to = Scenes.Communal) {
+ spec = tween(durationMillis = 500)
+
+ translate(Communal.Elements.Content, Edge.Right)
+ fade(Communal.Elements.Content)
+ }
+}
+
+/**
+ * View containing a [SceneTransitionLayout] that shows the communal UI and handles transitions.
+ *
+ * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
+ * handling and transitions before the full Flexiglass layout is ready.
+ */
+@Composable
+fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
+ val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
+
+ // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
+ var showSceneTransitionLayout by remember { mutableStateOf(true) }
+ if (!showSceneTransitionLayout) {
+ return
+ }
+
+ SceneTransitionLayout(
+ modifier = modifier.fillMaxSize(),
+ currentScene = currentScene,
+ onChangeScene = setCurrentScene,
+ transitions = sceneTransitions,
+ ) {
+ scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
+ BlankScene { showSceneTransitionLayout = false }
+ }
+
+ scene(
+ Scenes.Communal,
+ userActions = mapOf(Swipe.Right to Scenes.Blank),
+ ) {
+ CommunalScene(viewModel, modifier = modifier)
+ }
+ }
+}
+
+/**
+ * Blank scene that shows over keyguard/dream. This scene will eventually show nothing at all and is
+ * only used to allow for transitions to the communal scene.
+ */
+@Composable
+private fun BlankScene(
+ modifier: Modifier = Modifier,
+ hideSceneTransitionLayout: () -> Unit,
+) {
+ Box(modifier.fillMaxSize()) {
+ Column(
+ Modifier.fillMaxHeight()
+ .width(100.dp)
+ .align(Alignment.CenterEnd)
+ .background(Color(0x55e9f2eb)),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text("Default scene")
+
+ IconButton(onClick = hideSceneTransitionLayout) {
+ Icon(Icons.Filled.Close, contentDescription = "Close button")
+ }
+ }
+ }
+}
+
+/** Scene containing the glanceable hub UI. */
+@Composable
+private fun SceneScope.CommunalScene(
+ viewModel: CommunalViewModel,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 3d827fb..b8fb264 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1,5 +1,8 @@
package com.android.systemui.communal.ui.compose
+import android.appwidget.AppWidgetHostView
+import android.os.Bundle
+import android.util.SizeF
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -12,9 +15,12 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R
@@ -24,6 +30,7 @@
viewModel: CommunalViewModel,
) {
val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
+ val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList())
Box(
modifier = modifier.fillMaxSize().background(Color.White),
) {
@@ -36,7 +43,7 @@
gridHeight = dimensionResource(R.dimen.communal_grid_height),
gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
),
- communalCards = if (showTutorial) tutorialContent else emptyList(),
+ communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
)
}
}
@@ -58,8 +65,37 @@
override val supportedSizes = listOf(size)
@Composable
- override fun Content(modifier: Modifier) {
+ override fun Content(modifier: Modifier, size: SizeF) {
Card(modifier = modifier, content = {})
}
}
}
+
+private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
+ return object : CommunalGridLayoutCard() {
+ override val supportedSizes = listOf(convertToCardSize(model.size))
+ override val priority = model.priority
+
+ @Composable
+ override fun Content(modifier: Modifier, size: SizeF) {
+ AndroidView(
+ modifier = modifier,
+ factory = {
+ model.view.apply {
+ if (this is AppWidgetHostView) {
+ updateAppWidgetSize(Bundle(), listOf(size))
+ }
+ }
+ },
+ )
+ }
+ }
+}
+
+private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
+ return when (size) {
+ CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
+ CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
+ CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
+ }
+}
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
new file mode 100755
index 0000000..5db27d8
--- /dev/null
+++ b/packages/SystemUI/flag_check.py
@@ -0,0 +1,134 @@
+#! /usr/bin/env python3
+
+import sys
+import re
+import argparse
+
+# partially copied from tools/repohooks/rh/hooks.py
+
+TEST_MSG = """Commit message is missing a "Flag:" line. It must match one of the
+following case-sensitive regex:
+
+ %s
+
+The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
+
+As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the
+flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD).
+
+Some examples below:
+
+Flag: NONE
+Flag: NA
+Flag: LEGACY ENABLE_ONE_SEARCH DISABLED
+Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT
+Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD
+
+Check the git history for more examples. It's a regex matched field.
+"""
+
+def main():
+ """Check the commit message for a 'Flag:' line."""
+ parser = argparse.ArgumentParser(
+ description='Check the commit message for a Flag: line.')
+ parser.add_argument('--msg',
+ metavar='msg',
+ type=str,
+ nargs='?',
+ default='HEAD',
+ help='commit message to process.')
+ parser.add_argument(
+ '--files',
+ metavar='files',
+ nargs='?',
+ default='',
+ help=
+ 'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.')
+ parser.add_argument(
+ '--project',
+ metavar='project',
+ type=str,
+ nargs='?',
+ default='',
+ help=
+ 'REPO_PATH in repo upload to determine whether the check should run for this project.')
+
+ # Parse the arguments
+ args = parser.parse_args()
+ desc = args.msg
+ files = args.files
+ project = args.project
+
+ if not should_run_path(project, files):
+ return
+
+ field = 'Flag'
+ none = '(NONE|NA|N\/A)' # NONE|NA|N/A
+
+ typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG]
+
+ # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH
+ # Aconfig Flag name format = "packageName"."flagName"
+ # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
+ # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check.
+ #common_typos_disable
+ flagName = '([a-zA-z0-9_.])+'
+
+ #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)]
+ stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)'
+ #common_typos_enable
+
+ readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD'
+
+ flagRegex = fr'^{field}: .*$'
+ check_flag = re.compile(flagRegex) #Flag:
+
+ # Ignore case for flag name format.
+ flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*'
+ check_flagName = re.compile(flagNameRegex) #Flag: <flag name format>
+
+ flagError = False
+ foundFlag = []
+ # Check for multiple "Flag:" lines and all lines should match this format
+ for line in desc.splitlines():
+ if check_flag.match(line):
+ if not check_flagName.match(line):
+ flagError = True
+ break
+ foundFlag.append(line)
+
+ # Throw error if
+ # 1. No "Flag:" line is found
+ # 2. "Flag:" doesn't follow right format.
+ if (not foundFlag) or (flagError):
+ error = TEST_MSG % (readableRegexMsg)
+ print(error)
+ sys.exit(1)
+
+ sys.exit(0)
+
+
+def should_run_path(path, files):
+ """Returns a boolean if this check should run with these paths.
+ If you want to check for a particular subdirectory under the path,
+ add a check here, call should_run_files and check for a specific sub dir path in should_run_files.
+ """
+ if not path:
+ return False
+ if path == 'frameworks/base':
+ return should_run_files(files)
+ # Default case, run for all other paths which calls this script.
+ return True
+
+
+def should_run_files(files):
+ """Returns a boolean if this check should run with these files."""
+ if not files:
+ return False
+ if 'packages/SystemUI' in files:
+ return True
+ return False
+
+
+if __name__ == '__main__':
+ main()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/multivalentTestsForDevice b/packages/SystemUI/multivalentTestsForDevice
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/packages/SystemUI/multivalentTestsForDevice
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTestsForDeviceless b/packages/SystemUI/multivalentTestsForDeviceless
new file mode 120000
index 0000000..20ee34a
--- /dev/null
+++ b/packages/SystemUI/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+multivalentTests
\ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 9cc87fd..f0e3c99 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -57,6 +57,16 @@
@Nullable ActivityLaunchAnimator.Controller animationController);
/**
+ * Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching
+ * activities on top of the keyguard. If the activity supports {@code showOverLockscreen}, it
+ * will show over keyguard without first dimissing it. If it doesn't support it, calling this
+ * method is exactly the same as calling {@link #startPendingIntentDismissingKeyguard}.
+ */
+ void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent,
+ @Nullable Runnable intentSentUiThreadCallback,
+ @Nullable ActivityLaunchAnimator.Controller animationController);
+
+ /**
* The intent flag can be specified in startActivity().
*/
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
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 a9d2ee3..403c7c5 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 @@
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 @@
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 @@
}
/**
- * @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 @@
}
/**
- * @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-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 0ace8a7..a4b13487 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -130,5 +130,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 0ace8a7..a4b13487 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -130,5 +130,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 0ace8a7..a4b13487 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -130,5 +130,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 3faa7ca..68f22cb 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -130,5 +130,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Debuxa o padrón para instalar a actualización máis tarde"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Actualizouse o dispositivo. Mete o PIN para continuar."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Actualizouse o dispositivo. Mete o contrasinal para continuar."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Actualizouse o dispositivo. Debuxa o padrón para continuar."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Actualizouse o dispositivo. Debuxa o padrón."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index bc66355..00c717c 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -130,5 +130,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"צריך למתוח את קו ביטול הנעילה כדי להתקין את העדכון מאוחר יותר"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"המכשיר עודכן. צריך להזין את קוד האימות כדי להמשיך."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"המכשיר עודכן. צריך להזין את הסיסמה כדי להמשיך."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"המכשיר עודכן. צריך למתוח את קו ביטול הנעילה כדי להמשיך."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"המכשיר עודכן. יש למתוח קו ביטול נעילה כדי להמשיך"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index 532253e..0e09fad 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -129,6 +129,6 @@
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"나중에 업데이트를 설치하려면 비밀번호를 입력하세요."</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"나중에 업데이트를 설치하려면 패턴을 그리세요."</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"기기가 업데이트되었습니다. 계속하려면 PIN을 입력하세요."</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"기기가 업데이트되었습니다. 계속 진행하려면 비밀번호를 입력하세요."</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"기기가 업데이트되었습니다. 계속하려면 비밀번호를 입력하세요."</string>
<string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"기기가 업데이트되었습니다. 계속하려면 패턴을 그리세요."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index e85cf8a..576250b 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -128,7 +128,7 @@
<string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် ပင်နံပါတ်ထည့်ပါ"</string>
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် စကားဝှက်ထည့်ပါ"</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် ပုံဖော်ရေးဆွဲပါ"</string>
- <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပင်နံပါတ်ထည့်ပါ။"</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် စကားဝှက်ထည့်ပါ။"</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပုံဖော်ရေးဆွဲပါ။"</string>
+ <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပင်နံပါတ်ထည့်ပါ။"</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် စကားဝှက်ထည့်ပါ။"</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပုံဖော်ရေးဆွဲပါ။"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index a236639..1ba4a81 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -130,5 +130,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Teken het patroon om de update later te installeren"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Apparaat geüpdatet. Voer de pincode in om door te gaan."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Apparaat geüpdatet. Voer het wachtwoord in om door te gaan."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Apparaat geüpdatet. Teken het patroon om door te gaan."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Apparaat geüpdatet. Teken patroon om door te gaan."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index 4309b56..df28b8d 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -130,5 +130,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează pentru a instala actualizarea mai târziu"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Dispozitivul s-a actualizat. Introdu codul PIN pentru a continua."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Dispozitivul s-a actualizat. Introdu parola pentru a continua."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitivul s-a actualizat. Desenează modelul pentru a continua."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitiv actualizat. Desenează modelul și continuă."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 45149a5..62249a1 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -128,7 +128,7 @@
<string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Введите PIN-код, чтобы установить обновление позже."</string>
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Введите пароль, чтобы установить обновление позже."</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Введите графический ключ, чтобы установить обновление позже."</string>
- <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Устройство обновлено. Введите PIN-код."</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Устройство обновлено. Введите пароль."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Устройство обновлено. Введите графический ключ."</string>
+ <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Устройство обновлено. Чтобы продолжить, введите PIN-код."</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Устройство обновлено. Чтобы продолжить, введите пароль."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Устройство обновлено. Чтобы продолжить, введите графический ключ."</string>
</resources>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 5a70b79..452bc31 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 acee425..7e03bd9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,24 +26,6 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
- <com.android.systemui.statusbar.BackDropView
- android:id="@+id/backdrop"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- sysui:ignoreRightInset="true"
- >
- <ImageView android:id="@+id/backdrop_back"
- android:layout_width="match_parent"
- android:scaleType="centerCrop"
- android:layout_height="match_parent" />
- <ImageView android:id="@+id/backdrop_front"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop"
- android:visibility="invisible" />
- </com.android.systemui.statusbar.BackDropView>
-
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
@@ -63,7 +45,8 @@
<com.android.systemui.statusbar.LightRevealScrim
android:id="@+id/light_reveal_scrim"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ sysui:ignoreRightInset="true" />
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
@@ -83,6 +66,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
+ <ViewStub
+ android:id="@+id/communal_ui_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<include layout="@layout/brightness_mirror_container" />
<com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 24846d9..f68dd7b 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Stoor tans skermskoot in werkprofiel …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skermkiekie is gestoor"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kon nie skermkiekie stoor nie"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Toestel moet ontsluit word voordat skermkiekie gestoor kan word"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer weer skermkiekie neem"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan nie skermkiekie stoor nie"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Saai tans uit"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Onbenoemde toestel"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Geen toestelle beskikbaar nie"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi is nie gekoppel nie"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleuromkering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurregstelling"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans vinnig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans stadig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik die pylknoppie om die gemeenskaplike tutoriaal te begin"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 23def28..20a8120 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ቅጽበታዊ ገፅ እይታን ወደ የስራ መገለጫ በማስቀመጥ ላይ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ቅጽበታዊ ገፅ ዕይታ ተቀምጧል"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ቅጽበታዊ ገፅ ዕይታን ማስቀመጥ አልተቻለም"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ቅጽበታዊ ገፅ ዕይታ ከመቀመጡ በፊት መሣሪያ መከፈት አለበት"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ቅጽበታዊ ገፅ ዕይታን እንደገና ማንሳት ይሞክሩ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ቅጽበታዊ ገፅ እይታን ማስቀመጥ አልተቻለም"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"በመውሰድ ላይ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ያልተሰየመ መሣሪያ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ምንም መሣሪያዎች አይገኙም"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi አልተገናኘም"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ብሩህነት"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ተቃራኒ ቀለም"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"የቀለም ማስተካከያ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በፍጥነት ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በዝግታ ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ኃይል በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"የጋራ አጋዥ ሥልጠናን ለመጀመር የቀስት አዝራሩ ላይ ጠቅ ያድርጉ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 80d63a2..03e019c 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"جارٍ حفظ لقطة الشاشة في الملف الشخصي للعمل…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"تم حفظ لقطة الشاشة."</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"تعذّر حفظ لقطة الشاشة"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"يجب أن يتم فتح قفل الجهاز قبل حفظ لقطة الشاشة."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"جرّب أخذ لقطة الشاشة مرة أخرى"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"يتعذّر حفظ لقطة الشاشة."</string>
@@ -159,7 +161,7 @@
<string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"النقش غير صحيح."</string>
<string name="biometric_dialog_wrong_password" msgid="69477929306843790">"كلمة مرور غير صحيحة"</string>
<string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"تم إجراء عدد كبير جدًا من المحاولات غير الصحيحة.\nأعد المحاولة خلال <xliff:g id="NUMBER">%d</xliff:g> ثانية."</string>
- <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"الطوارئ"</string>
+ <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"طوارئ"</string>
<string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"يُرجى إعادة المحاولة. المحاولة <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> من <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g>"</string>
<string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"سيتم حذف بياناتك"</string>
<string name="biometric_dialog_last_pattern_attempt_before_wipe_device" msgid="6562299244825817598">"عند إدخال نقش غير صحيح في المحاولة التالية، سيتم حذف بيانات هذا الجهاز."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"جارٍ الإرسال"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"جهاز لا يحمل اسمًا"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"لا يتوفر أي جهاز"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"لم يتم الاتصال بشبكة Wi-Fi."</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"السطوع"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"قلب الألوان"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحيح الألوان"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن سريعًا • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن ببطء • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"انقر على زر السهم لبدء الدليل التوجيهي العام."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index c845773..014e3b7 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"কৰ্মস্থানৰ প্ৰ’ফাইলত স্ক্ৰীনশ্বট ছেভ কৰি থকা হৈছে…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্ৰীনশ্বট ছেভ কৰা হ’ল"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্ৰীনশ্বট ছেভ কৰিব পৰা নগ\'ল"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্ৰীনশ্বট ছেভ কৰিবলৈ ডিভাইচটো আনলক কৰিবই লাগিব"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"স্ক্ৰীনশ্বট আকৌ ল\'বলৈ চেষ্টা কৰক"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্ৰীনশ্বট ছেভ কৰিব নোৱাৰি"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"কাষ্টিং"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"নাম নথকা ডিভাইচ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"কোনো ডিভাইচ নাই"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ৱাই-ফাইৰ সৈতে সংযোগ হৈ থকা নাই"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ৰং বিপৰীতকৰণ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ৰং শুধৰণী"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্ৰুতগতিৰে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • লাহে লাহে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"সম্প্ৰদায় সম্পৰ্কীয় নিৰ্দেশনা আৰম্ভ কৰিবলৈ কাঁড়চিহ্নৰ বুটামটোত ক্লিক কৰক"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 5aa26ba..d34ad9a 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"İş profili skrinşotu saxlanılır…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinşot yadda saxlandı"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinşotu yadda saxlamaq alınmadı"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skrinşotu saxlamazdan əvvəl cihaz kiliddən çıxarılmalıdır"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Skrinşotu yenidən çəkin"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Skrinşotu yadda saxlamaq mümkün olmadı"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Yayım"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Adsız cihaz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Heç bir cihaz əlçatan deyil"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi qoşulu deyil"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaqlıq"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rəng inversiyası"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Rəng korreksiyası"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sürətlə şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Asta şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"İcma təlimatını başlatmaq üçün ox düyməsinə klikləyin"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index cd36884..2b19e36 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Snimak ekrana se čuva na poslovnom profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Čuvanje snimka ekrana nije uspelo"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Uređaj mora da bude otključan da bi snimak ekrana mogao da se sačuva"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probajte da ponovo napravite snimak ekrana"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Čuvanje snimka ekrana nije uspelo"</string>
@@ -101,8 +103,8 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađujemo video snimka ekrana"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Obaveštenje o sesiji snimanja ekrana je aktivno"</string>
<string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Želite da započnete snimanje?"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"Započni snimanje"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Snimaj zvuk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk uređaja"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nije dostupan nijedan uređaj"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi nije povezan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Puni se • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite na dugme sa strelicom da biste započeli zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string>
@@ -418,17 +422,17 @@
<string name="screen_share_permission_dialog_option_single_app" msgid="4350961814397220929">"Jedna aplikacija"</string>
<string name="screen_share_permission_app_selector_title" msgid="1404878013670347899">"Delite ili snimite aplikaciju"</string>
<string name="media_projection_entry_app_permission_dialog_title" msgid="9155535851866407199">"Želite da počnete snimanje ili prebacivanje pomoću aplikacije <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Pokreni"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je onemogućila ovu opciju"</string>
<string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"Želite da započnete prebacivanje?"</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Kada prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Kada prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Kada prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Kada prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Započni prebacivanje"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Želite da počnete da delite?"</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
<string name="media_projection_task_switcher_text" msgid="590885489897412359">"Deljenje se zaustavlja kada menjate aplikacije"</string>
<string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Deli ovu aplikaciju"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 1b03c8b..301a042 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Захаванне здымка экрана ў працоўны профіль…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Здымак экрана захаваны"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не атрымалася зрабіць здымак экрана"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Перад захаваннем здымка экрана трэба разблакіраваць прыладу"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Паспрабуйце зрабіць здымак экрана яшчэ раз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не ўдалося захаваць здымак экрана"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Ідзе перадача"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Прылада без назвы"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Няма даступных прылад"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Няма падключэння да Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркасць"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія колераў"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Карэкцыя колераў"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе хуткая зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе павольная зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Націсніце кнопку са стрэлкай, каб азнаёміцца з дапаможнікам"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 4ed1ad9..29f9d9e9 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Екранната снимка се запазва в служебния профил…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Екранната снимка е запазена"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не можа да се запази екранна снимка"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"За да бъде запазена екранната снимка, устройството трябва да бъде отключено"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Опитайте да направите екранна снимка отново"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Екранната снимка не може да се запази"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Предава се"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Устройство без име"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Няма налични устройства"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Не е установена връзка с Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркост"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инвертиране на цветовете"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекция на цветове"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бързо • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бавно • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликнете върху бутона със стрелка, за да стартирате общия урок"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 426d38d..c2662d1 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"অফিস প্রোফাইলে স্ক্রিনশট সেভ করা হচ্ছে…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্রিনশট সেভ করা হয়েছে"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্রিনশট সেভ করা যায়নি"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্রিনশট সেভ করার আগে ডিভাইসটি অবশ্যই আনলক করতে হবে"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"আবার স্ক্রিনশট নেওয়ার চেষ্টা করুন"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্রিনশট সেভ করা যায়নি"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"কাস্ট করা হচ্ছে"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"নামবিহীন ডিভাইস"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"কোনো ডিভাইস উপলব্ধ নয়"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ওয়াই-ফাই কানেক্ট করা নেই"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"কালার ইনভার্সন"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"রঙ সংশোধন"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্রুত চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ধীরে চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চার্জ হচ্ছে • পুরো চার্জ হতে আরও <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> সময় লাগবে"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"কমিউনিটি টিউটোরিয়াল চালু করতে তীরচিহ্ন বোতামে ক্লিক করুন"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 4eed7b89..8d10454 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pohranjivanje snimka ekrana na radni profil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nije moguće sačuvati snimak ekrana"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Morate otključati uređaj da možete sačuvati snimak ekrana"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pokušajte ponovo snimiti ekran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nije moguće sačuvati snimak ekrana"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nema dostupnih uređaja"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi mreža nije povezana"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvjetljenje"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ispravka boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite na dugme sa strelicom da pokrenete zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index b55a79a..683c034 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"S\'està desant la captura al perfil de treball…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"S\'ha desat la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No s\'ha pogut desar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositiu ha d\'estar desbloquejat abans que la captura de pantalla es pugui desar"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prova de tornar a fer una captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No es pot desar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"En emissió"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositiu sense nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hi ha cap dispositiu disponible."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"La Wi‑Fi no està connectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversió de colors"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correcció de color"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant ràpidament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant lentament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fes clic al botó de la fletxa per iniciar el tutorial de la comunitat"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 05b0419..cbbf9df 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukládání snímku obrazovky do pracovního profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snímek obrazovky byl uložen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snímek obrazovky se nepodařilo uložit"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Aby bylo možné uložit screenshot, zařízení musí být odemknuto"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Zkuste snímek pořídit znovu"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Snímek obrazovky se nepodařilo uložit"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Odesílání"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nepojmenované zařízení"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nejsou dostupná žádná zařízení"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Není připojena Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Převrácení barev"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekce barev"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Rychlé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Pomalé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknutím na tlačítko s šipkou spustíte společný výukový program"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 5971034..57124bd 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gemmer screenshot på din arbejdsprofil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshottet blev gemt"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshottet kunne ikke gemmes"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enheden skal være låst op, før du kan gemme screenshots"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prøv at tage et screenshot igen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Dit screenshot kunne ikke gemmes."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Caster"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhed uden navn"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Der er ingen tilgængelige enheder"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Manglende Wi-Fi-forbindelse"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ombytning af farver"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farvekorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader hurtigt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader langsomt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik på pilen for at starte den fælles vejledning"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index c773f71..8c308b9 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot wird in Arbeitsprofil gespeichert…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot gespeichert"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshot konnte nicht gespeichert werden"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Damit Screenshots gespeichert werden können, muss das Gerät entsperrt sein"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Versuche noch einmal, den Screenshot zu erstellen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Screenshot kann nicht gespeichert werden"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Wird übertragen"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unbenanntes Gerät"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Keine Geräte verfügbar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WLAN nicht verbunden"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helligkeit"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Farbumkehr"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farbkorrektur"</string>
@@ -395,6 +398,8 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird schnell geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird langsam geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <!-- no translation found for communal_tutorial_indicator_text (700342473477865107) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index b6c95aa..69d877b 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Αποθήκευση στιγμιότ. οθόνης στο προφίλ εργασίας…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Το στιγμιότυπο οθόνης αποθηκεύτηκε"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Μη δυνατή αποθήκευση του στιγμιότυπου οθόνης"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Η συσκευή πρέπει να ξεκλειδωθεί για να αποθηκευτεί το στιγμιότυπο οθόνης."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Δοκιμάστε να κάνετε ξανά λήψη του στιγμιότυπου οθόνης"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Δεν είναι δυνατή η αποθήκευση στιγμιότυπου οθόνης."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Μετάδοση"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Ανώνυμη συσκευή"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Δεν υπάρχουν διαθέσιμες συσκευές"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Το Wi-Fi δεν είναι συνδεδεμένο"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Φωτεινότητα"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Αντιστροφή χρωμάτων"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Διόρθωση χρωμάτων"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Γρήγορη φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Αργή φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Κάντε κλικ στο κουμπί βέλους για να ξεκινήσετε τον κοινόχρηστο οδηγό"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 6c2b230..645f70e 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 0e282ff..f05d3c0 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -76,6 +76,7 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External Display"</string>
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +281,7 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <string name="quick_settings_cast_no_network" msgid="3863016850468559522">"No Wi‑Fi or Ethernet connection"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Color inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Color correction"</string>
@@ -395,6 +396,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 6c2b230..645f70e 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 6c2b230..645f70e 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 1f3ab27..4934d73 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -76,6 +76,7 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External Display"</string>
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +281,7 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <string name="quick_settings_cast_no_network" msgid="3863016850468559522">"No Wi‑Fi or Ethernet connection"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Color inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Color correction"</string>
@@ -395,6 +396,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 52514d2..fa1a3f5 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando cap. de pantalla en perfil de trabajo…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Se guardó la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No se pudo guardar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe estar desbloqueado para poder guardar la captura de pantalla"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a hacer una captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se pudo guardar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitiendo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sin nombre"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hay dispositivos disponibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Red Wi-Fi no conectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corregir colores"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lento • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Haz clic en el botón de flecha para iniciar el instructivo comunal"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 4a3c064..005895b 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando captura en el perfil de trabajo…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Se ha guardado la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No se ha podido guardar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe desbloquearse para que se pueda guardar la captura de pantalla"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a intentar hacer la captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se puede guardar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Enviando"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sin nombre"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hay dispositivos disponibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi no conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de color"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Carga completa en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Haz clic en el botón de la flecha para iniciar el tutorial de la comunidad"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 08b489d..b4463d2 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekraanipildi salvestamine tööprofiilile …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekraanipilt salvestati"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekraanipilti ei õnnestunud salvestada"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enne ekraanipildi salvestamist tuleb seade avada"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Proovige ekraanipilt uuesti jäädvustada"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekraanipilti ei saa salvestada"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Osatäitjad"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimeta seade"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ühtegi seadet pole saadaval"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi-ühendus puudub"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Heledus"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Värvide ümberpööramine"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värviparandus"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kiirlaadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Aeglane laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ühise õpetuse käivitamiseks klõpsake noolenuppu"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 19495bc..6701a77 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pantaila-argazkia laneko profilean gordetzen…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Gorde da pantaila-argazkia"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ezin izan da gorde pantaila-argazkia"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pantaila-argazkia gordetzeko, gailuak desblokeatuta egon beharko du"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Saiatu berriro pantaila-argazkia ateratzen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ezin da gorde pantaila-argazkia"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Igortzen"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Izenik gabeko gailua"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ez dago gailurik erabilgarri"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ez zaude konektatuta wifi-sarera"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Koloreen alderantzikatzea"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Bizkor kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mantso kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Tutorial komuna hasteko, sakatu geziaren botoia"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 68f6c1d..705cc7c 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"درحال ذخیره کردن نماگرفت در نمایه کاری…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"نماگرفت ذخیره شد"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"نماگرفت ذخیره نشد"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"برای ذخیره کردن نماگرفت، قفل دستگاه باید باز باشد"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"دوباره نماگرفت بگیرید"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"نماگرفت ذخیره نمیشود"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"در حال فرستادن"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"دستگاه بدون نام"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"دستگاهی موجود نیست"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi وصل نیست"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"روشنایی"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"وارونگی رنگ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحیح رنگ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن سریع • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن آهسته • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ شدن • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"برای شروع آموزش گامبهگام عمومی، روی دکمه جهتنما کلیک کنید"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایینپر"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامهها و دادههای این جلسه حذف خواهد شد."</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 934abad..d1c583b 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Kuvakaappausta tallennetaan työprofiiliin…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Kuvakaappaus tallennettu"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kuvakaappauksen tallennus epäonnistui"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Laitteen lukitus täytyy avata ennen kuin kuvakaappaus voidaan tallentaa"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Yritä ottaa kuvakaappaus uudelleen."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kuvakaappausta ei voi tallentaa"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Lähetetään"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimetön laite"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Laitteita ei ole käytettävissä"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fiä ei ole yhdistetty"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kirkkaus"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Käänteiset värit"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värinkorjaus"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu nopeasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu hitaasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Aloita yhteisöesittely klikkaamalla nuolipainiketta"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index fe90569..366d715 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sauv. de la capture dans le profil prof. en cours…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"L\'appareil doit être déverrouillé avant qu\'une capture d\'écran puisse être enregistrée"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Essayez de faire une autre capture d\'écran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossible d\'enregistrer la capture d\'écran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Diffusion"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Appareil sans nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Aucun appareil à proximité"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Non connecté au Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"En recharge rapide : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"En recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge en cours… • Se terminera dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Cliquez sur le bouton de flèche pour démarrer le tutoriel communautaire"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 27a4bb6..8a51092 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Enregistrement de capture d\'écran dans profil pro…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Vous devez déverrouiller l\'appareil pour que la capture d\'écran soit enregistrée"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Essayez de nouveau de faire une capture d\'écran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossible d\'enregistrer la capture d\'écran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Diffusion"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Appareil sans nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Aucun appareil disponible."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi non connecté"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge rapide • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge lente • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Cliquer sur la flèche pour démarrer le tutoriel collectif"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 2056c2f..e8aa65f 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gardando captura de pantalla no perfil de traballo"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Gardouse a captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Non se puido gardar a captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que se poida gardar a captura de pantalla, o dispositivo debe estar desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Volve tentar crear unha captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Non se puido gardar a captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Emitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sen nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Non hai dispositivos dispoñibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"A wifi non está conectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión da cor"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección da cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando rapidamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lentamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fai clic no botón da frecha para iniciar o titorial comunitario"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 84be50a..15b1b44 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ઑફિસની પ્રોફાઇલમાં સ્ક્રીનશૉટ સાચવી રહ્યાં છીએ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"સ્ક્રીનશૉટ સાચવ્યો"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"સ્ક્રીનશૉટ સાચવી શક્યાં નથી"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"સ્ક્રીનશૉટ સાચવવામાં આવે તે પહેલાં ડિવાઇસને અનલૉક કરવું જરૂરી છે"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ફરીથી સ્ક્રીનશૉટ લેવાનો પ્રયાસ કરો"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"સ્ક્રીનશૉટ સાચવી શકાતો નથી"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"કાસ્ટ કરી રહ્યાં છે"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"અનામાંકિત ઉપકરણ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"કોઈ ઉપકરણો ઉપલબ્ધ નથી"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"વાઇ-ફાઇ કનેક્ટ નથી"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"તેજ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"વિપરીત રંગમાં બદલવું"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"રંગ સુધારણા"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ઝડપથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ધીમેથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં પૂરું ચાર્જ થઈ જશે"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"કૉમ્યુનલ ટ્યૂટૉરિઅલ શરૂ કરવા માટે ઍરો બટન પર ક્લિક કરો"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 2b0019f..d63f5cb 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"स्क्रीनशॉट, वर्क प्रोफ़ाइल में सेव किया जा रहा है…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव किया गया"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव नहीं किया जा सका"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव करने के लिए डिवाइस का अनलॉक होना ज़रूरी है"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट दोबारा लेने की कोशिश करें"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट को सेव नहीं किया जा सकता"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"कास्टिंग"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"अनाम डिवाइस"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कोई डिवाइस उपलब्ध नहीं"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाई-फ़ाई कनेक्ट नहीं है"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"स्क्रीन की रोशनी"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"रंग बदलने की सुविधा"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग में सुधार करने की सुविधा"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • तेज़ चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • धीरे चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"कम्यूनिटी ट्यूटोरियल शुरू करने के लिए, तीर के निशान वाले बटन पर क्लिक करें"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 23899fc..c3ea5ff2 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Spremanje snimke zaslona na poslovni profil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimka zaslona spremljena"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snimka zaslona nije spremljena"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Uređaj mora biti otključan da bi se snimka zaslona mogla spremiti"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pokušajte ponovo napraviti snimku zaslona"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nije moguće spremiti snimku zaslona"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Emitiranje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Uređaj bez naziva"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nema dostupnih uređaja"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi mreža nije povezana"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svjetlina"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • brzo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • sporo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite gumb sa strelicom da biste pokrenuli zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index e5b17d9..c894232 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Képernyőkép mentése a munkaprofilba…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"A képernyőkép mentése sikerült"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nem sikerült a képernyőkép mentése"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Az eszközt fel kell oldani a képernyőkép mentése előtt"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Próbálja meg újra elkészíteni a képernyőképet"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nem lehetséges a képernyőkép mentése"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Átküldés"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Név nélküli eszköz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nem áll rendelkezésre eszköz"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nem kapcsolódik Wi‑Fi-hálózathoz"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Fényerő"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Színek invertálása"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Színjavítás"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Gyors töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lassú töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kattintson a nyíl gombra a közösségi útmutató elindításához"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 4a1c291..67d3886 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Սքրինշոթը պահվում է աշխատանքային պրոֆիլում…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Սքրինշոթը պահվեց"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Չհաջողվեց պահել սքրինշոթը"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Սքրինշոթը պահելու համար ապակողպեք սարքը։"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Փորձեք նորից"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Չհաջողվեց պահել սքրինշոթը"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Հեռարձակում"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Անանուն սարք"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Հասանելի սարքեր չկան"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-ը միացված չէ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Պայծառություն"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Գունաշրջում"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Գունաշտկում"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Արագ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Դանդաղ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Սեղմեք սլաքի կոճակը՝ ուղեցույցը գործարկելու համար"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 18a7668..b1feb48 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan screenshot ke profil kerja …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot disimpan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Perangkat harus dibuka kuncinya agar screenshot dapat disimpan"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Coba ambil screenshot lagi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Tidak dapat menyimpan screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Melakukan transmisi"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Perangkat tanpa nama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Perangkat tak tersedia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi tidak terhubung"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversi warna"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koreksi warna"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan cepat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan lambat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik tombol panah untuk memulai tutorial komunal"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 70bed46..9ff9c1a 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Vistar skjámynd á vinnusnið…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skjámynd vistuð"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekki var hægt að vista skjámynd"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Taka verður tækið úr lás áður en hægt er að vista skjámynd"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prófaðu að taka skjámynd aftur"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekki er hægt að vista skjámynd"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Sendir út"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Ónefnt tæki"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Engin tæki til staðar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ekki tengt"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Birtustig"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Umsnúningur lita"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Litaleiðrétting"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hraðhleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hæg hleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Í hleðslu • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Smelltu á örvahnappinn til að hefja samfélagsleiðsögnina"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index baffdb9..7fafa62 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvataggio screenshot nel profilo di lavoro…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot salvato"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossibile salvare lo screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"È necessario sbloccare il dispositivo per poter salvare lo screenshot"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Riprova ad acquisire lo screenshot"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossibile salvare lo screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"In trasmissione"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo senza nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nessun dispositivo disponibile"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nessuna connessione Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosità"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversione dei colori"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correzione del colore"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica veloce • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica lenta • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • In carica • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fai clic sul pulsante Freccia per iniziare il tutorial della community"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 968a982..ced5895 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"צילום המסך נשמר בפרופיל העבודה…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"צילום המסך נשמר"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"לא ניתן היה לשמור את צילום המסך"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"כדי שצילום המסך יישמר, צריך לבטל את הנעילה של המכשיר"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"אפשר לצלם שוב את המסך"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"לא ניתן לשמור את צילום המסך"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"מתבצעת העברה (cast)"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"מכשיר ללא שם"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"אין מכשירים זמינים"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"אין חיבור ל-Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"בהירות"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"היפוך צבעים"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"תיקון צבע"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה מהירה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה איטית • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"אפשר ללחוץ על לחצן החץ כדי להפעיל את המדריך המשותף"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"החלפת משתמש"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"תפריט במשיכה למטה"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בסשן הזה יימחקו."</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 798d42a..f5d5b74 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"スクリーンショットを仕事用プロファイルに保存中…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"スクリーンショットを保存しました"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"スクリーンショット保存エラー"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"スクリーンショットを保存するには、デバイスのロックを解除する必要があります"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"スクリーンショットを撮り直してください"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"スクリーンショットを保存できません"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"キャストしています"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"名前のないデバイス"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"利用可能なデバイスがありません"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi 未接続"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"画面の明るさ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色反転"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色補正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 急速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 低速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • フル充電まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"矢印ボタンをクリックすると、コミュニティ チュートリアルが開始します"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index f21a2a4..ae93966 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"მიმდინარეობს ეკრანის ანაბეჭდის შენახვა სამუშაო პროფილში…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ეკრანის ანაბეჭდი შენახულია"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ეკრანის ანაბეჭდის შენახვა ვერ მოხერხდა"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"მოწყობილობა უნდა განიბლოკოს ეკრანის ანაბეჭდის შენახვა რომ შეძლოთ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ხელახლა ცადეთ ეკრანის ანაბეჭდის გაკეთება"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ეკრანის ანაბეჭდის შენახვა ვერ ხერხდება"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"გადაიცემა"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"უსახელო მოწყობილობა"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"მოწყობილობები მიუწვდომელია"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi არ არის დაკავშირებული"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"განათება"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ფერთა ინვერსია"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ფერთა კორექცია"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • სწრაფად იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ნელა იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"დააწკაპუნეთ ისრის ღილაკზე, რათა დაიწყოთ საერთო სახელმძღვანელო"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 746c02e..8c8efd1 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жұмыс профиліне сақталып жатыр…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сақталды"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сақталмады"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Скриншот сақталуы үшін, құрылғы құлпын ашу керек."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Қайта скриншот жасап көріңіз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Скриншотты сақтау мүмкін емес."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Трансляциялануда"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Атаусыз құрылғы"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Құрылғылар қол жетімді емес"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi желісіне жалғанбаған"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарықтығы"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түс инверсиясы"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсті түзету"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жылдам зарядтау • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Баяу зарядталуда • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядталып жатыр. • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Жалпы оқулықты ашу үшін бағыт түймесін басыңыз."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9f3b991..cefdd27 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"កំពុងរក្សាទុករូបថតអេក្រង់ទៅកម្រងព័ត៌មានការងារ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"បានរក្សាទុករូបថតអេក្រង់"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"មិនអាចរក្សាទុករូបថតអេក្រង់បានទេ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ត្រូវតែដោះសោឧបករណ៍ជាមុនសិន ទើបអាចរក្សាទុករូបថតអេក្រង់បាន"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"សាកល្បងថតរូបថតអេក្រង់ម្តងទៀត"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"មិនអាចរក្សាទុករូបថតអេក្រង់បានទេ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ការចាត់ថ្នាក់"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ឧបករណ៍ដែលមិនមានឈ្មោះ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"មិនមានឧបករណ៍ដែលអាចប្រើបាន"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"មិនមានការតភ្ជាប់ Wi-Fi ទេ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ពន្លឺ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ការបញ្ច្រាសពណ៌"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ការកែតម្រូវពណ៌"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្មយ៉ាងឆាប់រហ័ស • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្មយឺត • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្ម • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ចុចលើប៊ូតុងសញ្ញាព្រួញ ដើម្បីចាប់ផ្ដើមមេរៀនសហគមន៍"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរអ្នកប្រើ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយទាញចុះ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យទាំងអស់ក្នុងវគ្គនេះនឹងត្រូវលុប។"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 79eef72..6ec02ca 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ಗೆ ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲಾಗಿದೆ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸುವ ಮೊದಲು ಸಾಧನವನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಬೇಕು"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಪುನಃ ತೆಗೆದುಕೊಳ್ಳಲು ಪ್ರಯತ್ನಿಸಿ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ಹೆಸರಿಸದಿರುವ ಸಾಧನ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ಯಾವುದೇ ಸಾಧನಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ವೈ-ಫೈ ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ಕಲರ್ ಇನ್ವರ್ಶನ್"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ವೇಗವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ನಿಧಾನವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ಸಮುದಾಯದ ಟುಟೋರಿಯಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಆ್ಯರೋ ಬಟನ್ ಅನ್ನು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್ಡೌನ್ ಮೆನು"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಷನ್ನಲ್ಲಿನ ಎಲ್ಲ ಅಪ್ಲಿಕೇಶನ್ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 7985e4c..69525ee 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"직장 프로필에 스크린샷 저장 중…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"스크린샷 저장됨"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"스크린샷을 저장할 수 없음"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"스크린샷을 저장하려면 기기를 잠금 해제해야 합니다."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"스크린샷을 다시 찍어 보세요."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"스크린샷을 저장할 수 없습니다."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"전송 중"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"이름이 없는 기기"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"사용 가능한 기기가 없습니다."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi가 연결되지 않음"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"밝기"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"색상 반전"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"색상 보정"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 고속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 저속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"공동 튜토리얼을 시작하려면 화살표 버튼을 클릭하세요."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index ded1f75..400afed 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жумуш профилине сакталууда…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сакталды"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сакталган жок"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Скриншотту сактоо үчүн түзмөктүн кулпусун ачуу керек"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Скриншотту кайра тартып көрүңүз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Скриншот сакталган жок"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Тышкы экранга чыгарылууда"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Аты жок түзмөк"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Жеткиликтүү түзмөктөр жок"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi туташкан жок"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарыктыгы"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түстөрдү инверсиялоо"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түстөрдү тууралоо"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Тез кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жай кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Жалпы үйрөткүчтү иштетүү үчүн жебе баскычын басыңыз"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 587caaf..db526b1 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-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 1e61b8a..b66e6dc 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ກຳລັງບັນທຶກຮູບໜ້າຈໍໃສ່ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ບັນທຶກຮູບໜ້າຈໍໄວ້ແລ້ວ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ຈະຕ້ອງປົດລັອກອຸປະກອນກ່ອນບັນທຶກຮູບໜ້າຈໍ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ກະລຸນາລອງຖ່າຍຮູບໜ້າຈໍອີກຄັ້ງ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ກຳລັງສົ່ງສັນຍານ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ອຸປະກອນບໍ່ມີຊື່"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ບໍ່ມີອຸປະກອນທີ່ສາມາດໃຊ້ໄດ້"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ບໍ່ໄດ້ເຊື່ອມຕໍ່ Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ຄວາມແຈ້ງ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ການປີ້ນສີ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ການແກ້ໄຂສີ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບໄວ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບຊ້າ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ຄລິກໃສ່ປຸ່ມລູກສອນເພື່ອເລີ່ມຕົ້ນສອນການນຳໃຊ້ຊຸມຊົນ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯແລະຂໍ້ມູນທັງໝົດໃນເຊດຊັນນີ້ຈະຖືກລຶບອອກ."</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 06c1369..955fe67 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Išsaugoma ekrano kopija darbo profilyje…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrano kopija išsaugota"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrano kopijos išsaugoti nepavyko"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Įrenginys turi būti atrakintas, kad būtų galima išsaugoti ekrano kopiją"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pabandykite padaryti ekrano kopiją dar kartą"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekrano kopijos išsaugoti nepavyko"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Perduodama"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Įrenginys be pavadinimo"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nėra pasiekiamų įrenginių"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"„Wi-Fi“ neprijungtas"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Šviesumas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Spalvų inversija"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Spalvų taisymas"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sparčiai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lėtai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Spustelėkite rodyklės mygtuką, kad paleistumėte bendruomenės mokomąją medžiagą"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index fdf3028..16cef4b 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Notiek ekrānuzņēmuma saglabāšana darba profilā…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrānuzņēmums saglabāts"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrānuzņēmumu neizdevās saglabāt."</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Lai varētu saglabāt ekrānuzņēmumu, ierīcei ir jābūt atbloķētai."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Mēģiniet izveidot jaunu ekrānuzņēmumu."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nevar saglabāt ekrānuzņēmumu"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Notiek apraide…"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nenosaukta ierīce"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nav pieejamu ierīču."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nav izveidots savienojums ar Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Spilgtums"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Krāsu inversija"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Krāsu korekcija"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ātrā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lēnā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Notiek uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Noklikšķiniet uz bultiņas pogas, lai palaistu kopienas pamācību."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 80ef7bb..8babe8c 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Се зачувува слика од екранот на вашиот работен профил…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Сликата од екранот е зачувана"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не може да се зачува слика од екранот"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Уредот мора да биде отклучен за да може да се зачува слика од екранот"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Повторно обидете се да направите слика од екранот"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не може да се зачува слика од екранот"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Емитување"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименуван уред"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Нема достапни уреди"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нема Wi-Fi врска"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветленост"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија на боите"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција на боите"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни брзо • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни бавно • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликнете на копчето со стрелка за да го започнете заедничкото упатство"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Промени го корисникот"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"паѓачко мени"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Сите апликации и податоци во сесијата ќе се избришат."</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 5b1c80f..c5b6536 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നു…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"സ്ക്രീൻഷോട്ട് സംരക്ഷിച്ചു"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കാനായില്ല"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നതിന് മുമ്പ് ഉപകരണം അൺലോക്ക് ചെയ്തിരിക്കണം"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"സ്ക്രീൻഷോട്ട് എടുക്കാൻ വീണ്ടും ശ്രമിക്കുക"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കാനാകുന്നില്ല"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"കാസ്റ്റുചെയ്യുന്നു"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"പേരിടാത്ത ഉപകരണം"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ഉപകരണങ്ങളൊന്നും ലഭ്യമല്ല"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"വൈഫൈ കണക്റ്റ് ചെയ്തിട്ടില്ല"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"നിറം വിപരീതമാക്കൽ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"നിറം ശരിയാക്കൽ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • വേഗത്തിൽ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"കമ്മ്യൂണൽ ട്യൂട്ടോറിയൽ ആരംഭിക്കാൻ അമ്പടയാള ബട്ടണിൽ ക്ലിക്ക് ചെയ്യുക"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 3f65931..b083c66 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Дэлгэцийн агшныг ажлын профайлд хадгалж байна…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Дэлгэцээс дарсан зургийг хадгалсан"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Дэлгэцээс дарсан зургийг хадгалж чадсангүй"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Дэлгэцийн агшныг хадгалах боломжтой болохоос өмнө төхөөрөмжийн түгжээг тайлах ёстой"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Дэлгэцийн зургийг дахин дарж үзнэ үү"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Дэлгэцийн агшныг хадгалах боломжгүй"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Дамжуулж байна"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Нэргүй төхөөрөмж"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Төхөөрөмж байхгүй"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-д холбогдоогүй байна"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Тодрол"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Өнгө хувиргалт"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Өнгө тохируулга"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Хурдтай цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Удаан цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Нийтийн практик хичээлийг эхлүүлэхийн тулд суман товчийг товшино уу"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 238aaca..5d9f67e 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलवर स्क्रीनशॉट सेव्ह करत आहे…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव्ह केला"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव्ह करू शकलो नाही"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव्ह करण्याआधी डिव्हाइस अनलॉक करणे आवश्यक आहे"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट पुन्हा घेण्याचा प्रयत्न करा"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट सेव्ह करू शकत नाही"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"कास्ट करत आहे"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"निनावी डिव्हाइस"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कोणतेही डिव्हाइसेस उपलब्ध नाहीत"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाय-फाय नाही"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"चमक"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्व्हर्जन"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग सुधारणा"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • वेगाने चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • हळू चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"सामुदायिक ट्यूटोरियल सुरू करण्यासाठी ॲरो बटणावर क्लिक करा"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अॅप्स आणि डेटा हटवला जाईल."</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 8c764f1..10e9859c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan tangkapan skrin ke profil kerja…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Tangkapan skrin disimpan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan tangkapan skrin"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Peranti mesti dibuka kunci sebelum tangkapan skrin dapat disimpan"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Cuba ambil tangkapan skrin sekali lagi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Tidak dapat menyimpan tangkapan skrin"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Menghantar"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Peranti tidak bernama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Tiada peranti tersedia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tidak disambungkan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Penyongsangan warna"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pembetulan warna"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan cepat • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan perlahan • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik butang anak panah untuk memulakan tutorial umum"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 883b9e9..dddc2fa 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"အလုပ်ပရိုဖိုင်တွင် ဖန်သားပြင်ဓာတ်ပုံ သိမ်းနေသည်…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်းပြီးပါပြီ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"မျက်နှာပြင်ပုံကို သိမ်း၍မရပါ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ဖန်သားပြင်ဓာတ်ပုံကို မသိမ်းမီ စက်ပစ္စည်းကို လော့ခ်ဖွင့်ထားရမည်"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"မျက်နှာပြင်ပုံကို ထပ်ရိုက်ကြည့်ပါ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်း၍မရပါ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ကာစ်တင်"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"အမည်မတပ် ကိရိယာ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ကိရိယာများ မရှိ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ချိတ်ဆက်ထားခြင်းမရှိပါ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"အလင်းတောက်ပမှု"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"အရောင်ပြောင်းပြန်ပြုလုပ်ရန်"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"အရောင် အမှန်ပြင်ခြင်း"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အမြန်အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • နှေးကွေးစွာ အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"အများသုံးရှင်းလင်းပို့ချချက် စတင်ရန် မြားခလုတ်ကို နှိပ်ပါ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 53a4c52..ff32177 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Lagrer skjermdumpen i jobbprofilen …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skjermdumpen er lagret"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kunne ikke lagre skjermdump"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enheten må være låst opp før skjermdumpen kan lagres"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prøv å ta skjermdump på nytt"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan ikke lagre skjermdumpen"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhet uten navn"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ingen enheter er tilgjengelige"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi er ikke tilkoblet"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader raskt • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader sakte • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klikk på pilen for å starte fellesveiledningen"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 3b31b3a..61e52bd 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलमा स्क्रिनसट सेभ गरिँदै छ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रिनसट सेभ गरियो"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"डिभाइस अनलक गरेपछि मात्र स्क्रिनसट सुरक्षित गर्न सकिन्छ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रिनसट फेरि लिएर हेर्नुहोस्"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"प्रसारण गर्दै"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"बेनाम उपकरण"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कुनै उपकरणहरू उपलब्ध छैन"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi कनेक्ट गरिएको छैन"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"उज्यालपन"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्भर्सन"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"कलर करेक्सन"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • छिटो चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • बिस्तारै चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा फुल चार्ज हुने छ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"कम्युनल ट्युटोरियल सुरु गर्न एरो बटनमा क्लिक गर्नुहोस्"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 664af5e..7d21874 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot opslaan in werkprofiel…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot opgeslagen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kan screenshot niet opslaan"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Je moet het apparaat ontgrendelen voordat het screenshot kan worden opgeslagen"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer opnieuw een screenshot te maken"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan screenshot niet opslaan"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casten"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Naamloos apparaat"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Geen apparaten beschikbaar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi niet verbonden"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleurinversie"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurcorrectie"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Snel opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Langzaam opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik op de pijl om de communitytutorial te starten"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index db9e0c5..38039cb 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ୱାର୍କ ପ୍ରୋଫାଇଲରେ ସ୍କ୍ରିନସଟ ସେଭ କରାଯାଉଛି…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ ହୋଇଛି"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ସ୍କ୍ରୀନ୍ଶଟ୍ ସେଭ୍ କରିହେବ ନାହିଁ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ସ୍କ୍ରିନସଟ୍ ସେଭ୍ କରିବା ପୂର୍ବରୁ ଡିଭାଇସକୁ ଅନଲକ୍ କରାଯିବା ଆବଶ୍ୟକ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ପୁଣିଥରେ ସ୍କ୍ରୀନ୍ଶଟ୍ ନେବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ସ୍କ୍ରିନସଟକୁ ସେଭ୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"କାଷ୍ଟିଙ୍ଗ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ନାମହୀନ ଡିଭାଇସ୍"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"କୌଣସି ଡିଭାଇସ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ୱାଇ-ଫାଇ ସଂଯୋଜିତ ହୋଇନାହିଁ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ଉଜ୍ଜ୍ୱଳତା"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"କଲର ଇନଭର୍ସନ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ରଙ୍ଗ ସଂଶୋଧନ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଧୀରେ ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"କମ୍ୟୁନାଲ ଟ୍ୟୁଟୋରିଆଲ ଆରମ୍ଭ କରିବା ପାଇଁ ତୀର ବଟନରେ କ୍ଲିକ କରନ୍ତୁ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍ ବଦଳାନ୍ତୁ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍ ଓ ଡାଟା ଡିଲିଟ୍ ହୋଇଯିବ।"</string>
@@ -660,7 +664,7 @@
<string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"ବର୍ତ୍ତମାନର"</string>
<string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ଫେରନ୍ତୁ"</string>
<string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ବିଜ୍ଞପ୍ତି"</string>
- <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀ\'ବୋର୍ଡ ସର୍ଟକଟ୍"</string>
+ <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string>
<string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"କୀ\'ବୋର୍ଡ୍ର ଲେଆଉଟ୍କୁ ବଦଳାନ୍ତୁ"</string>
<string name="keyboard_shortcut_clear_text" msgid="4679927133259287577">"ଟେକ୍ସଟ ଖାଲି କରନ୍ତୁ"</string>
<string name="keyboard_shortcut_search_list_title" msgid="1156178106617830429">"ସର୍ଟକଟଗୁଡ଼ିକ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 591c5e7..9011855 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਕੀਤੇ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਡੀਵਾਈਸ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਦੁਬਾਰਾ ਲੈ ਕੇ ਦੇਖੋ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ਕਾਸਟਿੰਗ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ਬਿਨਾਂ ਨਾਮ ਦਾ ਡੀਵਾਈਸ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ਕੋਈ ਡਿਵਾਈਸਾਂ ਉਪਲਬਧ ਨਹੀਂ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ਵਾਈ-ਫਾਈ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ਚਮਕ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ਰੰਗ ਪਲਟਨਾ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ਰੰਗ ਸੁਧਾਈ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ਭਾਈਚਾਰਕ ਟਿਊਟੋਰੀਅਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਤੀਰ ਬਟਨ \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਏਗਾ।"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 1951221..9b2b3cf 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Zapisuję zrzut ekranu w profilu służbowym…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Zrzut ekranu został zapisany"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nie udało się zapisać zrzutu ekranu"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Przed zapisaniem zrzutu ekranu musisz odblokować urządzenie"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Spróbuj jeszcze raz wykonać zrzut ekranu"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nie można zapisać zrzutu ekranu."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Przesyłam"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Urządzenie bez nazwy"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Brak dostępnych urządzeń"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Brak połączenia z Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jasność"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Odwrócenie kolorów"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcja kolorów"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Szybkie ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wolne ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknij przycisk strzałki, aby uruchomić samouczek społecznościowy"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 693a3a1..a2b1fdb 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que a captura de tela seja salva, o dispositivo precisa ser desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tente fazer a captura de tela novamente"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não foi possível salvar a captura de tela"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregamento rápido • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial compartilhado"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index c4f1f67..373cafb 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"A guardar captura de ecrã no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de ecrã guardada"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Não foi possível guardar a captura de ecrã"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"É necessário desbloquear o dispositivo para guardar a captura de ecrã"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Experimente voltar a efetuar a captura de ecrã."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não é possível guardar a captura de ecrã."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmissão"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Sem dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não ligado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção da cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar rapidamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar lentamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial coletivo"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 693a3a1..a2b1fdb 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que a captura de tela seja salva, o dispositivo precisa ser desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tente fazer a captura de tela novamente"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não foi possível salvar a captura de tela"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregamento rápido • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial compartilhado"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index a942332..1aabc3e 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Se salvează captura în profilul de serviciu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captură de ecran salvată"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nu s-a putut salva captura de ecran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pentru a salva captura de ecran, trebuie să deblochezi dispozitivul"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Încearcă să faci din nou o captură de ecran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nu se poate salva captura de ecran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Se proiectează"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispozitiv nedenumit"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Niciun dispozitiv disponibil"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Rețeaua Wi-Fi nu este conectată"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminozitate"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversarea culorilor"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă rapid • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă lent • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Dă clic pe butonul săgeată pentru a începe tutorialul pentru comunitate"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 68601d6..35b4c29 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Сохранение скриншота в рабочем профиле…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сохранен"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не удалось сохранить скриншот"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Чтобы сохранить скриншот, разблокируйте устройство."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Попробуйте сделать скриншот снова."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не удалось сохранить скриншот."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Передача изображения"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Безымянное устройство"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Нет доступных устройств"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нет подключения к сети Wi-Fi."</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркость"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверсия цветов"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Коррекция цвета"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Быстрая зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Медленная зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Нажмите кнопку со стрелкой, чтобы ознакомиться с руководством"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 86cb3c3..f2dd421 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"කාර්යාල පැතිකඩ වෙත තිර රුව සුරකිමින්…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"තිර රුව සුරකින ලදී"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"තිර රුව සුරැකිය නොහැකි විය"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"තිර රුව සුරැකීමට පෙර උපාංගය අගුලු හැරිය යුතුය"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"තිර රුව නැවත ගැනීමට උත්සාහ කරන්න"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"තිර රුව සුරැකීමට නොහැකිය"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"කාස්ට් කිරීම"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"නම් නොකළ උපාංගය"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"උපාංග නොතිබේ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi සම්බන්ධ නොවීය"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"දීප්තිමත් බව"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"වර්ණ අපවර්තනය"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"වර්ණ නිවැරදි කිරීම"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • වේගයෙන් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • සෙමින් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"වාර්ගික නිබන්ධනය ආරම්භ කිරීමට ඊතල බොත්තම ක්ලික් කරන්න"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 56b9f75..844f30c 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukladá sa snímka obrazovky do pracovného profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snímka obrazovky bola uložená"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snímku obrazovky sa nepodarilo uložiť"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pred uložením snímky obrazovky je potrebné zariadenie odomknúť"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Skúste snímku urobiť znova"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Snímka obrazovky sa nedá uložiť"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prenáša sa"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nepomenované zariadenie"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nie sú k dispozícii žiadne zariadenia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Sieť Wi‑Fi nie je pripojená"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzia farieb"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Úprava farieb"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa rýchlo • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa pomaly • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ak chcete spustiť komunitný návod, kliknite na tlačidlo so šípkou"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index b72f750..1eab754 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Shranjevanje posnetka zaslona v delovni profil …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Posnetek zaslona je shranjen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Posnetka zaslona ni bilo mogoče shraniti"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pred shranjevanjem posnetka zaslona morate odkleniti napravo"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Poskusite znova ustvariti posnetek zaslona"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Posnetka zaslona ni mogoče shraniti"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Predvajanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovana naprava"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Na voljo ni nobene naprave"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Povezava Wi-Fi ni vzpostavljena"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svetlost"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija barv"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Popravljanje barv"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hitro polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Počasno polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite gumb s puščico, da zaženete vadnico za skupnost"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 92f6f9c..cafd6a0 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pamja e ekranit po ruhet te profili i punës…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Pamja e ekranit u ruajt"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Pamja e ekranit nuk mund të ruhej"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pajisja duhet të shkyçet para se të mund të ruhet pamja e ekranit"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Provo ta nxjerrësh përsëri pamjen e ekranit"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Pamja e ekranit nuk mund të ruhet"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Po transmeton"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Pajisje e paemërtuar"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nuk ofrohet për përdorim asnjë pajisje"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi nuk është lidhur"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ndriçimi"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Anasjellja e ngjyrës"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korrigjimi i ngjyrës"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet shpejt • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet ngadalë • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliko mbi butonin e shigjetës për të filluar udhëzuesin e përbashkët"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 7c0d9be..8c1450a1 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Снимак екрана се чува на пословном профилу…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Снимак екрана је сачуван"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Чување снимка екрана није успело"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Уређај мора да буде откључан да би снимак екрана могао да се сачува"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Пробајте да поново направите снимак екрана"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Чување снимка екрана није успело"</string>
@@ -101,8 +103,8 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обрађујемо видео снимка екрана"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Обавештење о сесији снимања екрана је активно"</string>
<string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Желите да започнете снимање?"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"Започни снимање"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Снимај звук"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук уређаја"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Пребацивање"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименовани уређај"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Није доступан ниједан уређај"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi није повезан"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија боја"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција боја"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Брзо се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Споро се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Пуни се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликните на дугме са стрелицом да бисте започели заједнички водич"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string>
@@ -418,17 +422,17 @@
<string name="screen_share_permission_dialog_option_single_app" msgid="4350961814397220929">"Једна апликација"</string>
<string name="screen_share_permission_app_selector_title" msgid="1404878013670347899">"Делите или снимите апликацију"</string>
<string name="media_projection_entry_app_permission_dialog_title" msgid="9155535851866407199">"Желите да почнете снимање или пребацивање помоћу апликације <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Покрени"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> је онемогућила ову опцију"</string>
<string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"Желите да започнете пребацивање?"</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Када пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Када пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Када пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Када пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Започни пребацивање"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Желите да почнете да делите?"</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
<string name="media_projection_task_switcher_text" msgid="590885489897412359">"Дељење се зауставља када мењате апликације"</string>
<string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Дели ову апликацију"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 323ce11f..59bb1f5 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sparar skärmbild i jobbprofilen …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skärmbilden har sparats"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Det gick inte att spara skärmbilden"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skärmbilden kan bara sparas om enheten är upplåst"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Testa att ta en skärmbild igen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Det gick inte att spara skärmbilden"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Castar"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Namnlös enhet"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Inga tillgängliga enheter"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ej ansluten till wifi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ljusstyrka"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Färginvertering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Färgkorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas snabbt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas långsamt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klicka på pilknappen för att börja med gruppguiden"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 304910a..b977dc4 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Inahifadhi picha ya skrini kwenye wasifu wa kazini…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Imehifadhi picha ya skrini"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Imeshindwa kuhifadhi picha ya skrini"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Ni sharti ufungue kifaa kabla ya kuhifadhi picha ya skrini"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Jaribu kupiga picha ya skrini tena"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Imeshindwa kuhifadhi picha ya skrini"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Inatuma"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Kifaa hakina jina"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Hakuna vifaa vilivyopatikana"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi haijaunganishwa"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ung\'avu"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ugeuzaji rangi"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Usahihishaji wa rangirangi"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji kwa kasi • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji polepole • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Bofya kwenye kitufe cha kishale ili kuanzisha mafunzo ya jumuiya"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 3ac5e2e..3d05824 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"பணிக் கணக்கில் ஸ்கிரீன்ஷாட் சேமிக்கப்படுகிறது…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ஸ்கிரீன்ஷாட் சேமிக்கப்பட்டது"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ஸ்கிரீன் ஷாட்டைச் சேமிக்க முடியவில்லை"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ஸ்கிரீன்ஷாட் சேமிக்கப்படுவதற்கு முன்பு சாதனம் அன்லாக் செய்யப்பட வேண்டும்"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ஸ்கிரீன் ஷாட்டை மீண்டும் எடுக்க முயலவும்"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ஸ்கிரீன்ஷாட்டைச் சேமிக்க முடியவில்லை"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"அனுப்புகிறது"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"பெயரிடப்படாத சாதனம்"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"சாதனங்கள் இல்லை"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"வைஃபை இணைக்கப்படவில்லை"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ஒளிர்வு"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"கலர் இன்வெர்ஷன்"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"கலர் கரெக்ஷன்"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • வேகமாகச் சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • மெதுவாக சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுவதும் சார்ஜாகும்"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"சமூகப் பயிற்சியைத் தொடங்க அம்புக்குறி பட்டனைக் கிளிக் செய்யுங்கள்"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 0526f95..6d68b63 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"స్క్రీన్షాట్ను వర్క్ ప్రొఫైల్కు సేవ్ చేస్తోంది…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"స్క్రీన్షాట్ సేవ్ చేయబడింది"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"స్క్రీన్షాట్ని సేవ్ చేయడం సాధ్యం కాలేదు"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"స్క్రీన్షాట్ సేవ్ అవ్వకముందే పరికరం అన్లాక్ చేయబడాలి"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"స్క్రీన్షాట్ తీయడానికి మళ్లీ ట్రై చేయండి"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"స్క్రీన్షాట్ను సేవ్ చేయడం సాధ్యపడలేదు"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ప్రసారం చేస్తోంది"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"పేరులేని పరికరం"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"పరికరాలు ఏవీ అందుబాటులో లేవు"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi కనెక్ట్ కాలేదు"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ప్రకాశం"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"కలర్ మార్పిడి"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"కలర్ కరెక్షన్"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • వేగంగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • నెమ్మదిగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"కమ్యూనల్ ట్యుటోరియల్ను ప్రారంభించడానికి బాణం బటన్పై క్లిక్ చేయండి"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్డౌన్ మెనూ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని యాప్లు మరియు డేటా తొలగించబడతాయి."</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 8949360..3f5a47e 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"กำลังบันทึกภาพหน้าจอไปยังโปรไฟล์งาน…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"บันทึกภาพหน้าจอแล้ว"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"บันทึกภาพหน้าจอไม่ได้"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ต้องปลดล็อกอุปกรณ์ก่อนจึงจะบันทึกภาพหน้าจอได้"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ลองบันทึกภาพหน้าจออีกครั้ง"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"บันทึกภาพหน้าจอไม่ได้"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"กำลังส่ง"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"อุปกรณ์ที่ไม่มีชื่อ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ไม่มีอุปกรณ์ที่สามารถใช้ได้"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ความสว่าง"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"การกลับสี"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"การแก้สี"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างเร็ว • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างช้าๆ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"คลิกปุ่มลูกศรเพื่อเริ่มบทแนะนำส่วนกลาง"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index e5c11df..3fac637 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sine-save ang screenshot sa profile sa trabaho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Na-save ang screenshot"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Hindi ma-save ang screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Dapat naka-unlock ang device bago ma-save ang screenshot"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Subukang kumuhang muli ng screenshot"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Hindi ma-save ang screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Nagka-cast"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Walang pangalang device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Walang available na mga device"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Hindi nakakonekta sa Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Pag-invert ng kulay"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pagtatama ng kulay"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabilis na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabagal na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"I-click ang arrow button para simulan ang communal na tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 3552d92..ca357b41 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekran görüntüsü iş profiline kaydediliyor…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekran görüntüsü kaydedildi"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekran görüntüsü kaydedilemedi"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Ekran görüntüsünün kaydedilebilmesi için cihazın kilidi açık olmalıdır"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tekrar ekran görüntüsü almayı deneyin"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekran görüntüsü kaydedilemiyor"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Yayınlanıyor"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Adsız cihaz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Kullanılabilir cihaz yok"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Kablosuz ağ bağlı değil"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaklık"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rengi ters çevirme"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Renk düzeltme"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hızlı şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Yavaş şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ortak eğitimi başlatmak için ok düğmesini tıklayın"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 2e6aea9..6078e31 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Зберігання знімка екрана в робочому профілі…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Знімок екрана збережено"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не вдалося зберегти знімок екрана"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Щоб зберегти знімок екрана, розблокуйте пристрій"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Спробуйте зробити знімок екрана ще раз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не вдалося зберегти знімок екрана"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Трансляція"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Пристрій без назви"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Немає пристроїв"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi не під’єднано"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яскравість"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія кольорів"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекція кольору"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Швидке заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Повільне заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Натисніть кнопку зі стрілкою, щоб відкрити спільний навчальний посібник"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 2e2fc41..5cf763f 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"اسکرین شاٹ دفتری پروفائل میں محفوظ کیا جا رہا ہے…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"اسکرین شاٹ محفوظ ہو گیا"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکا"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"اسکرین شاٹ محفوظ کرنے سے پہلے آلے کو غیر مقفل کرنا ضروری ہے"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"دوبارہ اسکرین شاٹ لینے کی کوشش کریں"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکتا"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"کاسٹنگ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"بغیر نام والا آلہ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"کوئی آلات دستیاب نہیں ہیں"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi سے منسلک نہیں ہے"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"چمکیلا پن"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"رنگوں کی تقلیب"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"رنگ کی اصلاح"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • تیزی سے چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • آہستہ چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"کمیونل ٹیوٹوریل شروع کرنے کے لیے تیر کے نشان والے بٹن پر کلک کریں"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index a8654d5..412dd8a 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -20,8 +20,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4811759950673118541">"Tizim interfeysi"</string>
- <string name="battery_low_title" msgid="5319680173344341779">"Quvvat tejash funksiyasi yoqilsinmi?"</string>
- <string name="battery_low_description" msgid="3282977755476423966">"<xliff:g id="PERCENTAGE">%s</xliff:g> batareya quvvati qoldi. Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
+ <string name="battery_low_title" msgid="5319680173344341779">"Quvvat tejash yoqilsinmi?"</string>
+ <string name="battery_low_description" msgid="3282977755476423966">"Batareya quvvati <xliff:g id="PERCENTAGE">%s</xliff:g> qoldi. Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
<string name="battery_low_intro" msgid="5148725009653088790">"Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
<string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> qoldi"</string>
<string name="invalid_charger_title" msgid="938685362320735167">"USB orqali quvvatlash imkonsiz"</string>
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Skrinshot ish profiliga saqlanmoqda…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinshot saqlandi"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinshot saqlanmadi"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skrinshotni saqlashdan oldin qurilma qulflanmagan boʻlishi lozim"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Qayta skrinshot olib ko‘ring"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Skrinshot saqlanmadi"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Translatsiya qilinmoqda"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nomsiz qurilma"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Qurilmalar topilmadi"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tarmoqqa ulanmagan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Yorqinlik"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ranglarni akslantirish"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ranglarni tuzatish"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Tez quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sekin quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Qoʻllanma bilan tanishish uchun strelka tugmasini bosing"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 5443745..45dfc45 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Đang lưu ảnh chụp màn hình vào hồ sơ công việc…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Đã lưu ảnh chụp màn hình"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Không thể lưu ảnh chụp màn hình"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Bạn phải mở khóa thiết bị để chúng tôi có thể lưu ảnh chụp màn hình"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Hãy thử chụp lại màn hình"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Không thể lưu ảnh chụp màn hình"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Đang truyền"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Thiết bị không có tên"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Không có thiết bị nào"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Chưa kết nối với Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Độ sáng"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Đảo màu"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Chỉnh màu"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc nhanh • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc chậm • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Nhấp vào nút mũi tên để bắt đầu xem hướng dẫn chung"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 44c163b..17a66787 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在将屏幕截图保存到工作资料…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"已保存屏幕截图"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"无法保存屏幕截图"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必须先解锁设备,然后才能保存屏幕截图"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"请再次尝试截屏"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"无法保存屏幕截图"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"正在投放"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名设备"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"没有可用设备"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未连接到 WLAN 网络"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"颜色反转"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在快速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在慢速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"点击箭头按钮,即可启动公共教程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index f45b2fe..48fbf7c 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存至工作設定檔…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕擷取畫面已儲存"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕擷取畫面"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必須先解鎖裝置,才能儲存螢幕截圖"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"請再嘗試拍攝螢幕擷取畫面"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"無法儲存螢幕截圖"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"正在放送"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名的裝置"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"沒有可用裝置"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"按一下箭咀鍵,即可開始共用教學課程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 5a5bb9d..55d4cb6 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存到工作資料夾…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕截圖已儲存"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕截圖"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必須先解鎖裝置,才能儲存螢幕截圖"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"請再次嘗試拍攝螢幕截圖"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"無法儲存螢幕截圖"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"投放"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名的裝置"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"沒有可用裝置"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"點選箭頭按鈕,即可開始通用教學課程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會遭到刪除。"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 8b6bfae..b883b9a 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ilondoloza isithombe-skrini kuphrofayela yomsebenzi…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Isithombe-skrini silondoloziwe"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ayikwazanga ukulondoloza isithombe-skrini"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Idivayisi kufanele ivulwe ngaphambi kokuthi isithombe-skrini singalondolozwa"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Zama ukuthatha isithombe-skrini futhi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ayikwazi ukulondoloza isithombe-skrini"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Ukusakaza"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Idivayisi engenalo igama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ayikho idivayisi etholakalayo"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"I-Wi-Fi ayixhunyiwe"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ukugqama"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ukuguqulwa kombala"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ukulungiswa kombala"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja ngokushesha • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja kancane • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Iyashaja • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Chofoza inkinobho yomcibisholo ukuze uqalise isifundo somphakathi"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7a6d29a..9e2ebf6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3224,6 +3224,9 @@
<!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
<string name="dismiss_dialog">Dismiss</string>
+ <!--- Content description of the connected display status bar icon that appears every time a display is connected [CHAR LIMIT=NONE]-->
+ <string name="connected_display_icon_desc">Display connected</string>
+
<!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
<string name="privacy_dialog_title">Microphone & Camera</string>
<!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
index db46ccf..80f70a0 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
@@ -33,6 +33,10 @@
val hasFingerprint: Boolean
get() = fingerprintProperties != null
+ /** If SFPS authentication is available. */
+ val hasSfps: Boolean
+ get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType
+
/** If fingerprint authentication is available (and [faceProperties] is non-null). */
val hasFace: Boolean
get() = faceProperties != null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
similarity index 77%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
copy to packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
index 0aa6b0b..9b7cd70 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
@@ -13,10 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.dagger.qualifiers
-package com.android.systemui.qs.tiles.base.interactor
+import javax.inject.Qualifier
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
-)
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Tracing
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index eb20669..c505bd5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -61,6 +61,8 @@
InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
public static final int CUJ_OPEN_SEARCH_RESULT =
InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+ public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
+ InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -77,6 +79,7 @@
CUJ_CLOSE_ALL_APPS_SWIPE,
CUJ_CLOSE_ALL_APPS_TO_HOME,
CUJ_OPEN_SEARCH_RESULT,
+ CUJ_LAUNCHER_UNFOLD_ANIM,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
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 0000000..f219cec
--- /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 fe607e1..7b67e3f 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 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 0000000..63ea116
--- /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/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 7b2e1af..e459034 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.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.
@@ -18,13 +18,25 @@
import android.os.Trace
import android.os.TraceNameSupplier
+import android.util.Log
+import com.android.systemui.util.tracing.TraceContextElement
+import com.android.systemui.util.tracing.TraceData
+import com.android.systemui.util.tracing.TraceData.Companion.FIRST_VALID_SPAN
+import com.android.systemui.util.tracing.TraceData.Companion.INVALID_SPAN
+import com.android.systemui.util.tracing.threadLocalTrace
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.coroutineContext
+import kotlin.random.Random
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
/**
* Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
@@ -44,6 +56,10 @@
class TraceUtils {
companion object {
+ const val TAG = "TraceUtils"
+ private const val DEBUG_COROUTINE_TRACING = false
+ const val DEFAULT_TRACK_NAME = "AsyncTraces"
+
inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
return Runnable { traceSection(tag) { block() } }
}
@@ -73,7 +89,7 @@
* under a single track.
*/
inline fun <T> traceAsync(method: String, block: () -> T): T =
- traceAsync("AsyncTraces", method, block)
+ traceAsync(DEFAULT_TRACK_NAME, method, block)
/**
* Creates an async slice in a track with [trackName] while [block] runs.
@@ -93,16 +109,313 @@
}
/**
- * Convenience method to avoid one indentation level when we want to add a trace when
- * launching a coroutine
+ * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
*/
- fun <T> CoroutineScope.tracedAsync(
- method: String,
+ inline fun CoroutineScope.launch(
+ crossinline spanName: () -> String,
context: CoroutineContext = EmptyCoroutineContext,
- start: CoroutineStart = CoroutineStart.DEFAULT,
- block: suspend () -> T
- ): Deferred<T> {
- return async(context, start) { traceAsync(method) { block() } }
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> Unit
+ ): Job = launch(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun CoroutineScope.launch(
+ spanName: String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> Unit
+ ): Job = launch(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
+ * tracing
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> CoroutineScope.async(
+ crossinline spanName: () -> String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> T
+ ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> CoroutineScope.async(
+ spanName: String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> T
+ ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> runBlocking(
+ crossinline spanName: () -> String,
+ context: CoroutineContext,
+ crossinline block: suspend () -> T
+ ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> runBlocking(
+ spanName: String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ suspend inline fun <T> withContext(
+ spanName: String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = withContext(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ suspend inline fun <T> withContext(
+ crossinline spanName: () -> String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = withContext(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * A hacky way to propagate the value of the COROUTINE_TRACING flag for static usage in this
+ * file. It should only every be set to true during startup. Once true, it cannot be set to
+ * false again.
+ */
+ var coroutineTracingIsEnabled = false
+ set(v) {
+ if (v) field = true
+ }
+
+ /**
+ * Traces a section of work of a `suspend` [block]. The trace sections will appear on the
+ * thread that is currently executing the [block] of work. If the [block] is suspended, all
+ * trace sections added using this API will end until the [block] is resumed, which could
+ * happen either on this thread or on another thread. If a child coroutine is started, it
+ * will inherit the trace sections of its parent. The child will continue to print these
+ * trace sections whether or not the parent coroutine is still running them.
+ *
+ * The current [CoroutineContext] must have a [TraceContextElement] for this API to work.
+ * Otherwise, the trace sections will be dropped.
+ *
+ * For example, in the following trace, Thread #1 ran some work, suspended, then continued
+ * working on Thread #2. Meanwhile, Thread #2 created a new child coroutine which inherited
+ * its trace sections. Then, the original coroutine resumed on Thread #1 before ending.
+ * Meanwhile Thread #3 is still printing trace sections from its parent because they were
+ * copied when it was created. There is no way for the parent to communicate to the child
+ * that it marked these slices as completed. While this might seem counterintuitive, it
+ * allows us to pinpoint the origin of the child coroutine's work.
+ *
+ * ```
+ * Thread #1 | [==== Slice A ====] [==== Slice A ====]
+ * | [==== B ====] [=== B ===]
+ * --------------------------------------------------------------------------------------
+ * Thread #2 | [====== Slice A ======]
+ * | [========= B =========]
+ * | [===== C ======]
+ * --------------------------------------------------------------------------------------
+ * Thread #3 | [== Slice A ==] [== Slice A ==]
+ * | [===== B =====] [===== B =====]
+ * | [===== C =====] [===== C =====]
+ * | [=== D ===]
+ * ```
+ *
+ * @param name The name of the code section to appear in the trace
+ * @see endSlice
+ * @see traceCoroutine
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ suspend inline fun <T> traceCoroutine(
+ spanName: Lazy<String>,
+ crossinline block: suspend () -> T
+ ): T {
+ // For coroutine tracing to work, trace spans must be added and removed even when
+ // tracing is not active (i.e. when TRACE_TAG_APP is disabled). Otherwise, when the
+ // coroutine resumes when tracing is active, we won't know its name.
+ val tracer = getTraceData(spanName)
+ val coroutineSpanCookie = tracer?.beginSpan(spanName.value) ?: INVALID_SPAN
+
+ // For now, also trace to "AsyncTraces". This will allow us to verify the correctness
+ // of the COROUTINE_TRACING feature flag.
+ val asyncTraceCookie =
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP))
+ Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE)
+ else INVALID_SPAN
+ if (asyncTraceCookie != INVALID_SPAN) {
+ Trace.asyncTraceForTrackBegin(
+ Trace.TRACE_TAG_APP,
+ DEFAULT_TRACK_NAME,
+ spanName.value,
+ asyncTraceCookie
+ )
+ }
+ try {
+ return block()
+ } finally {
+ if (asyncTraceCookie != INVALID_SPAN) {
+ Trace.asyncTraceForTrackEnd(
+ Trace.TRACE_TAG_APP,
+ DEFAULT_TRACK_NAME,
+ asyncTraceCookie
+ )
+ }
+ tracer?.endSpan(coroutineSpanCookie)
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ suspend fun getTraceData(spanName: Lazy<String>): TraceData? {
+ if (!coroutineTracingIsEnabled) {
+ logVerbose("Experimental flag COROUTINE_TRACING is off", spanName)
+ } else if (coroutineContext[TraceContextElement] == null) {
+ logVerbose("Current CoroutineContext is missing TraceContextElement", spanName)
+ } else {
+ return threadLocalTrace.get().also {
+ if (it == null) logVerbose("ThreadLocal TraceData is null", spanName)
+ }
+ }
+ return null
+ }
+
+ private fun logVerbose(logMessage: String, spanName: Lazy<String>) {
+ if (DEBUG_COROUTINE_TRACING && Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "$logMessage. Dropping trace section: \"${spanName.value}\"")
+ }
+ }
+
+ /** @see traceCoroutine */
+ suspend inline fun <T> traceCoroutine(
+ spanName: String,
+ crossinline block: suspend () -> T
+ ): T = traceCoroutine(lazyOf(spanName)) { block() }
+
+ /** @see traceCoroutine */
+ suspend inline fun <T> traceCoroutine(
+ crossinline spanName: () -> String,
+ crossinline block: suspend () -> T
+ ): T = traceCoroutine(lazy(LazyThreadSafetyMode.PUBLICATION) { spanName() }) { block() }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has begun running __on
+ * the current thread__. This must be followed by a corresponding call to [endSlice] in a
+ * reasonably short amount of time __on the same thread__ (i.e. _before_ the thread becomes
+ * idle again and starts running other, unrelated work).
+ *
+ * Calls to [beginSlice] and [endSlice] may be nested, and they will render in Perfetto as
+ * follows:
+ * ```
+ * Thread #1 | [==========================]
+ * | [==============]
+ * | [====]
+ * ```
+ *
+ * This function is provided for convenience to wrap a call to [Trace.traceBegin], which is
+ * more verbose to call than [Trace.beginSection], but has the added benefit of not throwing
+ * an [IllegalArgumentException] if the provided string is longer than 127 characters. We
+ * use the term "slice" instead of "section" to be consistent with Perfetto.
+ *
+ * # Avoiding malformed traces
+ *
+ * Improper usage of this API will lead to malformed traces with long slices that sometimes
+ * never end. This will look like the following:
+ * ```
+ * Thread #1 | [===================================================================== ...
+ * | [==============] [====================================== ...
+ * | [=======] [======] [===================== ...
+ * | [=======]
+ * ```
+ *
+ * To avoid this, [beginSlice] and [endSlice] should never be called from `suspend` blocks
+ * (instead, use [traceCoroutine] for tracing suspending functions). While it would be
+ * technically okay to call from a suspending function if that function were to only wrap
+ * non-suspending blocks with [beginSlice] and [endSlice], doing so is risky because suspend
+ * calls could be mistakenly added to that block as the code is refactored.
+ *
+ * Additionally, it is _not_ okay to call [beginSlice] when registering a callback and match
+ * it with a call to [endSlice] inside that callback, even if the callback runs on the same
+ * thread. Doing so would cause malformed traces because the [beginSlice] wasn't closed
+ * before the thread became idle and started running unrelated work.
+ *
+ * @param sliceName The name of the code section to appear in the trace
+ * @see endSlice
+ * @see traceCoroutine
+ */
+ fun beginSlice(sliceName: String) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, sliceName)
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has ended. This call must
+ * be preceded by a corresponding call to [beginSlice]. See [beginSlice] for important
+ * information regarding usage.
+ *
+ * @see beginSlice
+ * @see traceCoroutine
+ */
+ fun endSlice() {
+ Trace.traceEnd(Trace.TRACE_TAG_APP)
+ }
+
+ /**
+ * Writes a trace message indicating that an instant event occurred on the current thread.
+ * Unlike slices, instant events have no duration and do not need to be matched with another
+ * call. Perfetto will display instant events using an arrow pointing to the timestamp they
+ * occurred:
+ * ```
+ * Thread #1 | [==============] [======]
+ * | [====] ^
+ * | ^
+ * ```
+ *
+ * @param eventName The name of the event to appear in the trace.
+ */
+ fun instant(eventName: String) {
+ Trace.instant(Trace.TRACE_TAG_APP, eventName)
+ }
+
+ /**
+ * Writes a trace message indicating that an instant event occurred on the given track.
+ * Unlike slices, instant events have no duration and do not need to be matched with another
+ * call. Perfetto will display instant events using an arrow pointing to the timestamp they
+ * occurred:
+ * ```
+ * Async | [==============] [======]
+ * Track | [====] ^
+ * Name | ^
+ * ```
+ *
+ * @param trackName The track where the event should appear in the trace.
+ * @param eventName The name of the event to appear in the trace.
+ */
+ fun instantForTrack(trackName: String, eventName: String) {
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, eventName)
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt
new file mode 100644
index 0000000..4d8c545
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.tracing
+
+import com.android.systemui.util.TraceUtils.Companion.instant
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CopyableThreadContextElement
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Used for safely persisting [TraceData] state when coroutines are suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+@OptIn(DelicateCoroutinesApi::class)
+@ExperimentalCoroutinesApi
+class TraceContextElement(private val traceData: TraceData = TraceData()) :
+ CopyableThreadContextElement<TraceData?> {
+
+ companion object Key : CoroutineContext.Key<TraceContextElement>
+
+ override val key: CoroutineContext.Key<TraceContextElement> = Key
+
+ @OptIn(ExperimentalStdlibApi::class)
+ override fun updateThreadContext(context: CoroutineContext): TraceData? {
+ val oldState = threadLocalTrace.get()
+ oldState?.endAllOnThread()
+ threadLocalTrace.set(traceData)
+ instant("resuming ${context[CoroutineDispatcher]}")
+ traceData.beginAllOnThread()
+ return oldState
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ override fun restoreThreadContext(context: CoroutineContext, oldState: TraceData?) {
+ instant("suspending ${context[CoroutineDispatcher]}")
+ traceData.endAllOnThread()
+ threadLocalTrace.set(oldState)
+ oldState?.beginAllOnThread()
+ }
+
+ override fun copyForChild(): CopyableThreadContextElement<TraceData?> {
+ return TraceContextElement(traceData.copy())
+ }
+
+ override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
+ return TraceContextElement(traceData.copy())
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt
new file mode 100644
index 0000000..0ae58fc
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.tracing
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.util.TraceUtils.Companion.beginSlice
+import com.android.systemui.util.TraceUtils.Companion.endSlice
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+import kotlin.random.Random
+
+/**
+ * Used for giving each thread a unique [TraceData] for thread-local storage. `null` by default.
+ * [threadLocalTrace] can only be used when it is paired with a [TraceContextElement].
+ *
+ * This ThreadLocal will be `null` if either 1) we aren't in a coroutine, or 2) the coroutine we are
+ * in does not have a [TraceContextElement].
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+val threadLocalTrace = ThreadLocal<TraceData?>()
+
+/**
+ * Used for storing trace sections so that they can be added and removed from the currently running
+ * thread when the coroutine is suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+class TraceData {
+ private var slices = mutableListOf<TraceSection>()
+
+ /** Adds current trace slices back to the current thread. Called when coroutine is resumed. */
+ fun beginAllOnThread() {
+ slices.forEach { beginSlice(it.name) }
+ }
+
+ /**
+ * Removes all current trace slices from the current thread. Called when coroutine is suspended.
+ */
+ fun endAllOnThread() {
+ for (i in 0..slices.size) {
+ endSlice()
+ }
+ }
+
+ /**
+ * Creates a new trace section with a unique ID and adds it to the current trace data. The slice
+ * will also be added to the current thread immediately. This slice will not propagate to parent
+ * coroutines, or to child coroutines that have already started. The unique ID is used to verify
+ * that the [endSpan] is corresponds to a [beginSpan].
+ */
+ fun beginSpan(name: String): Int {
+ val newSlice = TraceSection(name, Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE))
+ slices.add(newSlice)
+ beginSlice(name)
+ return newSlice.id
+ }
+
+ /**
+ * Used by [TraceContextElement] when launching a child coroutine so that the child coroutine's
+ * state is isolated from the parent.
+ */
+ fun copy(): TraceData {
+ return TraceData().also { it.slices.addAll(slices) }
+ }
+
+ /**
+ * Ends the trace section and validates it corresponds with an earlier call to [beginSpan]. The
+ * trace slice will immediately be removed from the current thread. This information will not
+ * propagate to parent coroutines, or to child coroutines that have already started.
+ */
+ fun endSpan(id: Int) {
+ val v = slices.removeLast()
+ if (v.id != id) {
+ if (STRICT_MODE) {
+ throw IllegalArgumentException(errorMsg)
+ } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, errorMsg)
+ }
+ }
+ endSlice()
+ }
+
+ companion object {
+ private const val TAG = "TraceData"
+ const val INVALID_SPAN = -1
+ const val FIRST_VALID_SPAN = 1
+
+ /**
+ * If true, throw an exception instead of printing a warning when trace sections beginnings
+ * and ends are mismatched.
+ */
+ private val STRICT_MODE = Build.IS_ENG
+
+ private const val errorMsg =
+ "Mismatched trace section. This likely means you are accessing the trace local " +
+ "storage (threadLocalTrace) without a corresponding CopyableThreadContextElement." +
+ " This could happen if you are using a global dispatcher like Dispatchers.IO." +
+ " To fix this, use one of the coroutine contexts provided by the dagger scope " +
+ "(e.g. \"@Main CoroutineContext\")."
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt
new file mode 100644
index 0000000..b70c497
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.tracing
+
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+
+/**
+ * Represents a section of code executing in a coroutine. This can be split up into multiple slices
+ * on different threads as the coroutine is suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @param name the name of the slice to appear on the current thread's track.
+ * @param id used for matching the beginning and end of trace sections and validating correctness
+ * @see traceCoroutine
+ */
+data class TraceSection(
+ val name: String,
+ val id: Int,
+)
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 fae9fec..a263361 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 @@
@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 7aacb4e..9684d5e 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 @@
@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 fd84543..494efb7 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.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 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 @@
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 @@
}
/**
+ * 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 @@
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 @@
* 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 @@
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 @@
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 b573fad..0f5f869 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -27,6 +27,7 @@
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.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 @@
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 @@
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/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 453a7a6..8ea867b 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -28,6 +28,8 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.TraceUtils.Companion.async
+import com.android.systemui.util.TraceUtils.Companion.withContext
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlin.math.max
@@ -263,7 +265,7 @@
* not being throttled.
*/
private suspend fun refreshThrottling(): Long {
- return withContext(backgroundDispatcher) {
+ return withContext("$TAG#refreshThrottling", backgroundDispatcher) {
val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
val deadline = async { repository.getThrottlingEndTimestamp() }
val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
@@ -311,6 +313,10 @@
DomainLayerAuthenticationMethodModel.Pattern
}
}
+
+ companion object {
+ const val TAG = "AuthenticationInteractor"
+ }
}
/** Result of a user authentication attempt. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
deleted file mode 100644
index 3f2da5e..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthBiometricFaceIconController"
-
-/** Face only icon animator for BiometricPrompt. */
-class AuthBiometricFaceIconController(
- context: Context,
- iconView: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
- // false = dark to light, true = light to dark
- private var lastPulseLightToDark = false
-
- private var state: BiometricState = BiometricState.STATE_IDLE
-
- init {
- val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
- iconView.layoutParams.width = size
- iconView.layoutParams.height = size
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
- }
-
- private fun startPulsing() {
- lastPulseLightToDark = false
- animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
- }
-
- private fun pulseInNextDirection() {
- val iconRes = if (lastPulseLightToDark) {
- R.drawable.face_dialog_pulse_dark_to_light
- } else {
- R.drawable.face_dialog_pulse_light_to_dark
- }
- animateIcon(iconRes, true /* repeat */)
- lastPulseLightToDark = !lastPulseLightToDark
- }
-
- override fun handleAnimationEnd(drawable: Drawable) {
- if (state == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) {
- pulseInNextDirection()
- }
- }
-
- override fun updateIcon(oldState: BiometricState, newState: BiometricState) {
- val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP)
- if (newState == BiometricState.STATE_AUTHENTICATING_ANIMATING_IN) {
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
- )
- } else if (newState == BiometricState.STATE_AUTHENTICATING) {
- startPulsing()
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
- )
- } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_confirmed
- )
- } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_IDLE) {
- animateIconOnce(R.drawable.face_dialog_error_to_idle)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
- )
- } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) {
- animateIconOnce(R.drawable.face_dialog_dark_to_error)
- iconView.contentDescription = context.getString(
- R.string.keyguard_face_failed
- )
- } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_PENDING_CONFIRMATION) {
- animateIconOnce(R.drawable.face_dialog_wink_from_dark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_IDLE) {
- showStaticDrawable(R.drawable.face_dialog_idle_static)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
- )
- } else {
- Log.w(TAG, "Unhandled state: $newState")
- }
- state = newState
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
deleted file mode 100644
index 09eabf2..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-/** Face/Fingerprint combined icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintAndFaceIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) {
-
- override val actsAsConfirmButton: Boolean = true
-
- override fun shouldAnimateIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Boolean = when (newState) {
- STATE_PENDING_CONFIRMATION -> true
- else -> super.shouldAnimateIconViewForTransition(oldState, newState)
- }
-
- @RawRes
- override fun getAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Int? = when (newState) {
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_PENDING_CONFIRMATION) {
- R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
- } else {
- super.getAnimationForTransition(oldState, newState)
- }
- }
- STATE_PENDING_CONFIRMATION -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_unlock_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
- }
- }
- else -> super.getAnimationForTransition(oldState, newState)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
deleted file mode 100644
index 0ad3848..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import android.content.Context.FINGERPRINT_SERVICE
-import android.hardware.fingerprint.FingerprintManager
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.View
-import androidx.annotation.VisibleForTesting
-import com.airbnb.lottie.LottieAnimationView
-import com.android.settingslib.widget.LottieColorUtils
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-
-/** Fingerprint only icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintIconController(
- context: Context,
- iconView: LottieAnimationView,
- protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
- private val isSideFps: Boolean
- private val isReverseDefaultRotation =
- context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-
- var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
- set(value) {
- if (field == value) {
- return
- }
- iconViewOverlay.layoutParams.width = value.first
- iconViewOverlay.layoutParams.height = value.second
- iconView.layoutParams.width = value.first
- iconView.layoutParams.height = value.second
- field = value
- }
-
- init {
- iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
- R.dimen.biometric_dialog_fingerprint_icon_width),
- context.resources.getDimensionPixelSize(
- R.dimen.biometric_dialog_fingerprint_icon_height))
- isSideFps =
- (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
- fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
- } ?: false
- preloadAssets(context)
- val displayInfo = DisplayInfo()
- context.display?.getDisplayInfo(displayInfo)
- if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
- iconView.rotation = 180f
- }
- }
-
- private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) {
- val displayInfo = DisplayInfo()
- context.display?.getDisplayInfo(displayInfo)
- val rotation = getRotationFromDefault(displayInfo.rotation)
- val iconViewOverlayAnimation =
- getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
-
- if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
- iconViewOverlay.setAnimation(iconViewOverlayAnimation)
- }
-
- val iconContentDescription = getIconContentDescription(newState)
- if (iconContentDescription != null) {
- iconView.contentDescription = iconContentDescription
- }
-
- iconView.frame = 0
- iconViewOverlay.frame = 0
- if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
- iconView.playAnimation()
- }
-
- if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
- iconViewOverlay.playAnimation()
- }
-
- LottieColorUtils.applyDynamicColors(context, iconView)
- LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
- }
-
- private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) {
- val icon = getAnimationForTransition(lastState, newState) ?: return
-
- if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
- iconView.setAnimation(icon)
- }
-
- val iconContentDescription = getIconContentDescription(newState)
- if (iconContentDescription != null) {
- iconView.contentDescription = iconContentDescription
- }
-
- iconView.frame = 0
- if (shouldAnimateIconViewForTransition(lastState, newState)) {
- iconView.playAnimation()
- }
- LottieColorUtils.applyDynamicColors(context, iconView)
- }
-
- override fun updateIcon(lastState: BiometricState, newState: BiometricState) {
- if (isSideFps) {
- updateIconSideFps(lastState, newState)
- } else {
- iconViewOverlay.visibility = View.GONE
- updateIconNormal(lastState, newState)
- }
- }
-
- @VisibleForTesting
- fun getIconContentDescription(newState: BiometricState): CharSequence? {
- val id = when (newState) {
- STATE_IDLE,
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING,
- STATE_AUTHENTICATED ->
- if (isSideFps) {
- R.string.security_settings_sfps_enroll_find_sensor_message
- } else {
- R.string.fingerprint_dialog_touch_sensor
- }
- STATE_PENDING_CONFIRMATION ->
- if (isSideFps) {
- R.string.security_settings_sfps_enroll_find_sensor_message
- } else {
- R.string.fingerprint_dialog_authenticated_confirmation
- }
- STATE_ERROR,
- STATE_HELP -> R.string.biometric_dialog_try_again
- else -> null
- }
- return if (id != null) context.getString(id) else null
- }
-
- protected open fun shouldAnimateIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- private fun shouldAnimateSfpsIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING ->
- oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- protected open fun shouldAnimateIconViewOverlayForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- @RawRes
- protected open fun getAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Int? {
- val id = when (newState) {
- STATE_HELP,
- STATE_ERROR -> {
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
- }
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
- }
- }
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_success_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
- }
- }
- else -> return null
- }
- return if (id != null) return id else null
- }
-
- private fun getRotationFromDefault(rotation: Int): Int =
- if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
-
- @RawRes
- private fun getSideFpsOverlayAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState,
- rotation: Int
- ): Int? = when (newState) {
- STATE_HELP,
- STATE_ERROR -> {
- when (rotation) {
- Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
- else -> R.raw.biometricprompt_fingerprint_to_error_landscape
- }
- }
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- }
- } else {
- when (rotation) {
- Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
- else -> R.raw.biometricprompt_fingerprint_to_error_landscape
- }
- }
- }
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_error_to_success_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_error_to_success_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_error_to_success_landscape
- }
- } else {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- }
- }
- }
- else -> null
- }
-
- private fun preloadAssets(context: Context) {
- if (isSideFps) {
- cacheLottieAssetsInContext(
- context,
- R.raw.biometricprompt_fingerprint_to_error_landscape,
- R.raw.biometricprompt_folded_base_bottomright,
- R.raw.biometricprompt_folded_base_default,
- R.raw.biometricprompt_folded_base_topleft,
- R.raw.biometricprompt_landscape_base,
- R.raw.biometricprompt_portrait_base_bottomright,
- R.raw.biometricprompt_portrait_base_topleft,
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
- R.raw.biometricprompt_symbol_error_to_success_landscape,
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
- )
- } else {
- cacheLottieAssetsInContext(
- context,
- R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
- R.raw.fingerprint_dialogue_error_to_success_lottie,
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
- R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 054bd08..8d1d905 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.biometrics
import android.annotation.MainThread
@@ -25,7 +41,7 @@
shadeExpansionCollectorJob =
scope.launch {
// wait for it to emit true once
- shadeInteractorLazy.get().isAnyExpanding.first { it }
+ shadeInteractorLazy.get().isUserInteracting.first { it }
onShadeInteraction.run()
}
shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
deleted file mode 100644
index 958213a..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.drawable.Animatable2
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieCompositionFactory
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthIconController"
-
-/** Controller for animating the BiometricPrompt icon/affordance. */
-abstract class AuthIconController(
- protected val context: Context,
- protected val iconView: LottieAnimationView
-) : Animatable2.AnimationCallback() {
-
- /** If this controller should ignore events and pause. */
- var deactivated: Boolean = false
-
- /** If the icon view should be treated as an alternate "confirm" button. */
- open val actsAsConfirmButton: Boolean = false
-
- final override fun onAnimationStart(drawable: Drawable) {
- super.onAnimationStart(drawable)
- }
-
- final override fun onAnimationEnd(drawable: Drawable) {
- super.onAnimationEnd(drawable)
-
- if (!deactivated) {
- handleAnimationEnd(drawable)
- }
- }
-
- /** Set the icon to a static image. */
- protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
- iconView.setImageDrawable(context.getDrawable(iconRes))
- }
-
- /** Animate a resource. */
- protected fun animateIconOnce(@DrawableRes iconRes: Int) {
- animateIcon(iconRes, false)
- }
-
- /** Animate a resource. */
- protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
- if (!deactivated) {
- val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
- iconView.setImageDrawable(icon)
- icon.forceAnimationOnUI()
- if (repeat) {
- icon.registerAnimationCallback(this)
- }
- icon.start()
- }
- }
-
- /** Update the icon to reflect the [newState]. */
- fun updateState(lastState: BiometricState, newState: BiometricState) {
- if (deactivated) {
- Log.w(TAG, "Ignoring updateState when deactivated: $newState")
- } else {
- updateIcon(lastState, newState)
- }
- }
-
- /** Call during [updateState] if the controller is not [deactivated]. */
- abstract fun updateIcon(lastState: BiometricState, newState: BiometricState)
-
- /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
- open fun handleAnimationEnd(drawable: Drawable) {}
-
- // TODO(b/251476085): Migrate this to an extension at the appropriate level?
- /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
- protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
- for (res in rawResources) {
- LottieCompositionFactory.fromRawRes(context, res)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index c4c52e8b..050b399 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -42,8 +42,11 @@
/** Repository for the current state of the display */
interface DisplayStateRepository {
/**
- * Whether or not the direction rotation is applied to get to an application's requested
- * orientation is reversed.
+ * If true, the direction rotation is applied to get to an application's requested orientation
+ * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+ * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+ * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+ * true here reverses that logic. See go/natural-orientation for context.
*/
val isReverseDefaultRotation: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index a317a06..427361d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -57,6 +57,15 @@
/** Display change event indicating a change to the given displayId has occurred. */
val displayChanges: Flow<Int>
+ /**
+ * If true, the direction rotation is applied to get to an application's requested orientation
+ * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+ * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+ * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+ * true here reverses that logic. See go/natural-orientation for context.
+ */
+ val isReverseDefaultRotation: Boolean
+
/** Called on configuration changes, used to keep the display state in sync */
fun onConfigurationChanged(newConfig: Configuration)
}
@@ -112,6 +121,8 @@
override val currentRotation: StateFlow<DisplayRotation> =
displayStateRepository.currentRotation
+ override val isReverseDefaultRotation: Boolean = displayStateRepository.isReverseDefaultRotation
+
override fun onConfigurationChanged(newConfig: Configuration) {
screenSizeFoldProvider.onConfigurationChange(newConfig)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index cef0be0..0d72b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -29,11 +29,10 @@
import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.systemui.res.R;
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthDialog;
import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter;
+import com.android.systemui.res.R;
import kotlin.Pair;
@@ -85,13 +84,13 @@
}
@Deprecated
- public void updateFingerprintAffordanceSize(
- @NonNull AuthBiometricFingerprintIconController iconController) {
+ public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() {
if (mUdfpsAdapter != null) {
final int sensorDiameter = mUdfpsAdapter.getSensorDiameter(
mScaleFactorProvider.provide());
- iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter));
+ return new Pair(sensorDiameter, sensorDiameter);
}
+ return null;
}
@NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index c29efc0..ac48b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -38,11 +38,7 @@
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.AuthBiometricFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController
-import com.android.systemui.biometrics.AuthIconController
+import com.airbnb.lottie.LottieCompositionFactory
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -56,6 +52,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -101,10 +98,15 @@
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
descriptionView.movementMethod = ScrollingMovementMethod()
- val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+ val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
- PromptFingerprintIconViewBinder.bind(iconView, viewModel.fingerprintIconViewModel)
+ PromptIconViewBinder.bind(
+ iconView,
+ iconOverlayView,
+ view.getUpdatedFingerprintAffordanceSize(),
+ viewModel.iconViewModel
+ )
val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
@@ -128,9 +130,21 @@
// bind to prompt
var boundSize = false
+
view.repeatWhenAttached {
// these do not change and need to be set before any size transitions
val modalities = viewModel.modalities.first()
+ if (modalities.hasFingerprint) {
+ /**
+ * Load the given [rawResources] immediately so they are cached for use in the
+ * [context].
+ */
+ val rawResources = viewModel.iconViewModel.getRawAssets(modalities.hasSfps)
+ for (res in rawResources) {
+ LottieCompositionFactory.fromRawRes(view.context, res)
+ }
+ }
+
titleView.text = viewModel.title.first()
descriptionView.text = viewModel.description.first()
subtitleView.text = viewModel.subtitle.first()
@@ -148,27 +162,8 @@
legacyCallback.onButtonTryAgain()
}
- // TODO(b/251476085): migrate legacy icon controllers and remove
- var legacyState = viewModel.legacyState.value
- val iconController =
- modalities.asIconController(
- view.context,
- iconView,
- iconViewOverlay,
- )
- adapter.attach(this, iconController, modalities, legacyCallback)
- if (iconController is AuthBiometricFingerprintIconController) {
- view.updateFingerprintAffordanceSize(iconController)
- }
- if (iconController is HackyCoexIconController) {
- iconController.faceMode = !viewModel.isConfirmationRequired.first()
- }
+ adapter.attach(this, modalities, legacyCallback)
- // the icon controller must be created before this happens for the legacy
- // sizing code in BiometricPromptLayout to work correctly. Simplify this
- // when those are also migrated. (otherwise the icon size may not be set to
- // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly
- // used as part of the measure spec)
if (!boundSize) {
boundSize = true
BiometricViewSizeBinder.bind(
@@ -212,14 +207,6 @@
) {
legacyCallback.onStartDelayedFingerprintSensor()
}
-
- if (newMode.isStarted) {
- // do wonky switch from implicit to explicit flow
- (iconController as? HackyCoexIconController)?.faceMode = false
- viewModel.showAuthenticating(
- modalities.asDefaultHelpMessage(view.context),
- )
- }
}
}
@@ -312,7 +299,7 @@
viewModel.isIconConfirmButton
.map { isPending ->
when {
- isPending && iconController.actsAsConfirmButton ->
+ isPending && modalities.hasFaceAndFingerprint ->
View.OnTouchListener { _: View, event: MotionEvent ->
viewModel.onOverlayTouch(event)
}
@@ -320,22 +307,11 @@
}
}
.collect { onTouch ->
- iconViewOverlay.setOnTouchListener(onTouch)
+ iconOverlayView.setOnTouchListener(onTouch)
iconView.setOnTouchListener(onTouch)
}
}
- // TODO(b/251476085): remove w/ legacy icon controllers
- // set icon affordance using legacy states
- // like the old code, this causes animations to repeat on config changes :(
- // but keep behavior for now as no one has complained...
- launch {
- viewModel.legacyState.collect { newState ->
- iconController.updateState(legacyState, newState)
- legacyState = newState
- }
- }
-
// dismiss prompt when authenticated and confirmed
launch {
viewModel.isAuthenticated.collect { authState ->
@@ -350,7 +326,7 @@
// Allow icon to be used as confirmation button with a11y enabled
if (accessibilityManager.isTouchExplorationEnabled) {
- iconViewOverlay.setOnClickListener {
+ iconOverlayView.setOnClickListener {
viewModel.confirmAuthenticated()
}
iconView.setOnClickListener { viewModel.confirmAuthenticated() }
@@ -377,7 +353,6 @@
launch {
viewModel.message.collect { promptMessage ->
val isError = promptMessage is PromptMessage.Error
-
indicatorMessageView.text = promptMessage.message
indicatorMessageView.setTextColor(
if (isError) textColorError else textColorHint
@@ -472,9 +447,6 @@
private var modalities: BiometricModalities = BiometricModalities()
private var legacyCallback: Callback? = null
- var legacyIconController: AuthIconController? = null
- private set
-
// hacky way to suppress lockout errors
private val lockoutErrorStrings =
listOf(
@@ -485,24 +457,20 @@
fun attach(
lifecycleOwner: LifecycleOwner,
- iconController: AuthIconController,
activeModalities: BiometricModalities,
callback: Callback,
) {
modalities = activeModalities
- legacyIconController = iconController
legacyCallback = callback
lifecycleOwner.lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
lifecycleScope = owner.lifecycleScope
- iconController.deactivated = false
}
override fun onDestroy(owner: LifecycleOwner) {
lifecycleScope = null
- iconController.deactivated = true
}
}
)
@@ -626,61 +594,9 @@
else -> ""
}
-private fun BiometricModalities.asIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-): AuthIconController =
- when {
- hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay)
- hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
- hasFace -> AuthBiometricFaceIconController(context, iconView)
- else -> throw IllegalStateException("unexpected view type :$this")
- }
-
private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
// TODO(b/251476085): proper type?
typealias BiometricJankListener = Animator.AnimatorListener
-
-// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced
-private class HackyCoexIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) {
-
- private var state: Spaghetti.BiometricState? = null
- private val faceController = AuthBiometricFaceIconController(context, iconView)
-
- var faceMode: Boolean = true
- set(value) {
- if (field != value) {
- field = value
-
- faceController.deactivated = !value
- iconView.setImageIcon(null)
- iconViewOverlay.setImageIcon(null)
- state?.let { updateIcon(Spaghetti.BiometricState.STATE_IDLE, it) }
- }
- }
-
- override fun updateIcon(
- lastState: Spaghetti.BiometricState,
- newState: Spaghetti.BiometricState,
- ) {
- if (deactivated) {
- return
- }
-
- if (faceMode) {
- faceController.updateIcon(lastState, newState)
- } else {
- super.updateIcon(lastState, newState)
- }
-
- state = newState
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
deleted file mode 100644
index d28f1dc..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.biometrics.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.biometrics.ui.viewmodel.PromptFingerprintIconViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
-
-/** Sub-binder for [BiometricPromptLayout.iconView]. */
-object PromptFingerprintIconViewBinder {
-
- /** Binds [BiometricPromptLayout.iconView] to [PromptFingerprintIconViewModel]. */
- @JvmStatic
- fun bind(view: LottieAnimationView, viewModel: PromptFingerprintIconViewModel) {
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.onConfigurationChanged(view.context.resources.configuration)
- launch {
- viewModel.iconAsset.collect { iconAsset ->
- if (iconAsset != -1) {
- view.setAnimation(iconAsset)
- // TODO: must replace call below once non-sfps asset logic and
- // shouldAnimateIconView logic is migrated to this ViewModel.
- view.playAnimation()
- }
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
new file mode 100644
index 0000000..475ef18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.iconView]. */
+object PromptIconViewBinder {
+ /**
+ * Binds [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] to
+ * [PromptIconViewModel].
+ */
+ @JvmStatic
+ fun bind(
+ iconView: LottieAnimationView,
+ iconOverlayView: LottieAnimationView,
+ iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
+ viewModel: PromptIconViewModel
+ ) {
+ iconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.onConfigurationChanged(iconView.context.resources.configuration)
+ if (iconViewLayoutParamSizeOverride != null) {
+ iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+ iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+
+ iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+ iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+ }
+
+ var faceIcon: AnimatedVectorDrawable? = null
+ val faceIconCallback =
+ object : Animatable2.AnimationCallback() {
+ override fun onAnimationStart(drawable: Drawable) {
+ viewModel.onAnimationStart()
+ }
+
+ override fun onAnimationEnd(drawable: Drawable) {
+ viewModel.onAnimationEnd()
+ }
+ }
+
+ launch {
+ viewModel.activeAuthType.collect { activeAuthType ->
+ if (iconViewLayoutParamSizeOverride == null) {
+ val width: Int
+ val height: Int
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> {
+ width = viewModel.fingerprintIconWidth
+ height = viewModel.fingerprintIconHeight
+ }
+ AuthType.Face -> {
+ width = viewModel.faceIconWidth
+ height = viewModel.faceIconHeight
+ }
+ }
+
+ iconView.layoutParams.width = width
+ iconView.layoutParams.height = height
+
+ iconOverlayView.layoutParams.width = width
+ iconOverlayView.layoutParams.height = height
+ }
+ }
+ }
+
+ launch {
+ viewModel.iconAsset
+ .sample(
+ combine(
+ viewModel.activeAuthType,
+ viewModel.shouldAnimateIconView,
+ viewModel.shouldRepeatAnimation,
+ viewModel.showingError,
+ ::toQuad
+ ),
+ ::toQuint
+ )
+ .collect {
+ (
+ iconAsset,
+ activeAuthType,
+ shouldAnimateIconView,
+ shouldRepeatAnimation,
+ showingError) ->
+ if (iconAsset != -1) {
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> {
+ iconView.setAnimation(iconAsset)
+ iconView.frame = 0
+
+ if (shouldAnimateIconView) {
+ iconView.playAnimation()
+ }
+ }
+ AuthType.Face -> {
+ faceIcon?.apply {
+ unregisterAnimationCallback(faceIconCallback)
+ stop()
+ }
+ faceIcon =
+ iconView.context.getDrawable(iconAsset)
+ as AnimatedVectorDrawable
+ faceIcon?.apply {
+ iconView.setImageDrawable(this)
+ if (shouldAnimateIconView) {
+ forceAnimationOnUI()
+ if (shouldRepeatAnimation) {
+ registerAnimationCallback(faceIconCallback)
+ }
+ start()
+ }
+ }
+ }
+ }
+ LottieColorUtils.applyDynamicColors(iconView.context, iconView)
+ viewModel.setPreviousIconWasError(showingError)
+ }
+ }
+ }
+
+ launch {
+ viewModel.iconOverlayAsset
+ .sample(
+ combine(
+ viewModel.shouldAnimateIconOverlay,
+ viewModel.showingError,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) ->
+ if (iconOverlayAsset != -1) {
+ iconOverlayView.setAnimation(iconOverlayAsset)
+ iconOverlayView.frame = 0
+ LottieColorUtils.applyDynamicColors(
+ iconOverlayView.context,
+ iconOverlayView
+ )
+
+ if (shouldAnimateIconOverlay) {
+ iconOverlayView.playAnimation()
+ }
+ viewModel.setPreviousIconOverlayWasError(showingError)
+ }
+ }
+ }
+
+ launch {
+ viewModel.shouldFlipIconView.collect { shouldFlipIconView ->
+ if (shouldFlipIconView) {
+ iconView.rotation = 180f
+ }
+ }
+ }
+
+ launch {
+ viewModel.contentDescriptionId.collect { id ->
+ if (id != -1) {
+ iconView.contentDescription = iconView.context.getString(id)
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
deleted file mode 100644
index dfd3a9b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.annotation.RawRes
-import android.content.res.Configuration
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.shared.model.DisplayRotation
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Models UI of [BiometricPromptLayout.iconView] */
-class PromptFingerprintIconViewModel
-@Inject
-constructor(
- private val displayStateInteractor: DisplayStateInteractor,
- promptSelectorInteractor: PromptSelectorInteractor,
-) {
- /** Current BiometricPromptLayout.iconView asset. */
- val iconAsset: Flow<Int> =
- combine(
- displayStateInteractor.currentRotation,
- displayStateInteractor.isFolded,
- displayStateInteractor.isInRearDisplayMode,
- promptSelectorInteractor.sensorType,
- ) {
- rotation: DisplayRotation,
- isFolded: Boolean,
- isInRearDisplayMode: Boolean,
- sensorType: FingerprintSensorType ->
- when (sensorType) {
- FingerprintSensorType.POWER_BUTTON ->
- getSideFpsAnimationAsset(rotation, isFolded, isInRearDisplayMode)
- // Replace below when non-SFPS iconAsset logic is migrated to this ViewModel
- else -> -1
- }
- }
-
- @RawRes
- private fun getSideFpsAnimationAsset(
- rotation: DisplayRotation,
- isDeviceFolded: Boolean,
- isInRearDisplayMode: Boolean,
- ): Int =
- when (rotation) {
- DisplayRotation.ROTATION_90 ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_portrait_reverse_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_topleft
- } else {
- R.raw.biometricprompt_portrait_base_topleft
- }
- DisplayRotation.ROTATION_270 ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_portrait_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_bottomright
- } else {
- R.raw.biometricprompt_portrait_base_bottomright
- }
- else ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_landscape_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_default
- } else {
- R.raw.biometricprompt_landscape_base
- }
- }
-
- /** Called on configuration changes */
- fun onConfigurationChanged(newConfig: Configuration) {
- displayStateInteractor.onConfigurationChanged(newConfig)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
new file mode 100644
index 0000000..11a5d8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.RawRes
+import android.content.res.Configuration
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.combine
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
+ */
+class PromptIconViewModel
+constructor(
+ promptViewModel: PromptViewModel,
+ private val displayStateInteractor: DisplayStateInteractor,
+ promptSelectorInteractor: PromptSelectorInteractor
+) {
+
+ /** Auth types for the UI to display. */
+ enum class AuthType {
+ Fingerprint,
+ Face,
+ Coex
+ }
+
+ /**
+ * Indicates what auth type the UI currently displays.
+ * Fingerprint-only auth -> Fingerprint
+ * Face-only auth -> Face
+ * Co-ex auth, implicit flow -> Face
+ * Co-ex auth, explicit flow -> Coex
+ */
+ val activeAuthType: Flow<AuthType> =
+ combine(
+ promptViewModel.modalities.distinctUntilChanged(),
+ promptViewModel.faceMode.distinctUntilChanged()
+ ) { modalities, faceMode ->
+ if (modalities.hasFaceAndFingerprint && !faceMode) {
+ AuthType.Coex
+ } else if (modalities.hasFaceOnly || faceMode) {
+ AuthType.Face
+ } else if (modalities.hasFingerprintOnly) {
+ AuthType.Fingerprint
+ } else {
+ throw IllegalStateException("unexpected modality: $modalities")
+ }
+ }
+
+ /** Whether an error message is currently being shown. */
+ val showingError = promptViewModel.showingError
+
+ /** Whether the previous icon shown displayed an error. */
+ private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether the previous icon overlay shown displayed an error. */
+ private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ fun setPreviousIconWasError(previousIconWasError: Boolean) {
+ _previousIconWasError.value = previousIconWasError
+ }
+
+ fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) {
+ _previousIconOverlayWasError.value = previousIconOverlayWasError
+ }
+
+ /** Called when iconView begins animating. */
+ fun onAnimationStart() {
+ _animationEnded.value = false
+ }
+
+ /** Called when iconView ends animating. */
+ fun onAnimationEnd() {
+ _animationEnded.value = true
+ }
+
+ private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /**
+ * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation
+ * ended).
+ */
+ val shouldPulseAnimation: Flow<Boolean> =
+ combine(_animationEnded, promptViewModel.isAuthenticating) {
+ animationEnded,
+ isAuthenticating ->
+ animationEnded && isAuthenticating
+ }
+ .distinctUntilChanged()
+
+ private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
+ val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
+
+ /** Layout params for fingerprint iconView */
+ val fingerprintIconWidth: Int = promptViewModel.fingerprintIconWidth
+ val fingerprintIconHeight: Int = promptViewModel.fingerprintIconHeight
+
+ /** Layout params for face iconView */
+ val faceIconWidth: Int = promptViewModel.faceIconWidth
+ val faceIconHeight: Int = promptViewModel.faceIconHeight
+
+ /** Current BiometricPromptLayout.iconView asset. */
+ val iconAsset: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint ->
+ combine(
+ displayStateInteractor.currentRotation,
+ displayStateInteractor.isFolded,
+ displayStateInteractor.isInRearDisplayMode,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ rotation: DisplayRotation,
+ isFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+ else ->
+ getFingerprintIconViewAsset(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ }
+ }
+ AuthType.Face ->
+ shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean ->
+ if (shouldPulseAnimation) {
+ val iconAsset =
+ if (_lastPulseLightToDark.value) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ _lastPulseLightToDark.value = !_lastPulseLightToDark.value
+ flowOf(iconAsset)
+ } else {
+ combine(
+ promptViewModel.isAuthenticated.distinctUntilChanged(),
+ promptViewModel.isAuthenticating.distinctUntilChanged(),
+ promptViewModel.isPendingConfirmation.distinctUntilChanged(),
+ promptViewModel.showingError.distinctUntilChanged()
+ ) {
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ getFaceIconViewAsset(
+ authState,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ AuthType.Coex ->
+ combine(
+ displayStateInteractor.currentRotation,
+ displayStateInteractor.isFolded,
+ displayStateInteractor.isInRearDisplayMode,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError,
+ ) {
+ rotation: DisplayRotation,
+ isFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+ else ->
+ getCoexIconViewAsset(
+ authState,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ }
+
+ private fun getFingerprintIconViewAsset(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isAuthenticated) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_success_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ }
+ } else if (showingError) {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ } else {
+ -1
+ }
+
+ @RawRes
+ private fun getSfpsIconViewAsset(
+ rotation: DisplayRotation,
+ isDeviceFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ ): Int =
+ when (rotation) {
+ DisplayRotation.ROTATION_90 ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_portrait_reverse_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_topleft
+ } else {
+ R.raw.biometricprompt_portrait_base_topleft
+ }
+ DisplayRotation.ROTATION_270 ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_portrait_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_bottomright
+ } else {
+ R.raw.biometricprompt_portrait_base_bottomright
+ }
+ else ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_landscape_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_default
+ } else {
+ R.raw.biometricprompt_landscape_base
+ }
+ }
+
+ @DrawableRes
+ private fun getFaceIconViewAsset(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticated && isPendingConfirmation) {
+ R.drawable.face_dialog_wink_from_dark
+ } else if (authState.isAuthenticated) {
+ R.drawable.face_dialog_dark_to_checkmark
+ } else if (isAuthenticating) {
+ _lastPulseLightToDark.value = false
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else if (showingError) {
+ R.drawable.face_dialog_dark_to_error
+ } else if (_previousIconWasError.value) {
+ R.drawable.face_dialog_error_to_idle
+ } else {
+ R.drawable.face_dialog_idle_static
+ }
+
+ @RawRes
+ private fun getCoexIconViewAsset(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+ } else if (isPendingConfirmation) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_unlock_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
+ }
+ } else if (authState.isAuthenticated) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_success_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ }
+ } else if (showingError) {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ } else {
+ -1
+ }
+
+ /** Current BiometricPromptLayout.biometric_icon_overlay asset. */
+ var iconOverlayAsset: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ displayStateInteractor.currentRotation,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ rotation: DisplayRotation,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconOverlayAsset(
+ rotation,
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else -> -1
+ }
+ }
+ AuthType.Face -> flowOf(-1)
+ }
+ }
+
+ @RawRes
+ private fun getSfpsIconOverlayAsset(
+ rotation: DisplayRotation,
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isAuthenticated) {
+ if (_previousIconOverlayWasError.value) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+ }
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconOverlayWasError.value) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ }
+ }
+ } else if (showingError) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ }
+ } else {
+ -1
+ }
+
+ /** Content description for iconView */
+ val contentDescriptionId: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ getFingerprintIconContentDescriptionId(
+ sensorType,
+ authState.isAuthenticated,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ AuthType.Face ->
+ combine(
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError,
+ ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+ ->
+ getFaceIconContentDescriptionId(authState, isAuthenticating, showingError)
+ }
+ }
+ }
+
+ private fun getFingerprintIconContentDescriptionId(
+ sensorType: FingerprintSensorType,
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isPendingConfirmation) {
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ R.string.security_settings_sfps_enroll_find_sensor_message
+ else -> R.string.fingerprint_dialog_authenticated_confirmation
+ }
+ } else if (isAuthenticating || isAuthenticated) {
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ R.string.security_settings_sfps_enroll_find_sensor_message
+ else -> R.string.fingerprint_dialog_touch_sensor
+ }
+ } else if (showingError) {
+ R.string.biometric_dialog_try_again
+ } else {
+ -1
+ }
+
+ private fun getFaceIconContentDescriptionId(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ R.string.biometric_dialog_face_icon_description_confirmed
+ } else if (authState.isAuthenticated) {
+ R.string.biometric_dialog_face_icon_description_authenticated
+ } else if (isAuthenticating) {
+ R.string.biometric_dialog_face_icon_description_authenticating
+ } else if (showingError) {
+ R.string.keyguard_face_failed
+ } else {
+ R.string.biometric_dialog_face_icon_description_idle
+ }
+
+ /** Whether the current BiometricPromptLayout.iconView asset animation should be playing. */
+ val shouldAnimateIconView: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else ->
+ shouldAnimateFingerprintIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ }
+ }
+ AuthType.Face ->
+ combine(
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+ ->
+ isAuthenticating ||
+ authState.isAuthenticated ||
+ showingError ||
+ _previousIconWasError.value
+ }
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError,
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else ->
+ shouldAnimateCoexIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ }
+
+ private fun shouldAnimateFingerprintIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError
+
+ private fun shouldAnimateSfpsIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = isAuthenticated || isAuthenticating || showingError
+
+ private fun shouldAnimateCoexIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ) =
+ (isAuthenticating && _previousIconWasError.value) ||
+ isPendingConfirmation ||
+ isAuthenticated ||
+ showingError
+
+ /** Whether the current iconOverlayAsset animation should be playing. */
+ val shouldAnimateIconOverlay: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconOverlay(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else -> false
+ }
+ }
+ AuthType.Face -> flowOf(false)
+ }
+ }
+
+ private fun shouldAnimateSfpsIconOverlay(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError
+
+ /** Whether the iconView should be flipped due to a device using reverse default rotation . */
+ val shouldFlipIconView: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ displayStateInteractor.currentRotation
+ ) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ (rotation == DisplayRotation.ROTATION_180)
+ else -> false
+ }
+ }
+ AuthType.Face -> flowOf(false)
+ }
+ }
+
+ /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */
+ val shouldRepeatAnimation: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> flowOf(false)
+ AuthType.Face -> promptViewModel.isAuthenticating.map { it }
+ }
+ }
+
+ /** Called on configuration changes */
+ fun onConfigurationChanged(newConfig: Configuration) {
+ displayStateInteractor.onConfigurationChanged(newConfig)
+ }
+
+ /** iconView assets for caching */
+ fun getRawAssets(hasSfps: Boolean): List<Int> {
+ return if (hasSfps) {
+ listOf(
+ R.raw.biometricprompt_fingerprint_to_error_landscape,
+ R.raw.biometricprompt_folded_base_bottomright,
+ R.raw.biometricprompt_folded_base_default,
+ R.raw.biometricprompt_folded_base_topleft,
+ R.raw.biometricprompt_landscape_base,
+ R.raw.biometricprompt_portrait_base_bottomright,
+ R.raw.biometricprompt_portrait_base_topleft,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
+ R.raw.biometricprompt_symbol_error_to_success_landscape,
+ R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
+ R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ )
+ } else {
+ listOf(
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+ R.raw.fingerprint_dialogue_error_to_success_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 267afae..e49b4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -28,10 +28,10 @@
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.PromptKind
-import com.android.systemui.biometrics.ui.binder.Spaghetti
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.Job
@@ -39,7 +39,6 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -51,25 +50,29 @@
class PromptViewModel
@Inject
constructor(
- private val displayStateInteractor: DisplayStateInteractor,
- private val promptSelectorInteractor: PromptSelectorInteractor,
+ displayStateInteractor: DisplayStateInteractor,
+ promptSelectorInteractor: PromptSelectorInteractor,
private val vibrator: VibratorHelper,
@Application context: Context,
private val featureFlags: FeatureFlags,
) {
- /** Models UI of [BiometricPromptLayout.iconView] */
- val fingerprintIconViewModel: PromptFingerprintIconViewModel =
- PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
promptSelectorInteractor.prompt
.map { it?.modalities ?: BiometricModalities() }
.distinctUntilChanged()
- // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state
- private var _legacyState = MutableStateFlow(Spaghetti.BiometricState.STATE_IDLE)
- val legacyState: StateFlow<Spaghetti.BiometricState> = _legacyState.asStateFlow()
+ /** Layout params for fingerprint iconView */
+ val fingerprintIconWidth: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_width)
+ val fingerprintIconHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_height)
+
+ /** Layout params for face iconView */
+ val faceIconWidth: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ val faceIconHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -82,6 +85,12 @@
/** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
+ /** If the auth is pending confirmation. */
+ val isPendingConfirmation: Flow<Boolean> =
+ isAuthenticated.map { authState ->
+ authState.isAuthenticated && authState.needsUserConfirmation
+ }
+
private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** The kind of credential the user has. */
@@ -96,6 +105,9 @@
/** A message to show the user, if there is an error, hint, or help to show. */
val message: Flow<PromptMessage> = _message.asStateFlow()
+ /** Whether an error message is currently being shown. */
+ val showingError: Flow<Boolean> = message.map { it.isError }.distinctUntilChanged()
+
private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace }
private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending)
@@ -141,6 +153,38 @@
!isOverlayTouched && size.isNotSmall
}
+ /**
+ * When fingerprint and face modalities are enrolled, indicates whether only face auth has
+ * started.
+ *
+ * True when fingerprint and face modalities are enrolled and implicit flow is active. This
+ * occurs in co-ex auth when confirmation is not required and only face auth is started, then
+ * becomes false when device transitions to explicit flow after a first error, when the
+ * fingerprint sensor is started.
+ *
+ * False when the dialog opens in explicit flow (fingerprint and face modalities enrolled but
+ * confirmation is required), or if user has only fingerprint enrolled, or only face enrolled.
+ */
+ val faceMode: Flow<Boolean> =
+ combine(modalities, isConfirmationRequired, fingerprintStartMode) {
+ modalities: BiometricModalities,
+ isConfirmationRequired: Boolean,
+ fingerprintStartMode: FingerprintStartMode ->
+ if (modalities.hasFaceAndFingerprint) {
+ if (isConfirmationRequired) {
+ false
+ } else {
+ !fingerprintStartMode.isStarted
+ }
+ } else {
+ false
+ }
+ }
+ .distinctUntilChanged()
+
+ val iconViewModel: PromptIconViewModel =
+ PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
@@ -184,9 +228,9 @@
val isConfirmButtonVisible: Flow<Boolean> =
combine(
size,
- isAuthenticated,
- ) { size, authState ->
- size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation
+ isPendingConfirmation,
+ ) { size, isPendingConfirmation ->
+ size.isNotSmall && isPendingConfirmation
}
.distinctUntilChanged()
@@ -293,7 +337,6 @@
_isAuthenticated.value = PromptAuthState(false)
_forceMediumSize.value = true
_message.value = PromptMessage.Error(message)
- _legacyState.value = Spaghetti.BiometricState.STATE_ERROR
if (hapticFeedback) {
vibrator.error(failedModality)
@@ -305,7 +348,7 @@
if (authenticateAfterError) {
showAuthenticating(messageAfterError)
} else {
- showInfo(messageAfterError)
+ showHelp(messageAfterError)
}
}
}
@@ -325,15 +368,12 @@
private fun supportsRetry(failedModality: BiometricModality) =
failedModality == BiometricModality.Face
- suspend fun showHelp(message: String) = showHelp(message, clearIconError = false)
- suspend fun showInfo(message: String) = showHelp(message, clearIconError = true)
-
/**
* Show a persistent help message.
*
* Will be show even if the user has already authenticated.
*/
- private suspend fun showHelp(message: String, clearIconError: Boolean) {
+ suspend fun showHelp(message: String) {
val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated
if (!alreadyAuthenticated) {
_isAuthenticating.value = false
@@ -343,16 +383,6 @@
_message.value =
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
- _legacyState.value =
- if (alreadyAuthenticated && isConfirmationRequired.first()) {
- Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
- } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
- Spaghetti.BiometricState.STATE_AUTHENTICATED
- } else if (clearIconError) {
- Spaghetti.BiometricState.STATE_IDLE
- } else {
- Spaghetti.BiometricState.STATE_HELP
- }
messageJob?.cancel()
messageJob = null
@@ -376,7 +406,6 @@
_message.value =
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
- _legacyState.value = Spaghetti.BiometricState.STATE_HELP
messageJob?.cancel()
messageJob = launch {
@@ -396,7 +425,6 @@
_isAuthenticating.value = true
_isAuthenticated.value = PromptAuthState(false)
_message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message)
- _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATING
// reset the try again button(s) after the user attempts a retry
if (isRetry) {
@@ -427,12 +455,6 @@
_isAuthenticated.value =
PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay)
_message.value = PromptMessage.Empty
- _legacyState.value =
- if (needsUserConfirmation) {
- Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
- } else {
- Spaghetti.BiometricState.STATE_AUTHENTICATED
- }
if (!needsUserConfirmation) {
vibrator.success(modality)
@@ -472,7 +494,6 @@
_isAuthenticated.value = authState.asExplicitlyConfirmed()
_message.value = PromptMessage.Empty
- _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATED
vibrator.success(authState.authenticatedModality)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 2bd6258..21578f4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -146,6 +146,16 @@
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
fun show(isScrimmed: Boolean) {
+ if (primaryBouncerView.delegate == null) {
+ Log.d(
+ TAG,
+ "PrimaryBouncerInteractor#show is being called before the " +
+ "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " +
+ "primaryBouncer state."
+ )
+ return
+ }
+
// Reset some states as we show the bouncer.
repository.setKeyguardAuthenticatedBiometrics(null)
repository.setPrimaryStartingToHide(false)
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 0bf5069..7d73896 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 @@
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 @@
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 @@
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 @@
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 @@
* - 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 @@
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 0000000..8d5b84f
--- /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 0000000..b0e6931
--- /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 b8de8d8..e449274 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 @@
fun getDimensionPixelSize(id: Int): Int
}
-@ExperimentalCoroutinesApi
@SysUISingleton
class ConfigurationRepositoryImpl
@Inject
@@ -119,7 +122,12 @@
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/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
index f9c4f29..1a214ba 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
@@ -16,7 +16,7 @@
package com.android.systemui.communal.data.model
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize
/** Metadata for the default widgets */
data class CommunalWidgetMetadata(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 53b6879..485e512 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 @@
class CommunalRepositoryImpl
@Inject
constructor(
- private val featureFlags: FeatureFlagsClassic,
+ private val featureFlags: FeatureFlags,
+ private val featureFlagsClassic: FeatureFlagsClassic,
) : CommunalRepository {
override val isCommunalEnabled: Boolean
get() =
- featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
- featureFlags.isEnabled(Flags.COMMUNAL_HUB)
+ featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
+ featureFlags.communalHub()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index f13b62f..77025dc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -20,6 +20,7 @@
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.BroadcastReceiver
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
@@ -28,11 +29,12 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -43,6 +45,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
/** Encapsulates the state of widgets for communal mode. */
@@ -52,6 +55,9 @@
/** Widgets that are allowed to render in the glanceable hub */
val communalWidgetAllowlist: List<CommunalWidgetMetadata>
+
+ /** A flow of information about all the communal widgets to show. */
+ val communalWidgets: Flow<List<CommunalWidgetContentModel>>
}
@SysUISingleton
@@ -67,7 +73,7 @@
private val userManager: UserManager,
private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
@@ -88,49 +94,59 @@
// Widgets that should be rendered in communal mode.
private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
- private val isUserUnlocked: Flow<Boolean> = callbackFlow {
- if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
- awaitClose()
- }
-
- fun isUserUnlockingOrUnlocked(): Boolean {
- return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
- }
-
- fun send() {
- trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
- }
-
- if (isUserUnlockingOrUnlocked()) {
- send()
- awaitClose()
- } else {
- val receiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- send()
- }
+ private val isUserUnlocked: Flow<Boolean> =
+ callbackFlow {
+ if (!communalRepository.isCommunalEnabled) {
+ awaitClose()
}
- broadcastDispatcher.registerReceiver(
- receiver,
- IntentFilter(Intent.ACTION_USER_UNLOCKED),
- )
+ fun isUserUnlockingOrUnlocked(): Boolean {
+ return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
+ }
- awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+ fun send() {
+ trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
+ }
+
+ if (isUserUnlockingOrUnlocked()) {
+ send()
+ awaitClose()
+ } else {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ send()
+ }
+ }
+
+ broadcastDispatcher.registerReceiver(
+ receiver,
+ IntentFilter(Intent.ACTION_USER_UNLOCKED),
+ )
+
+ awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+ }
+ }
+ .distinctUntilChanged()
+
+ private val isHostActive: Flow<Boolean> =
+ isUserUnlocked.map {
+ if (it) {
+ startListening()
+ true
+ } else {
+ stopListening()
+ clearWidgets()
+ false
+ }
}
- }
override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> =
- isUserUnlocked.map { isUserUnlocked ->
- if (!isUserUnlocked) {
- clearWidgets()
- stopListening()
+ isHostActive.map { isHostActive ->
+ if (!isHostActive || !featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
return@map null
}
- startListening()
-
val providerInfo =
appWidgetManager.installedProviders.find {
it.loadLabel(packageManager).equals(WIDGET_LABEL)
@@ -144,6 +160,42 @@
return@map addWidget(providerInfo)
}
+ override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+ isHostActive.map { isHostActive ->
+ if (!isHostActive) {
+ return@map emptyList()
+ }
+
+ // The allowlist should be fetched from the local database with all the metadata tied to
+ // a widget, including an appWidgetId if it has been bound. Before the database is set
+ // up, we are going to use the app widget host as the source of truth for bound widgets,
+ // and rebind each time on boot.
+
+ // Remove all previously bound widgets.
+ appWidgetHost.appWidgetIds.forEach { appWidgetHost.deleteAppWidgetId(it) }
+
+ val inventory = mutableListOf<CommunalWidgetContentModel>()
+
+ // Bind all widgets from the allowlist.
+ communalWidgetAllowlist.forEach {
+ val id = appWidgetHost.allocateAppWidgetId()
+ appWidgetManager.bindAppWidgetId(
+ id,
+ ComponentName.unflattenFromString(it.componentName),
+ )
+
+ inventory.add(
+ CommunalWidgetContentModel(
+ appWidgetId = id,
+ providerInfo = appWidgetManager.getAppWidgetInfo(id),
+ priority = it.priority,
+ )
+ )
+ }
+
+ return@map inventory.toList()
+ }
+
private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> {
val componentNames =
applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist)
@@ -151,7 +203,7 @@
CommunalWidgetMetadata(
componentName = name,
priority = componentNames.size - index,
- sizes = listOf(CommunalContentSize.HALF)
+ sizes = listOf(CommunalContentSize.HALF),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 04bb6ae..6238707 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -18,7 +18,8 @@
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -38,4 +39,12 @@
/** A flow of info about the widget to be displayed, or null if widget is unavailable. */
val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
+
+ /**
+ * A flow of information about widgets to be shown in communal hub.
+ *
+ * Currently only showing persistent widgets that have been bound to the app widget service
+ * (have an allocated id).
+ */
+ val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
deleted file mode 100644
index 0bd7d86..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.android.systemui.communal.shared
-
-/** Supported sizes for communal content in the layout grid. */
-enum class CommunalContentSize {
- FULL,
- HALF,
- THIRD,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
rename to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
index 0803a01..109ed2d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
import android.appwidget.AppWidgetProviderInfo
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
index 0803a01..7f05b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
@@ -12,15 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
-import android.appwidget.AppWidgetProviderInfo
+enum class CommunalContentCategory {
+ /** The content persists in the communal hub until removed by the user. */
+ PERSISTENT,
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetId: Int,
-)
+ /** The content temporarily shows up in the communal hub when certain conditions are met. */
+ TRANSIENT,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index 0803a01..39a6476 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -12,15 +12,18 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
-import android.appwidget.AppWidgetProviderInfo
+/** Supported sizes for communal content in the layout grid. */
+enum class CommunalContentSize {
+ /** Content takes the full height of the column. */
+ FULL,
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetId: Int,
-)
+ /** Content takes half of the height of the column. */
+ HALF,
+
+ /** Content takes a third of the height of the column. */
+ THIRD,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
similarity index 80%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 0803a01..e141dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -12,15 +12,15 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
import android.appwidget.AppWidgetProviderInfo
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
+/** Encapsulates data for a communal widget. */
+data class CommunalWidgetContentModel(
val appWidgetId: Int,
+ val providerInfo: AppWidgetProviderInfo,
+ val priority: Int,
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
index 2a08d7f..0daf7b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
@@ -20,7 +20,7 @@
import android.appwidget.AppWidgetManager
import android.content.Context
import android.util.SizeF
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
new file mode 100644
index 0000000..98060dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.communal.ui.model
+
+import android.view.View
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/**
+ * Encapsulates data for a communal content that holds a view.
+ *
+ * This model stays in the UI layer.
+ */
+data class CommunalContentUiModel(
+ val view: View,
+ val size: CommunalContentSize,
+ val priority: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index ddeb1d6..25c64ea 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,17 +16,43 @@
package com.android.systemui.communal.ui.viewmodel
+import android.appwidget.AppWidgetHost
+import android.content.Context
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
@SysUISingleton
class CommunalViewModel
@Inject
constructor(
+ @Application private val context: Context,
+ private val appWidgetHost: AppWidgetHost,
+ communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
) {
/** Whether communal hub should show tutorial content. */
val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
+
+ /** List of widgets to be displayed in the communal hub. */
+ val widgetContent: Flow<List<CommunalContentUiModel>> =
+ communalInteractor.widgetContent.map {
+ it.map {
+ // TODO(b/306406256): As adding and removing widgets functionalities are
+ // supported, cache the host views so they're not recreated each time.
+ val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
+ return@map CommunalContentUiModel(
+ view = hostView,
+ priority = it.priority,
+ // All widgets have HALF size.
+ size = CommunalContentSize.HALF,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
index 8fba342..d7bbea6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
index 20b2494..f7b6b0f 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
@@ -652,8 +652,7 @@
CrossFadeHelper.fadeOut(
mLayout,
mFadeOutDuration,
- /* delay= */ 0,
- /* endRunnable= */ null);
+ /* delay= */ 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 9221832b..4bdea75 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -79,4 +79,7 @@
context: Context,
viewModel: CommunalViewModel,
): View
+
+ /** Creates a container that hosts the communal UI and handles gesture transitions. */
+ fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5d6949b..d8ff535 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.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 @@
@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 f0d7592..04b2852d 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.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 @@
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 4f16685..3e2ecee 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.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 @@
/** 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 @@
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 @@
)
awaitClose { displayManager.unregisterDisplayListener(callback) }
}
+ .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) }
.flowOn(backgroundCoroutineDispatcher)
override val displayChangeEvent: Flow<Int> =
@@ -134,13 +130,9 @@
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 @@
}
/** 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 @@
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 @@
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 d19efbd..87b0f01 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.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 @@
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 @@
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 86ef439..91f535d 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.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 @@
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 @@
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 83c239f..dd58604 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -17,15 +17,19 @@
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 com.android.systemui.util.kotlin.UnflaggedApplication
+import com.android.systemui.util.kotlin.UnflaggedBackground
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Named
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlin.coroutines.CoroutineContext
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. */
@@ -35,11 +39,10 @@
private val systemExitRestarter: SystemExitRestarter,
private val conditions: Set<@JvmSuppressWildcards Condition>,
@Named(RESTART_DELAY) private val restartDelaySec: Long,
- @Application private val applicationScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @UnflaggedApplication private val applicationScope: CoroutineScope,
+ @UnflaggedBackground private val backgroundDispatcher: CoroutineContext,
) : Restarter {
- private var restartJob: Job? = null
private var pendingReason = ""
private var androidRestartRequested = false
@@ -57,17 +60,19 @@
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 @@
* 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 ecd8b0e..8c81fbb 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -118,7 +118,7 @@
// TODO(b/292213543): Tracking Bug
@JvmField
val NOTIFICATION_GROUP_EXPANSION_CHANGE =
- unreleasedFlag("notification_group_expansion_change", teamfood = true)
+ releasedFlag("notification_group_expansion_change")
// TODO(b/301955929)
@JvmField
@@ -549,12 +549,6 @@
val LOCKSCREEN_ENABLE_LANDSCAPE =
unreleasedFlag("lockscreen.enable_landscape")
- // TODO(b/273443374): Tracking Bug
- @Keep
- @JvmField
- val LOCKSCREEN_LIVE_WALLPAPER =
- sysPropBooleanFlag("persist.wm.debug.lockscreen_live_wallpaper", default = true)
-
// TODO(b/281648899): Tracking bug
@Keep
@JvmField
@@ -776,7 +770,7 @@
val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
// TODO(b/289573946): Tracking Bug
- @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true)
+ @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text")
// TODO(b/302087895): Tracking Bug
@JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data")
diff --git a/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt
new file mode 100644
index 0000000..f5b30cf
--- /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 3120638..dc08570 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)
+ }
}
- }
+ batteryControllerLazy.get().addCallback(batteryCallback)
- override fun canRestartNow(retryFn: () -> Unit): Boolean {
- if (!listenersAdded) {
- listenersAdded = true
- batteryController.addCallback(batteryCallback)
- }
+ trySend(batteryControllerLazy.get().isPluggedIn)
- this.retryFn = retryFn
-
- 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 7ccc26c..4a5cc64 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 @@
* }
* ````
*/
- 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 @@
* }
* ```
*/
- 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 0000000..2aa397f
--- /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 49e61af..3c9bc36 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 val canRestartNow: Flow<Boolean>
+ get() {
+ return powerInteractorLazy.get().isAsleep
}
-
- override fun canRestartNow(retryFn: () -> Unit): Boolean {
- if (!listenersAdded) {
- listenersAdded = true
- wakefulnessLifecycle.addObserver(observer)
- }
-
- this.retryFn = retryFn
-
- return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index bc07139..6f491d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -36,9 +36,9 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
+import com.android.systemui.util.TraceUtils.Companion.runBlocking
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
class CustomizationProvider :
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@@ -132,7 +132,7 @@
throw UnsupportedOperationException()
}
- return runBlocking(mainDispatcher) { insertSelection(values) }
+ return runBlocking("$TAG#insert", mainDispatcher) { insertSelection(values) }
}
override fun query(
@@ -142,7 +142,7 @@
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? {
- return runBlocking(mainDispatcher) {
+ return runBlocking("$TAG#query", mainDispatcher) {
when (uriMatcher.match(uri)) {
MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
MATCH_CODE_ALL_SLOTS -> querySlots()
@@ -172,7 +172,7 @@
throw UnsupportedOperationException()
}
- return runBlocking(mainDispatcher) { deleteSelection(uri, selectionArgs) }
+ return runBlocking("$TAG#delete", mainDispatcher) { deleteSelection(uri, selectionArgs) }
}
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 4779895..2b1cdc2 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 @@
// 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 @@
}
}
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 0bac40b..c8c06ae 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 @@
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 e893c63..4e6a872 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.concurrent.Executor;
import java.util.function.Consumer;
-
-
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -1517,12 +1515,11 @@
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/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 3cdff76..6a0d595 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -56,6 +56,10 @@
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.google.errorprone.annotations.CompileTimeConstant
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -78,10 +82,6 @@
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
/**
* API to run face authentication and detection for device entry / on keyguard (as opposed to the
@@ -368,10 +368,12 @@
return arrayOf(
Pair(
and(
- displayStateInteractor.isDefaultDisplayOff,
- keyguardTransitionInteractor.isFinishedInStateWhere(
- KeyguardState::deviceIsAwakeInState),
- ).isFalse(),
+ displayStateInteractor.isDefaultDisplayOff,
+ keyguardTransitionInteractor.isFinishedInStateWhere(
+ KeyguardState::deviceIsAwakeInState
+ ),
+ )
+ .isFalse(),
// this can happen if an app is requesting for screen off, the display can
// turn off without wakefulness.isStartingToSleepOrAsleep calls
"displayIsNotOffWhileFullyTransitionedToAwake",
@@ -381,10 +383,7 @@
"isFaceAuthEnrolledAndEnabled"
),
Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
- Pair(
- powerInteractor.isAsleep.isFalse(),
- "deviceNotAsleep"
- ),
+ Pair(powerInteractor.isAsleep.isFalse(), "deviceNotAsleep"),
Pair(
keyguardInteractor.isSecureCameraActive
.isFalse()
@@ -430,10 +429,15 @@
private val faceAuthCallback =
object : FaceManager.AuthenticationCallback() {
override fun onAuthenticationFailed() {
- _authenticationStatus.value = FailedFaceAuthenticationStatus()
_isAuthenticated.value = false
faceAuthLogger.authenticationFailed()
- onFaceAuthRequestCompleted()
+ if (!_isLockedOut.value) {
+ // onAuthenticationError gets invoked before onAuthenticationFailed when the
+ // last auth attempt locks out face authentication.
+ // Skip updating the authentication status in such a scenario.
+ _authenticationStatus.value = FailedFaceAuthenticationStatus()
+ onFaceAuthRequestCompleted()
+ }
}
override fun onAuthenticationAcquired(acquireInfo: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 84cd3ef..3eef6aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import java.util.UUID
@@ -48,8 +49,8 @@
* [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
* this repository.
*
- * To print all transitions to logcat to help with debugging, run this command:
- * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ * To print all transitions to logcat to help with debugging, run this command: adb shell settings
+ * put global systemui/buffer/KeyguardLog VERBOSE
*
* This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
*/
@@ -73,11 +74,8 @@
/**
* Begin a transition from one state to another. Transitions are interruptible, and will issue a
* [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
- *
- * When canceled, there are two options: to continue from the current position of the prior
- * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
*/
- fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
+ fun startTransition(info: TransitionInfo): UUID?
/**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -138,10 +136,7 @@
)
}
- override fun startTransition(
- info: TransitionInfo,
- resetIfCanceled: Boolean,
- ): UUID? {
+ override fun startTransition(info: TransitionInfo): UUID? {
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return null
@@ -149,10 +144,10 @@
val startingValue =
if (lastStep.transitionState != TransitionState.FINISHED) {
Log.i(TAG, "Transition still active: $lastStep, canceling")
- if (resetIfCanceled) {
- 0f
- } else {
- lastStep.value
+ when (info.modeOnCanceled) {
+ TransitionModeOnCanceled.LAST_VALUE -> lastStep.value
+ TransitionModeOnCanceled.RESET -> 0f
+ TransitionModeOnCanceled.REVERSE -> 1f - lastStep.value
}
} else {
0f
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 d06f31f..7e360cf 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.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 @@
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/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 6e0aa4c..a331a66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -65,8 +66,21 @@
)
.collect { (_, lastStartedStep, occluded) ->
if (lastStartedStep.to == KeyguardState.AOD) {
- startTransitionTo(
+ val toState =
if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+ val modeOnCanceled =
+ if (
+ toState == KeyguardState.LOCKSCREEN &&
+ lastStartedStep.from == KeyguardState.LOCKSCREEN
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+
+ startTransitionTo(
+ toState = toState,
+ modeOnCanceled = modeOnCanceled,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index c67153a..eace0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
@@ -114,8 +115,9 @@
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
- resetIfCancelled = true,
+ toState =
+ if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index c39a4c9..95ac0d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -27,9 +27,11 @@
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.TraceUtils.Companion.launch
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
@@ -42,7 +44,6 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.launch
@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -135,7 +136,7 @@
private fun listenForLockscreenToDreaming() {
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToDreaming") {
keyguardInteractor.isAbleToDream
.sample(
combine(
@@ -168,7 +169,7 @@
}
private fun listenForLockscreenToPrimaryBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -183,7 +184,7 @@
}
private fun listenForLockscreenToAlternateBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
keyguardInteractor.alternateBouncerShowing
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -201,7 +202,7 @@
/* Starts transitions when manually dragging up the bouncer from the lockscreen. */
private fun listenForLockscreenToPrimaryBouncerDragging() {
var transitionId: UUID? = null
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
shadeRepository.shadeModel
.sample(
combine(
@@ -286,7 +287,7 @@
return
}
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToGone") {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -303,7 +304,7 @@
return
}
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToGoneDragging") {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -316,7 +317,7 @@
}
private fun listenForLockscreenToOccluded() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToOccluded") {
keyguardInteractor.isKeyguardOccluded
.sample(transitionInteractor.startedKeyguardState, ::Pair)
.collect { (isOccluded, keyguardState) ->
@@ -328,7 +329,7 @@
}
private fun listenForLockscreenToAodOrDozing() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
powerInteractor.isAsleep
.sample(
combine(
@@ -340,8 +341,20 @@
)
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
- startTransitionTo(
+ val toState =
if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+ val modeOnCanceled =
+ if (
+ toState == KeyguardState.AOD &&
+ lastStartedStep.from == KeyguardState.AOD
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ startTransitionTo(
+ toState = toState,
+ modeOnCanceled = modeOnCanceled,
)
}
}
@@ -362,6 +375,7 @@
}
companion object {
+ const val TAG = "FromLockscreenTransitionInteractor"
private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index f1649fb..24b6661 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
@@ -236,7 +237,7 @@
getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
this.duration = duration.inWholeMilliseconds
},
- resetIfCancelled = true
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index de791aa..fe9370f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -23,7 +23,6 @@
import android.content.Intent
import android.util.Log
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
@@ -44,11 +43,12 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.TraceUtils.Companion.traceAsync
+import com.android.systemui.util.TraceUtils.Companion.withContext
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -59,7 +59,6 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -417,10 +416,8 @@
}
private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
- traceAsync(TAG, "isFeatureDisabledByDevicePolicy") {
- withContext(backgroundDispatcher) {
- devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
- }
+ withContext("$TAG#isFeatureDisabledByDevicePolicy", backgroundDispatcher) {
+ devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
}
companion object {
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 fbe26de..b0b8577 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 @@
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/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 54c6d5f..7601808 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
@@ -49,7 +50,7 @@
fun startTransitionTo(
toState: KeyguardState,
animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
- resetIfCancelled: Boolean = false,
+ modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
): UUID? {
if (
fromState != transitionInteractor.startedKeyguardState.value &&
@@ -73,8 +74,8 @@
fromState,
toState,
animator,
- ),
- resetIfCancelled
+ modeOnCanceled,
+ )
)
}
@@ -91,8 +92,8 @@
// so use the last finishedKeyguardState to determine the overriding FROM state
if (finishedKeyguardState == fromState) {
startTransitionTo(
- KeyguardState.OCCLUDED,
- resetIfCancelled = true,
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
index bfccf3fe..7a37365 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -22,7 +22,13 @@
val ownerName: String,
val from: KeyguardState,
val to: KeyguardState,
- val animator: ValueAnimator?, // 'null' animator signal manual control
+ /** [null] animator signals manual control, otherwise transition run by the animator */
+ val animator: ValueAnimator?,
+ /**
+ * If the transition resets in the cancellation of another transition, use this mode to
+ * determine how to continue.
+ */
+ val modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
) {
override fun toString(): String =
"TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
new file mode 100644
index 0000000..56f90bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** When canceled, provide different ways to start the next transition. */
+enum class TransitionModeOnCanceled {
+ /** Proceed from the last value. If canceled at .7, start from .7 and end at 1 */
+ LAST_VALUE,
+ /** Start over from 0. If canceled at .7, start from 0 and end at 1 */
+ RESET,
+ /** Reverse the transition. If canceled at .7, start from 1-.7 (0.3) and end at 1 */
+ REVERSE
+}
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 0000000..023d16ca
--- /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 0000000..da74f2f
--- /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 0783181..0e95be2 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.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 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 @@
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 @@
)
}
- /** 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/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 67531ad..fd6b3f1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -190,17 +190,6 @@
}
}
- /**
- * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is
- * unique for each tile.
- * go/qs-tile-refactor
- */
- @Provides
- @QSTilesDefaultLog
- public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) {
- return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */);
- }
-
@Provides
@QSTilesLogBuffers
public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
deleted file mode 100644
index 6575cdd..0000000
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
+++ /dev/null
@@ -1,28 +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.log.dagger
-
-import javax.inject.Qualifier
-
-/**
- * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
- * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
- * tile, add one to the map provided by the [QSTilesLogBuffers]
- */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class QSTilesDefaultLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 565bf24..baa07c1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -95,7 +95,7 @@
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Boolean, newVal: Boolean ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -114,7 +114,7 @@
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Int, newVal: Int ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -133,7 +133,7 @@
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Int?, newVal: Int? ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -152,7 +152,7 @@
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: String? ->
+ return this.pairwiseBy(initialValueFun) { prevVal: String?, newVal: String? ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -176,7 +176,7 @@
)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
+ return this.pairwiseBy(initialValueFun) { prevVal: List<T>, newVal: List<T> ->
if (prevVal != newVal) {
// TODO(b/267761156): Can we log list changes without using toString?
tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index a48e56a..7cb5b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -731,6 +731,7 @@
removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
removedPlayer?.run {
debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
+ onDestroy()
}
}
@@ -1302,6 +1303,7 @@
val removedPlayer = removeMediaPlayer(key)
if (removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
+ removedPlayer.onDestroy()
}
val sortKey =
MediaSortKey(
@@ -1329,6 +1331,7 @@
val removedPlayer = removeMediaPlayer(key)
if (!update && removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
+ removedPlayer.onDestroy()
}
val sortKey =
MediaSortKey(
@@ -1357,7 +1360,10 @@
// MediaPlayer should not be visible
// no need to set isDismissed flag.
val removedPlayer = removeMediaPlayer(newKey)
- removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
+ removedPlayer?.run {
+ debugLogger?.logPotentialMemoryLeak(newKey)
+ onDestroy()
+ }
mediaData.put(newKey, it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index a53f0f1..1962119 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -23,7 +23,6 @@
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_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.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -35,16 +34,48 @@
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)
+ }
+ }
+
+ /**
+ * 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 0bbcfd9..04d5566 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 @@
component =
componentFactory.create(
hostUserHandle = hostUserHandle,
+ hostUid = hostUid,
callingPackage = callingPackage,
view = this,
resultHandler = this,
@@ -305,6 +306,17 @@
)
}
+ 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 @@
*/
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 8c6f307..d247122 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 @@
@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 @@
/** 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 69132d3..67ef119 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 @@
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 fa418fc..f7cc589 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 @@
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 @@
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ mUid,
appName == null
? SessionCreationSource.CAST
: SessionCreationSource.APP);
@@ -249,6 +250,10 @@
setUpDialog(mDialog);
mDialog.show();
+
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid);
+ }
}
@Override
@@ -325,6 +330,9 @@
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/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 051eeb0..bd13d06 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -48,6 +48,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -538,12 +539,17 @@
Log.i(TAG, "Launching activity before click");
} else {
Log.i(TAG, "The activity is starting");
- ActivityLaunchAnimator.Controller controller = mViewClicked == null
- ? null
- : ActivityLaunchAnimator.Controller.fromView(mViewClicked, 0);
- mUiHandler.post(() ->
- mActivityStarter.startPendingIntentDismissingKeyguard(
- pendingIntent, null, controller)
+
+ ActivityLaunchAnimator.Controller controller =
+ mViewClicked == null ? null :
+ ActivityLaunchAnimator.Controller.fromView(
+ mViewClicked,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+ );
+ mActivityStarter.startPendingIntentMaybeDismissingKeyguard(
+ pendingIntent,
+ /* intentSentUiThreadCallback= */ null,
+ controller
);
}
}
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 959afd8..f37f58d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -30,12 +30,12 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
@@ -45,7 +45,9 @@
import com.android.systemui.qs.logging.QSLogger;
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;
@@ -69,6 +71,8 @@
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final FeatureFlags mFlags;
private final PanelInteractor mPanelInteractor;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final UserContextProvider mUserContextProvider;
private long mMillisUntilFinished = 0;
@@ -88,7 +92,9 @@
KeyguardDismissUtil keyguardDismissUtil,
KeyguardStateController keyguardStateController,
DialogLaunchAnimator dialogLaunchAnimator,
- PanelInteractor panelInteractor
+ PanelInteractor panelInteractor,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+ UserContextProvider userContextProvider
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -99,6 +105,8 @@
mKeyguardStateController = keyguardStateController;
mDialogLaunchAnimator = dialogLaunchAnimator;
mPanelInteractor = panelInteractor;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+ mUserContextProvider = userContextProvider;
}
@Override
@@ -190,6 +198,10 @@
} else {
dialog.show();
}
+
+ int uid = mUserContextProvider.getUserContext().getUserId();
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
+
return false;
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 1b0d5f9..2f8fe42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -32,7 +32,6 @@
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +42,7 @@
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -55,7 +55,7 @@
private final KeyguardStateController mKeyguard;
protected IndividualSensorPrivacyController mSensorPrivacyController;
- private final SafetyCenterManager mSafetyCenterManager;
+ private final Boolean mIsSafetyCenterEnabled;
/**
* @return Id of the sensor that will be toggled
@@ -89,7 +89,7 @@
statusBarStateController, activityStarter, qsLogger);
mSensorPrivacyController = sensorPrivacyController;
mKeyguard = keyguardStateController;
- mSafetyCenterManager = safetyCenterManager;
+ mIsSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
mSensorPrivacyController.observe(getLifecycle(), this);
}
@@ -138,7 +138,7 @@
@Override
public Intent getLongClickIntent() {
- if (mSafetyCenterManager.isSafetyCenterEnabled()) {
+ if (mIsSafetyCenterEnabled) {
return new Intent(Settings.ACTION_PRIVACY_CONTROLS);
} else {
return new Intent(Settings.ACTION_PRIVACY_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 9d10072..905d8ef 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 @@
* dismissing and tile from-view animations.
*/
@SysUISingleton
-class QSTileIntentUserActionHandler
+class QSTileIntentUserInputHandler
@Inject
constructor(private val activityStarter: ActivityStarter) {
@@ -43,4 +44,19 @@
}
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 0000000..4f25d3c
--- /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 7a22e3c..a3e3850 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 @@
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
similarity index 78%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
index 0aa6b0b..102fa36 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 14fc639..09d7a1f 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/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
deleted file mode 100644
index ffe38dd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
+++ /dev/null
@@ -1,27 +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.qs.tiles.base.interactor
-
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
-
-sealed interface StateUpdateTrigger {
- class UserAction<T>(val action: QSTileUserAction, val tileState: QSTileState, val tileData: T) :
- StateUpdateTrigger
- data object ForceUpdate : StateUpdateTrigger
- data object InitialRequest : StateUpdateTrigger
-}
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 70a683b..4dc1c82 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
@@ -19,24 +19,23 @@
import androidx.annotation.GuardedBy
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.QSTilesDefaultLog
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
import javax.inject.Inject
-import javax.inject.Provider
@SysUISingleton
class QSTileLogger
@Inject
constructor(
@QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
- @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>,
+ private val factory: LogBufferFactory,
private val mStatusBarStateController: StatusBarStateController,
) {
@GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
@@ -129,10 +128,21 @@
)
}
+ 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,
) {
@@ -142,11 +152,10 @@
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" }
)
}
@@ -154,14 +163,20 @@
private fun TileSpec.getLogBuffer(): LogBuffer =
synchronized(logBufferCache) {
- logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() }
+ logBufferCache.getOrPut(this) {
+ factory.create(
+ "QSTileLog_${this.getLogTag()}",
+ BUFFER_MAX_SIZE /* maxSize */,
+ false /* systrace */
+ )
+ }
}
- 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 =
@@ -185,5 +200,6 @@
private companion object {
const val TAG_FORMAT_PREFIX = "QSLog"
const val DATA_MAX_LENGTH = 50
+ const val BUFFER_MAX_SIZE = 25
}
}
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 2114751..14de5eb 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.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.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 @@
*
* Inject [BaseQSTileViewModel.Factory] to create a new instance of this class.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
class BaseQSTileViewModel<DATA_TYPE>
@VisibleForTesting
constructor(
@@ -74,9 +80,11 @@
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 @@
@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 @@
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 @@
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 @@
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) }
+ 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)
}
- .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) }
- }
- .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)
-
- 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)
- }
- }
- }
- }
- .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
- }
- .throttle(500)
+ /**
+ * 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
- .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)
- }
+ .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)
+
+ DataUpdateTrigger.UserInput(QSTileInput(userId, action, data))
}
+ .onEach { userActionInteractor.handleInput(it.input) }
+ .flowOn(backgroundDispatcher)
}
- private data class DataWithTrigger<T>(val data: T, val trigger: StateUpdateTrigger)
+ 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)
+ }
+ }
+ }
+ }
+ 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 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 0ccde74..dc5cccc 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 @@
// 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 f6299e3..33f55ab 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 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.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 @@
class QSTileViewModelAdapter
@AssistedInject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val qsHost: QSHost,
@Assisted private val qsTileViewModel: QSTileViewModel,
) : QSTile {
@@ -57,25 +57,28 @@
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 @@
)
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 @@
}
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 f469c6b..ea1205a 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.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 @@
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 @@
}
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 ade93b1..3b3aa53 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 @@
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 @@
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/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index aa6bfc3..10d5f59 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -34,11 +34,11 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.util.TraceUtils.Companion.launch
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@SysUISingleton
@@ -63,7 +63,9 @@
user: UserHandle,
overrideTransition: Boolean,
) {
- applicationScope.launch { launchIntent(intent, options, user, overrideTransition) }
+ applicationScope.launch("$TAG#launchIntentAsync") {
+ launchIntent(intent, options, user, overrideTransition)
+ }
}
suspend fun launchIntent(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index c34fd42..f1c74c1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,10 +20,10 @@
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.TraceUtils.Companion.launch
+import kotlinx.coroutines.CoroutineScope
import java.util.function.Consumer
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/** Processes a screenshot request sent from [ScreenshotHelper]. */
interface ScreenshotRequestProcessor {
@@ -88,7 +88,7 @@
* thread
*/
fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
- mainScope.launch {
+ mainScope.launch({ "$TAG#processAsync" }) {
val result = process(screenshot)
callback.accept(result)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 14e875d2..d2e4794 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.launch
+import com.android.systemui.util.TraceUtils.Companion.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
@@ -47,7 +47,9 @@
}
override fun dismissKeyguard(callback: IOnDoneCallback) {
- lifecycleScope.launch { executeAfterDismissing(callback) }
+ lifecycleScope.launch("IScreenshotProxy#dismissKeyguard") {
+ executeAfterDismissing(callback)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index cd0cab5..1eae191 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -20,7 +20,7 @@
import android.util.Log
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.util.TraceUtils.Companion.tracedAsync
+import com.android.systemui.util.TraceUtils.Companion.async
import com.google.errorprone.annotations.CanIgnoreReturnValue
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@@ -48,7 +48,7 @@
) : ScreenshotSoundController {
val player: Deferred<MediaPlayer?> =
- coroutineScope.tracedAsync("loadCameraSound", bgDispatcher) {
+ coroutineScope.async("loadCameraSound", bgDispatcher) {
try {
soundProvider.getScreenshotSound()
} catch (e: IllegalStateException) {
@@ -58,12 +58,10 @@
}
override fun playCameraSound(): Deferred<Unit> {
- return coroutineScope.tracedAsync("playCameraSound", bgDispatcher) {
- player.await()?.start()
- }
+ return coroutineScope.async("playCameraSound", bgDispatcher) { player.await()?.start() }
}
override fun releaseScreenshotSound(): Deferred<Unit> {
- return coroutineScope.tracedAsync("releaseScreenshotSound", bgDispatcher) {
+ return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
try {
withTimeout(1.seconds) { player.await()?.release() }
} catch (e: TimeoutCancellationException) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 0158284..5684605 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -13,14 +13,11 @@
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.util.TraceUtils.Companion.launch
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.launch
+import kotlinx.coroutines.flow.first
/**
* Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
@@ -40,12 +37,7 @@
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 @@
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 @@
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 @@
screenshotController.onDestroy()
}
screenshotControllers.clear()
- displaysCollectionJob.cancel()
}
private fun getScreenshotController(id: Int): ScreenshotController {
@@ -184,7 +176,7 @@
onSaved: Consumer<Uri>,
requestCallback: RequestCallback
) {
- mainScope.launch {
+ mainScope.launch("TakeScreenshotService#executeScreenshotsAsync") {
executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback)
}
}
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 1ecb127..ead10d6 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 @@
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 @@
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 0951440..a2627ed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,10 +42,13 @@
import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.communal.data.repository.CommunalRepository;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -62,7 +65,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -103,8 +106,10 @@
private final PulsingGestureListener mPulsingGestureListener;
private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
+ private final CommunalViewModel mCommunalViewModel;
+ private final CommunalRepository mCommunalRepository;
private final boolean mIsTrackpadCommonEnabled;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlagsClassic;
private final SysUIKeyEventHandler mSysUIKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -175,8 +180,10 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
KeyguardTransitionInteractor keyguardTransitionInteractor,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
- NotificationExpansionRepository notificationExpansionRepository,
- FeatureFlags featureFlags,
+ CommunalViewModel communalViewModel,
+ CommunalRepository communalRepository,
+ NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
+ FeatureFlagsClassic featureFlagsClassic,
SystemClock clock,
BouncerMessageInteractor bouncerMessageInteractor,
BouncerLogger bouncerLogger,
@@ -206,8 +213,10 @@
mPulsingGestureListener = pulsingGestureListener;
mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
- mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
- mFeatureFlags = featureFlags;
+ mCommunalViewModel = communalViewModel;
+ mCommunalRepository = communalRepository;
+ mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
+ mFeatureFlagsClassic = featureFlagsClassic;
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -223,18 +232,18 @@
messageAreaControllerFactory,
bouncerMessageInteractor,
bouncerLogger,
- featureFlags,
+ featureFlagsClassic,
selectedUserInteractor);
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition);
collectFlow(
mView,
- notificationExpansionRepository.isExpandAnimationRunning(),
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning(),
this::setExpandAnimationRunning);
mClock = clock;
- if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
+ if (featureFlagsClassic.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
unfoldTransitionProgressProvider.ifPresent(
progressProvider -> progressProvider.addCallback(
mDisableSubpixelTextTransitionListener));
@@ -263,7 +272,7 @@
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
- if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ if (mFeatureFlagsClassic.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mLockscreenHostedDreamGestureListener);
}
@@ -435,7 +444,7 @@
}
boolean bouncerShowing;
- if (mFeatureFlags.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing()
|| mAlternateBouncerInteractor.isVisibleState();
} else {
@@ -447,7 +456,7 @@
if (mDragDownHelper.isDragDownEnabled()) {
// This handles drag down over lockscreen
boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
if (result) {
mLastInterceptWasDragDownHelper = true;
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -479,7 +488,7 @@
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
}
cancellation.recycle();
@@ -494,7 +503,7 @@
if (mStatusBarKeyguardViewManager.onTouch(ev)) {
return true;
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
// we still want to finish our drag down gesture when locking the screen
handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -559,8 +568,28 @@
mDepthController.onPanelExpansionChanged(currentState);
}
+ /**
+ * Sets up the communal hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB} flag
+ * is enabled.
+ *
+ * The layout lives in {@link R.id.communal_ui_container}.
+ */
+ public void setupCommunalHubLayout() {
+ if (!mCommunalRepository.isCommunalEnabled()
+ || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+ return;
+ }
+
+ // Replace the placeholder view with the communal UI.
+ View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+ int index = mView.indexOfChild(communalPlaceholder);
+ mView.removeView(communalPlaceholder);
+ mView.addView(ComposeFacade.INSTANCE.createCommunalContainer(mView.getContext(),
+ mCommunalViewModel), index);
+ }
+
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
// Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
// to also ask NotificationPanelViewController directly, in order to process swipe up
// events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index a8a20cc..2f68476 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -35,7 +35,6 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
-import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -140,13 +139,6 @@
/** Whether either the shade or QS is fully expanded. */
val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
- /** Whether either the shade or QS is expanding from a fully collapsed state. */
- val isAnyExpanding: Flow<Boolean> =
- anyExpansion
- .pairwise(1f)
- .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f }
- .distinctUntilChanged()
-
/**
* Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At
* this time, this is not simply a matter of checking if either value in shadeExpansion and
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java b/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java
deleted file mode 100644
index f1eb9fe..0000000
--- 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/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 77b0958..7d81e55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar;
+import android.animation.Animator;
import android.view.View;
+import android.view.ViewPropertyAnimator;
import androidx.annotation.Nullable;
@@ -31,30 +33,35 @@
public static final long ANIMATION_DURATION_LENGTH = 210;
public static void fadeOut(final View view) {
- fadeOut(view, null);
+ fadeOut(view, (Runnable) null);
}
public static void fadeOut(final View view, final Runnable endRunnable) {
fadeOut(view, ANIMATION_DURATION_LENGTH, 0, endRunnable);
}
+ public static void fadeOut(final View view, final Animator.AnimatorListener listener) {
+ fadeOut(view, ANIMATION_DURATION_LENGTH, 0, listener);
+ }
+
+ public static void fadeOut(final View view, long duration, int delay) {
+ fadeOut(view, duration, delay, (Runnable) null);
+ }
+
public static void fadeOut(final View view, long duration, int delay,
- final Runnable endRunnable) {
+ @Nullable final Runnable endRunnable) {
view.animate().cancel();
view.animate()
.alpha(0f)
.setDuration(duration)
.setInterpolator(Interpolators.ALPHA_OUT)
.setStartDelay(delay)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- if (view.getVisibility() != View.GONE) {
- view.setVisibility(View.INVISIBLE);
- }
+ .withEndAction(() -> {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ if (view.getVisibility() != View.GONE) {
+ view.setVisibility(View.INVISIBLE);
}
});
if (view.hasOverlappingRendering()) {
@@ -62,6 +69,27 @@
}
}
+ public static void fadeOut(final View view, long duration, int delay,
+ @Nullable final Animator.AnimatorListener listener) {
+ view.animate().cancel();
+ ViewPropertyAnimator animator = view.animate()
+ .alpha(0f)
+ .setDuration(duration)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .setStartDelay(delay)
+ .withEndAction(() -> {
+ if (view.getVisibility() != View.GONE) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ if (listener != null) {
+ animator.setListener(listener);
+ }
+ if (view.hasOverlappingRendering()) {
+ view.animate().withLayer();
+ }
+ }
+
public static void fadeOut(View view, float fadeOutAmount) {
fadeOut(view, fadeOutAmount, true /* remap */);
}
@@ -119,8 +147,12 @@
fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, endRunnable);
}
+ public static void fadeIn(final View view, Animator.AnimatorListener listener) {
+ fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, listener);
+ }
+
public static void fadeIn(final View view, long duration, int delay) {
- fadeIn(view, duration, delay, /* endRunnable= */ null);
+ fadeIn(view, duration, delay, /* endRunnable= */ (Runnable) null);
}
public static void fadeIn(final View view, long duration, int delay,
@@ -141,6 +173,26 @@
}
}
+ public static void fadeIn(final View view, long duration, int delay,
+ @Nullable Animator.AnimatorListener listener) {
+ view.animate().cancel();
+ if (view.getVisibility() == View.INVISIBLE) {
+ view.setAlpha(0.0f);
+ view.setVisibility(View.VISIBLE);
+ }
+ ViewPropertyAnimator animator = view.animate()
+ .alpha(1f)
+ .setDuration(duration)
+ .setStartDelay(delay)
+ .setInterpolator(Interpolators.ALPHA_IN);
+ if (listener != null) {
+ animator.setListener(listener);
+ }
+ if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
+ view.animate().withLayer();
+ }
+ }
+
public static void fadeIn(View view, float fadeInAmount) {
fadeIn(view, fadeInAmount, false /* remap */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index ff4570e..2e1e395 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -29,8 +29,11 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
+import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
@@ -59,7 +62,9 @@
private static final long MAX_RANKING_DELAY_MILLIS = 500L;
private final Context mContext;
+ private final FeatureFlagsClassic mFeatureFlags;
private final NotificationManager mNotificationManager;
+ private final SilentNotificationStatusIconsVisibilityInteractor mStatusIconInteractor;
private final SystemClock mSystemClock;
private final Executor mMainExecutor;
private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
@@ -75,13 +80,17 @@
@Inject
public NotificationListener(
Context context,
+ FeatureFlagsClassic featureFlags,
NotificationManager notificationManager,
+ SilentNotificationStatusIconsVisibilityInteractor statusIconInteractor,
SystemClock systemClock,
@Main Executor mainExecutor,
PluginManager pluginManager) {
super(pluginManager);
mContext = context;
+ mFeatureFlags = featureFlags;
mNotificationManager = notificationManager;
+ mStatusIconInteractor = statusIconInteractor;
mSystemClock = systemClock;
mMainExecutor = mainExecutor;
}
@@ -95,6 +104,7 @@
}
/** Registers a listener that's notified when any notification-related settings change. */
+ @Deprecated
public void addNotificationSettingsListener(NotificationSettingsListener listener) {
mSettingsListeners.add(listener);
}
@@ -230,8 +240,12 @@
@Override
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
- for (NotificationSettingsListener listener : mSettingsListeners) {
- listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
+ } else {
+ for (NotificationSettingsListener listener : mSettingsListeners) {
+ listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+ }
}
}
@@ -294,6 +308,7 @@
return ranking;
}
+ @Deprecated
public interface NotificationSettingsListener {
default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f32f1c2..710e59d 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.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.Collection;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.Objects;
import javax.inject.Inject;
@@ -177,57 +179,50 @@
@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);
- }
+ 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);
}
- 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);
- });
+ }
+ 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;
- 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 (notificationKey != null) {
- final NotificationVisibility nv = mVisibilityProviderLazy.get()
- .obtain(notificationKey, true);
- mClickNotifier.onNotificationClick(notificationKey, nv);
- }
- break;
+ }
+ if (notificationKey != null) {
+ final NotificationVisibility nv = mVisibilityProviderLazy.get()
+ .obtain(notificationKey, true);
+ mClickNotifier.onNotificationClick(notificationKey, nv);
+ }
}
}
};
@@ -403,6 +398,10 @@
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 @@
}
}
+ 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 389486f..9c4625e 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 @@
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.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 @@
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 @@
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 @@
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 @@
Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
}
mMediaMetadata = metadata;
- dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
+ dispatchUpdateMediaMetaData();
}
};
@@ -155,23 +115,13 @@
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 void onNotificationRemoved(String key) {
if (key.equals(mMediaNotificationKey)) {
clearCurrentMediaNotification();
- dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */);
+ dispatchUpdateMediaMetaData();
}
}
@@ -309,21 +259,18 @@
}
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 @@
Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
+ mMediaController + ", receive metadata: " + mMediaMetadata);
}
-
- metaDataChanged = true;
}
if (mediaNotification != null
@@ -371,8 +316,6 @@
+ mMediaNotificationKey);
}
}
-
- return metaDataChanged;
}
public void clearCurrentMediaNotification() {
@@ -380,10 +323,7 @@
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 @@
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 5dcf6d1..f3b5ab6 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 @@
boolean isPresenterFullyCollapsed();
/**
- * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
- */
- void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
-
- /**
* Called when the current user changes.
* @param newUserId new user id
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 17da015..ffde8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -118,16 +118,27 @@
// If the view is being removed, this may be called even though we're not active
boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
+ boolean remoteInputActive = isRemoteInputActive();
mLogger.logRemoveRemoteInput(
entry.getKey() /* entryKey */,
entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
- isRemoteInputActive()/* isRemoteInputActive */,
+ remoteInputActive/* isRemoteInputActive */,
reason/* reason */,
entry.getNotificationStyle()/* notificationStyle */);
- if (!remoteInputActiveForEntry) return;
+ if (!remoteInputActiveForEntry) {
+ if (mLastAppliedRemoteInputActive != null
+ && mLastAppliedRemoteInputActive
+ && !remoteInputActive) {
+ mLogger.logRemoteInputApplySkipped(
+ entry.getKey() /* entryKey */,
+ reason/* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
+ }
+ return;
+ }
pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 125c8efe..1fe6b83 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.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 @@
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 e2de37f..22912df 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 @@
* ([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 @@
@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 0000000..29d53fc
--- /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 8136de9..d1594ef 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.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 @@
isEnabled && isKeyguardEnabled
}
}
+
+@Module
+interface KeyguardStatusBarRepositoryModule {
+ @Binds fun bindImpl(impl: KeyguardStatusBarRepositoryImpl): KeyguardStatusBarRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt
new file mode 100644
index 0000000..2c706a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.NotificationListener
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Exposes state pertaining to settings tracked over the [NotificationListener] boundary. */
+@SysUISingleton
+class NotificationListenerSettingsRepository @Inject constructor() {
+ /** Should icons for silent notifications be shown in the status bar? */
+ val showSilentStatusIcons = MutableStateFlow(true)
+}
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 2b05994..47994d9 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.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.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 @@
* 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 @@
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 @@
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/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt
new file mode 100644
index 0000000..1248b1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import javax.inject.Inject
+
+class SilentNotificationStatusIconsVisibilityInteractor
+@Inject
+constructor(private val repository: NotificationListenerSettingsRepository) {
+ /** Set whether icons for silent notifications be hidden in the status bar. */
+ fun setHideSilentStatusIcons(hideIcons: Boolean) {
+ repository.showSilentStatusIcons.value = !hideIcons
+ }
+}
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 bde298d..ea1d782 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 @@
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 @@
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 @@
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 @@
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 dccc23f..73c0bfe 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 @@
})
}
+ /** 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 7d866df..8ee1ade 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 @@
}
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 a3bc002..f0e60dd 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 @@
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/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 756151b..96279e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -21,7 +21,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.LaunchAnimator
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -33,7 +33,7 @@
/** A provider of [NotificationLaunchAnimatorController]. */
class NotificationLaunchAnimatorControllerProvider(
- private val notificationExpansionRepository: NotificationExpansionRepository,
+ private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
private val notificationListContainer: NotificationListContainer,
private val headsUpManager: HeadsUpManager,
private val jankMonitor: InteractionJankMonitor
@@ -44,7 +44,7 @@
onFinishAnimationCallback: Runnable? = null
): NotificationLaunchAnimatorController {
return NotificationLaunchAnimatorController(
- notificationExpansionRepository,
+ notificationLaunchAnimationInteractor,
notificationListContainer,
headsUpManager,
notification,
@@ -60,7 +60,7 @@
* notification expanding into an opening window.
*/
class NotificationLaunchAnimatorController(
- private val notificationExpansionRepository: NotificationExpansionRepository,
+ private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
private val notificationListContainer: NotificationListContainer,
private val headsUpManager: HeadsUpManager,
private val notification: ExpandableNotificationRow,
@@ -143,7 +143,7 @@
if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)")
}
- notificationExpansionRepository.setIsExpandAnimationRunning(willAnimate)
+ notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate)
notificationEntry.isExpandAnimationRunning = willAnimate
if (!willAnimate) {
@@ -180,7 +180,7 @@
// TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
// here?
- notificationExpansionRepository.setIsExpandAnimationRunning(false)
+ notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
notificationEntry.isExpandAnimationRunning = false
removeHun(animate = true)
onFinishAnimationCallback?.run()
@@ -200,7 +200,7 @@
jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
notification.isExpandAnimationRunning = false
- notificationExpansionRepository.setIsExpandAnimationRunning(false)
+ notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
notificationEntry.isExpandAnimationRunning = false
notificationListContainer.setExpandingNotification(null)
applyParams(null)
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 57d20246..f98f39e 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.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 @@
* An animator to animate properties
*/
public class PropertyAnimator {
+ private static final String TAG = "PropertyAnimator";
/**
* Set a property on a view, updating its value, even if it's already animating.
@@ -114,18 +116,23 @@
|| previousAnimator.getAnimatedFraction() == 0)) {
animator.setStartDelay(properties.delay);
}
- if (listener != null) {
- animator.addListener(listener);
- }
// remove the tag when the animation is finished
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- view.setTag(animatorTag, null);
- view.setTag(animationStartTag, null);
- view.setTag(animationEndTag, null);
+ Animator existing = (Animator) view.getTag(animatorTag);
+ if (existing == animation) {
+ view.setTag(animatorTag, null);
+ view.setTag(animationStartTag, null);
+ view.setTag(animationEndTag, null);
+ } else {
+ Log.e(TAG, "Unexpected Animator set during onAnimationEnd. Not cleaning up.");
+ }
}
});
+ if (listener != null) {
+ animator.addListener(listener);
+ }
ViewState.startAnimator(animator, listener);
view.setTag(animatorTag, animator);
view.setTag(animationStartTag, currentValue);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index ff89c62..23f87ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -83,6 +83,21 @@
}
)
+ fun logRemoteInputApplySkipped(entryKey: String, reason: String, notificationStyle: String) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
+ },
+ {
+ "removeRemoteInput[apply is skipped] reason: $str2" +
+ "for entry: $str1, style: $str3 "
+ }
+ )
+
private companion object {
private const val TAG = "RemoteInputControllerLog"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index e763797..7b3a93a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -225,7 +225,7 @@
/** @see NotifPipeline#getEntry(String) () */
@Nullable
- NotificationEntry getEntry(@NonNull String key) {
+ public NotificationEntry getEntry(@NonNull String key) {
return mNotificationSet.get(key);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4573d59..cfe9fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -748,7 +748,11 @@
return row != null && row.getGuts() != null && row.getGuts().isExposed();
}
- public boolean isChildInGroup() {
+ /**
+ * @return Whether the notification row is a child of a group notification view; false if the
+ * row is null
+ */
+ public boolean rowIsChildInGroup() {
return row != null && row.isChildInGroup();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2d83970..0c69a65 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.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 @@
@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 @@
override fun onCleanup() {
logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
- notificationListInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
+ seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
hasFilteredAnyNotifs = false
}
}
@@ -389,7 +389,7 @@
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 62a0d13..07e84bb 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) {
@@ -45,7 +52,11 @@
fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
traceSection("StackCoordinator.onAfterRenderList") {
controller.setNotifStats(calculateNotifStats(entries))
- notificationIconAreaController.updateNotificationIcons(entries)
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ renderListInteractor.setRenderedList(entries)
+ } else {
+ notificationIconAreaController.updateNotificationIcons(entries)
+ }
}
private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
@@ -75,4 +86,4 @@
hasClearableSilentNotifs = hasClearableSilentNotifs
)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 8561869..fa366c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -54,7 +54,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -204,12 +204,12 @@
@Provides
@SysUISingleton
static NotificationLaunchAnimatorControllerProvider provideNotifLaunchAnimControllerProvider(
- NotificationExpansionRepository notificationExpansionRepository,
+ NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
NotificationListContainer notificationListContainer,
HeadsUpManager headsUpManager,
InteractionJankMonitor jankMonitor) {
return new NotificationLaunchAnimatorControllerProvider(
- notificationExpansionRepository,
+ notificationLaunchAnimationInteractor,
notificationListContainer,
headsUpManager,
jankMonitor);
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 0000000..8064f04
--- /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/data/repository/HeadsUpNotificationIconViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt
new file mode 100644
index 0000000..afed6be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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 android.graphics.Rect
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** View-states pertaining to heads-up notification icons. */
+@SysUISingleton
+class HeadsUpNotificationIconViewStateRepository @Inject constructor() {
+ /** Notification key for a notification icon to show isolated, or `null` if none. */
+ val isolatedNotification = MutableStateFlow<String?>(null)
+ /** Area to display the isolated notification, or `null` if none. */
+ val isolatedIconLocation = MutableStateFlow<Rect?>(null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
deleted file mode 100644
index 6f0a97a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.data.repository
-
-import android.util.Log
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-private const val TAG = "NotificationExpansionRepository"
-
-/** A repository tracking the status of notification expansion animations. */
-@SysUISingleton
-class NotificationExpansionRepository @Inject constructor() {
- private val _isExpandAnimationRunning = MutableStateFlow(false)
-
- /**
- * Emits true if an animation that expands a notification object into an opening window is
- * running and false otherwise.
- *
- * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
- */
- val isExpandAnimationRunning: Flow<Boolean> = _isExpandAnimationRunning.asStateFlow()
-
- /** Sets whether the notification expansion animation is currently running. */
- fun setIsExpandAnimationRunning(running: Boolean) {
- if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
- Log.d(TAG, "setIsExpandAnimationRunning(running=$running)")
- }
- _isExpandAnimationRunning.value = running
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt
new file mode 100644
index 0000000..9b56299
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt
@@ -0,0 +1,27 @@
+/*
+ * 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 javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** A repository tracking the status of notification launch animations. */
+@SysUISingleton
+class NotificationLaunchAnimationRepository @Inject constructor() {
+ val isLaunchAnimationRunning = 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 0000000..bfec60bc
--- /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/HeadsUpNotificationIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
new file mode 100644
index 0000000..17b6e9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.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.statusbar.notification.domain.interactor
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Domain logic pertaining to heads up notification icons. */
+class HeadsUpNotificationIconInteractor
+@Inject
+constructor(
+ private val repository: HeadsUpNotificationIconViewStateRepository,
+) {
+ /** Notification key for a notification icon to show isolated, or `null` if none. */
+ val isolatedIconLocation: Flow<Rect?> = repository.isolatedIconLocation
+
+ /** Area to display the isolated notification, or `null` if none. */
+ val isolatedNotification: Flow<String?> = repository.isolatedNotification
+
+ /** Updates the location where isolated notification icons are shown. */
+ fun setIsolatedIconLocation(rect: Rect?) {
+ repository.isolatedIconLocation.value = rect
+ }
+
+ /** Updates which notification will have its icon displayed isolated. */
+ fun setIsolatedIconNotificationKey(key: String?) {
+ repository.isolatedNotification.value = key
+ }
+}
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
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
index 8f7e269..8079ce5 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.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/NotificationLaunchAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
new file mode 100644
index 0000000..22ce4f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.util.Log
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** A repository tracking the status of notification expansion animations. */
+@SysUISingleton
+class NotificationLaunchAnimationInteractor
+@Inject
+constructor(private val repository: NotificationLaunchAnimationRepository) {
+
+ /**
+ * Emits true if an animation that expands a notification object into an opening window is
+ * running and false otherwise.
+ *
+ * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
+ */
+ val isLaunchAnimationRunning: StateFlow<Boolean>
+ get() = repository.isLaunchAnimationRunning
+
+ /** Sets whether the notification expansion launch animation is currently running. */
+ fun setIsLaunchAnimationRunning(running: Boolean) {
+ if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+ Log.d(TAG, "setIsLaunchAnimationRunning(running=$running)")
+ }
+ repository.isLaunchAnimationRunning.value = running
+ }
+
+ companion object {
+ private const val TAG = "NotificationLaunchAnimationInteractor"
+ }
+}
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 0000000..604ecbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+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
+
+private typealias ModelStore = Map<String, ActiveNotificationModel>
+
+/**
+ * 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,
+ private val sectionStyleProvider: SectionStyleProvider,
+) {
+ /**
+ * 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 { existingModels ->
+ entries.associateBy(
+ keySelector = { it.key },
+ valueTransform = { it.toModel(existingModels) },
+ )
+ }
+ }
+
+ private fun ListEntry.toModel(
+ existingModels: ModelStore,
+ ): ActiveNotificationModel =
+ existingModels.createOrReuse(
+ key = key,
+ groupKey = representativeEntry?.sbn?.groupKey,
+ isAmbient = sectionStyleProvider.isMinimized(this),
+ isRowDismissed = representativeEntry?.isRowDismissed == true,
+ isSilent = sectionStyleProvider.isSilent(this),
+ isLastMessageFromReply = representativeEntry?.isLastMessageFromReply == true,
+ isSuppressedFromStatusBar = representativeEntry?.shouldSuppressStatusBar() == true,
+ isPulsing = representativeEntry?.showingPulsing() == true,
+ aodIcon = representativeEntry?.icons?.aodIcon?.sourceIcon,
+ shelfIcon = representativeEntry?.icons?.shelfIcon?.sourceIcon,
+ statusBarIcon = representativeEntry?.icons?.statusBarIcon?.sourceIcon,
+ )
+
+ private fun ModelStore.createOrReuse(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+ ): ActiveNotificationModel {
+ return this[key]?.takeIf {
+ it.isCurrent(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon
+ )
+ }
+ ?: ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
+ }
+
+ private fun ActiveNotificationModel.isCurrent(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+ ): Boolean {
+ return when {
+ key != this.key -> false
+ groupKey != this.groupKey -> false
+ isAmbient != this.isAmbient -> false
+ isRowDismissed != this.isRowDismissed -> false
+ isSilent != this.isSilent -> false
+ isLastMessageFromReply != this.isLastMessageFromReply -> false
+ isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
+ isPulsing != this.isPulsing -> false
+ aodIcon != this.aodIcon -> false
+ shelfIcon != this.shelfIcon -> false
+ statusBarIcon != this.statusBarIcon -> false
+ else -> true
+ }
+ }
+}
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
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 3fd68a8..1f7ab96 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,26 @@
* 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
+ /** Set whether already-seen notifications are currently filtered out of the shade. */
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 0000000..94e70e5
--- /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/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index e74b3fc..ba91654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -19,6 +19,9 @@
import static android.graphics.PorterDuff.Mode.SRC_ATOP;
import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -35,6 +38,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.FooterViewButton;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -44,6 +48,8 @@
import java.io.PrintWriter;
public class FooterView extends StackScrollerDecorView {
+ private static final String TAG = "FooterView";
+
private FooterViewButton mClearAllButton;
private FooterViewButton mManageButton;
private boolean mShowHistory;
@@ -57,6 +63,9 @@
private String mSeenNotifsFilteredText;
private Drawable mSeenNotifsFilteredIcon;
+ private @StringRes int mMessageStringId;
+ private @DrawableRes int mMessageIconId;
+
public FooterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -84,6 +93,50 @@
});
}
+ /** Set the string for a message to be shown instead of the buttons. */
+ public void setMessageString(@StringRes int messageId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mMessageStringId == messageId) {
+ return; // nothing changed
+ }
+ mMessageStringId = messageId;
+ updateMessageString();
+ }
+
+ private void updateMessageString() {
+ if (mMessageStringId == 0) {
+ return; // not initialized yet
+ }
+ String messageString = getContext().getString(mMessageStringId);
+ mSeenNotifsFooterTextView.setText(messageString);
+ }
+
+
+ /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
+ public void setMessageIcon(@DrawableRes int iconId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mMessageIconId == iconId) {
+ return; // nothing changed
+ }
+ mMessageIconId = iconId;
+ updateMessageIcon();
+ }
+
+ private void updateMessageIcon() {
+ if (mMessageIconId == 0) {
+ return; // not initialized yet
+ }
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ @SuppressLint("UseCompatLoadingForDrawables")
+ Drawable messageIcon = getContext().getDrawable(mMessageIconId);
+ if (messageIcon != null) {
+ messageIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ mSeenNotifsFooterTextView
+ .setCompoundDrawablesRelative(messageIcon, null, null, null);
+ }
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -148,9 +201,11 @@
mManageButton.setText(mManageNotificationText);
mManageButton.setContentDescription(mManageNotificationText);
}
- mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
- mSeenNotifsFooterTextView
- .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+ if (!FooterViewRefactor.isEnabled()) {
+ mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+ mSeenNotifsFooterTextView
+ .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+ }
}
/** Whether the start button shows "History" (true) or "Manage" (false). */
@@ -167,6 +222,11 @@
mContext.getString(R.string.accessibility_clear_all));
updateResources();
updateContent();
+
+ if (FooterViewRefactor.isEnabled()) {
+ updateMessageString();
+ updateMessageIcon();
+ }
}
/**
@@ -200,11 +260,13 @@
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
- int unlockIconSize = getResources()
- .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
- mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
- mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
- mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ if (!FooterViewRefactor.isEnabled()) {
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+ mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+ mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
new file mode 100644
index 0000000..6d823437
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [FooterView] to its [view model][FooterViewModel]. */
+object FooterViewBinder {
+ fun bind(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ ): DisposableHandle {
+ return footer.repeatWhenAttached {
+ // Listen for changes when the view is attached.
+ lifecycleScope.launch {
+ viewModel.message.collect { message ->
+ footer.setFooterLabelVisible(message.visible)
+ footer.setMessageString(message.messageId)
+ footer.setMessageIcon(message.iconId)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
index 0803a01..bc912fb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
@@ -12,15 +12,16 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
-package com.android.systemui.communal.shared
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
-import android.appwidget.AppWidgetProviderInfo
+import android.annotation.DrawableRes
+import android.annotation.StringRes
-/** A data class that stores info about an app widget that displays in communal mode. */
-data class CommunalAppWidgetInfo(
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetId: Int,
+/** A ViewModel for the string message that can be shown in the footer. */
+data class FooterMessageViewModel(
+ @StringRes val messageId: Int,
+ @DrawableRes val iconId: Int,
+ val visible: Boolean,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
new file mode 100644
index 0000000..ffa5ff0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [FooterView]. */
+@SysUISingleton
+class FooterViewModel
+@Inject
+constructor(seenNotificationsInteractor: SeenNotificationsInteractor) {
+ init {
+ /* Check if */ FooterViewRefactor.isUnexpectedlyInLegacyMode()
+ }
+
+ val message: Flow<FooterMessageViewModel> =
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs ->
+ FooterMessageViewModel(
+ messageId = R.string.unlock_to_see_notif_text,
+ iconId = R.drawable.ic_friction_lock_closed,
+ visible = hasFilteredOutNotifs,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
new file mode 100644
index 0000000..00d873e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.icon.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.jvm.optionals.getOrNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+
+/** Domain logic related to notification icons. */
+class NotificationIconsInteractor
+@Inject
+constructor(
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+ private val bubbles: Optional<Bubbles>,
+ private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository,
+) {
+ /** Returns a subset of all active notifications based on the supplied filtration parameters. */
+ fun filteredNotifSet(
+ showAmbient: Boolean = true,
+ showLowPriority: Boolean = true,
+ showDismissed: Boolean = true,
+ showRepliedMessages: Boolean = true,
+ showPulsing: Boolean = true,
+ ): Flow<Set<ActiveNotificationModel>> {
+ return combine(
+ activeNotificationsInteractor.notifications,
+ keyguardViewStateRepository.areNotificationsFullyHidden,
+ ) { notifications, notifsFullyHidden ->
+ notifications
+ .asSequence()
+ .filter { model: ActiveNotificationModel ->
+ shouldShowNotificationIcon(
+ model = model,
+ showAmbient = showAmbient,
+ showLowPriority = showLowPriority,
+ showDismissed = showDismissed,
+ showRepliedMessages = showRepliedMessages,
+ showPulsing = showPulsing,
+ notifsFullyHidden = notifsFullyHidden,
+ )
+ }
+ .toSet()
+ }
+ }
+
+ private fun shouldShowNotificationIcon(
+ model: ActiveNotificationModel,
+ showAmbient: Boolean,
+ showLowPriority: Boolean,
+ showDismissed: Boolean,
+ showRepliedMessages: Boolean,
+ showPulsing: Boolean,
+ notifsFullyHidden: Boolean,
+ ): Boolean {
+ return when {
+ !showAmbient && model.isAmbient -> false
+ !showLowPriority && model.isSilent -> false
+ !showDismissed && model.isRowDismissed -> false
+ !showRepliedMessages && model.isLastMessageFromReply -> false
+ !showAmbient && model.isSuppressedFromStatusBar -> false
+ !showPulsing && model.isPulsing && !notifsFullyHidden -> false
+ bubbles.getOrNull()?.isBubbleExpanded(model.key) == true -> false
+ else -> true
+ }
+ }
+}
+
+/** Domain logic related to notification icons shown on the always-on display. */
+class AlwaysOnDisplayNotificationIconsInteractor
+@Inject
+constructor(
+ deviceEntryInteractor: DeviceEntryInteractor,
+ iconsInteractor: NotificationIconsInteractor,
+) {
+ val aodNotifs: Flow<Set<ActiveNotificationModel>> =
+ deviceEntryInteractor.isBypassEnabled.flatMapLatest { isBypassEnabled ->
+ iconsInteractor.filteredNotifSet(
+ showAmbient = false,
+ showDismissed = false,
+ showRepliedMessages = false,
+ showPulsing = !isBypassEnabled,
+ )
+ }
+}
+
+/** Domain logic related to notification icons shown in the status bar. */
+class StatusBarNotificationIconsInteractor
+@Inject
+constructor(
+ iconsInteractor: NotificationIconsInteractor,
+ settingsRepository: NotificationListenerSettingsRepository,
+) {
+ val statusBarNotifs: Flow<Set<ActiveNotificationModel>> =
+ settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons ->
+ iconsInteractor.filteredNotifSet(
+ showAmbient = false,
+ showLowPriority = showSilentIcons,
+ showDismissed = false,
+ showRepliedMessages = false,
+ )
+ }
+}
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 eb5c1fa..246933a 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,50 +16,24 @@
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
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
import com.android.systemui.statusbar.phone.DozeParameters
-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
-import java.util.function.Function
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
@@ -74,69 +48,22 @@
class NotificationIconAreaControllerViewBinderWrapperImpl
@Inject
constructor(
- private val context: Context,
- private val wakeUpCoordinator: NotificationWakeUpCoordinator,
- private val bypassController: KeyguardBypassController,
+ private val configuration: ConfigurationState,
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 shelfIconViewStore: ShelfNotificationIconViewStore,
private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
- private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel,
+ private val aodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
-) :
- NotificationIconAreaController,
- DarkIconDispatcher.DarkReceiver,
- NotificationWakeUpCoordinator.WakeUpListener,
- DemoMode {
+) : NotificationIconAreaController {
- 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
- val settingsListener: NotificationListener.NotificationSettingsListener =
- object : NotificationListener.NotificationSettingsListener {
- override fun onStatusBarIconsBehaviorChanged(hideSilentStatusIcons: Boolean) {
- showLowPriority = !hideSilentStatusIcons
- updateStatusBarIcons()
- }
- }
-
- init {
- wakeUpCoordinator.addListener(this)
- demoModeController.addCallback(this)
- notificationListener.addNotificationSettingsListener(settingsListener)
- initializeNotificationAreaViews(context)
- reloadAodColor()
- darkIconDispatcher.addDarkReceiver(this)
- }
-
- @VisibleForTesting
- fun shouldShowLowPriorityIcons(): Boolean {
- return showLowPriority
- }
/** Called by the Keyguard*ViewController whose view contains the aod icons. */
override fun setupAodIcons(aodIcons: NotificationIconContainer) {
@@ -152,429 +79,58 @@
NotificationIconContainerViewBinder.bind(
aodIcons,
aodIconsViewModel,
+ configuration,
configurationController,
dozeParameters,
featureFlags,
screenOffAnimationController,
+ aodIconViewStore,
)
- if (changed) {
- updateAodNotificationIcons()
- }
- updateIconLayoutParams(context)
}
override fun setupShelf(notificationShelfController: NotificationShelfController) =
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,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
+ shelfIconViewStore,
+ )
+ shelfIcons = icons
+ }
}
- override fun onDensityOrFontScaleChanged(context: Context) {
- updateIconLayoutParams(context)
- }
+ override fun onDensityOrFontScaleChanged(context: Context) = unsupported
/** Returns the view that represents the notification area. */
- override fun getNotificationInnerAreaView(): View? {
- 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()
- }
+ override fun getNotificationInnerAreaView(): View? = unsupported
/** Updates the notifications with the given list of notifications to display. */
- override fun updateNotificationIcons(entries: List<ListEntry>) {
- notificationEntries = entries
- updateNotificationIcons()
- }
+ override fun updateNotificationIcons(entries: List<ListEntry>) = unsupported
- private fun updateStatusBarIcons() {
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.statusBarIcon },
- notificationIcons,
- showAmbient = false /* showAmbient */,
- showLowPriority = showLowPriority,
- hideDismissed = true /* hideDismissed */,
- hideRepliedMessages = true /* hideRepliedMessages */,
- hideCurrentMedia = false /* hideCurrentMedia */,
- hidePulsing = false /* hidePulsing */
- )
- }
+ override fun updateAodNotificationIcons() = unsupported
- override fun updateAodNotificationIcons() {
- if (aodIcons == null) {
- return
- }
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.aodIcon },
- aodIcons,
- showAmbient = false /* showAmbient */,
- showLowPriority = true /* showLowPriority */,
- hideDismissed = true /* hideDismissed */,
- hideRepliedMessages = true /* hideRepliedMessages */,
- hideCurrentMedia = true /* hideCurrentMedia */,
- hidePulsing = bypassController.bypassEnabled /* hidePulsing */
- )
- }
+ override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) = unsupported
- override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) {
- notificationIcons!!.showIconIsolated(icon, animated)
- }
-
- override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) {
- notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
- }
+ override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) =
+ unsupported
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
}
- override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
- updateAodNotificationIcons()
- updateAodIconColors()
- }
-
- override fun demoCommands(): List<String> {
- val commands = ArrayList<String>()
- commands.add(DemoMode.COMMAND_NOTIFICATIONS)
- return commands
- }
-
- override fun dispatchDemoCommand(command: String, args: Bundle) {
- if (notificationIconArea != null) {
- val visible = args.getString("visible")
- val vis = if ("false" == visible) View.INVISIBLE else View.VISIBLE
- notificationIconArea?.visibility = vis
- }
- }
-
- override fun onDemoModeFinished() {
- if (notificationIconArea != null) {
- notificationIconArea?.visibility = View.VISIBLE
- }
- }
-
- private fun inflateIconArea(inflater: LayoutInflater): View {
- return inflater.inflate(R.layout.notification_icon_area, null)
- }
-
- /** Initializes the views that will represent the notification area. */
- private fun initializeNotificationAreaViews(context: Context) {
- reloadDimens(context)
- val layoutInflater = LayoutInflater.from(context)
- notificationIconArea = inflateIconArea(layoutInflater)
- notificationIcons = notificationIconArea?.findViewById(R.id.notificationIcons)
- NotificationIconContainerViewBinder.bind(
- notificationIcons!!,
- statusBarIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- }
-
- private fun updateIconLayoutParams(context: Context) {
- reloadDimens(context)
- val params = generateIconLayoutParams()
- for (i in 0 until notificationIcons!!.childCount) {
- val child = notificationIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- if (shelfIcons != null) {
- for (i in 0 until shelfIcons!!.childCount) {
- val child = shelfIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- }
- if (aodIcons != null) {
- for (i in 0 until aodIcons!!.childCount) {
- val child = aodIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- }
- }
-
- private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
- return FrameLayout.LayoutParams(
- iconSize + 2 * iconHPadding,
- statusBarWindowController.statusBarHeight
- )
- }
-
- private fun reloadDimens(context: Context) {
- 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(
- entry: NotificationEntry,
- showAmbient: Boolean,
- showLowPriority: Boolean,
- hideDismissed: Boolean,
- hideRepliedMessages: Boolean,
- hideCurrentMedia: Boolean,
- hidePulsing: Boolean
- ): Boolean {
- if (!showAmbient && sectionStyleProvider.isMinimized(entry)) {
- return false
- }
- if (hideCurrentMedia && entry.key == mediaManager.mediaNotificationKey) {
- return false
- }
- if (!showLowPriority && sectionStyleProvider.isSilent(entry)) {
- return false
- }
- if (entry.isRowDismissed && hideDismissed) {
- return false
- }
- if (hideRepliedMessages && entry.isLastMessageFromReply) {
- return false
- }
- // showAmbient == show in shade but not shelf
- if (!showAmbient && entry.shouldSuppressStatusBar()) {
- return false
- }
- if (
- hidePulsing &&
- entry.showingPulsing() &&
- (!wakeUpCoordinator.notificationsFullyHidden || !entry.isPulseSuppressed)
- ) {
- return false
- }
- return if (bubblesOptional.isPresent && bubblesOptional.get().isBubbleExpanded(entry.key)) {
- false
- } else true
- }
-
- private fun updateNotificationIcons() {
- Trace.beginSection("NotificationIconAreaController.updateNotificationIcons")
- updateStatusBarIcons()
- updateShelfIcons()
- updateAodNotificationIcons()
- applyNotificationIconsTint()
- Trace.endSection()
- }
-
- private fun updateShelfIcons() {
- if (shelfIcons == null) {
- return
- }
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.shelfIcon },
- shelfIcons,
- showAmbient = true,
- showLowPriority = true,
- hideDismissed = false,
- hideRepliedMessages = false,
- hideCurrentMedia = false,
- hidePulsing = false
- )
- }
-
- /**
- * Updates the notification icons for a host layout. This will ensure that the notification host
- * layout will have the same icons like the ones in here.
- *
- * @param function A function to look up an icon view based on an entry
- * @param hostLayout which layout should be updated
- * @param showAmbient should ambient notification icons be shown
- * @param showLowPriority should icons from silent notifications be shown
- * @param hideDismissed should dismissed icons be hidden
- * @param hideRepliedMessages should messages that have been replied to be hidden
- * @param hidePulsing should pulsing notifications be hidden
- */
- private fun updateIconsForLayout(
- function: Function<NotificationEntry, StatusBarIconView?>,
- hostLayout: NotificationIconContainer?,
- showAmbient: Boolean,
- showLowPriority: Boolean,
- hideDismissed: Boolean,
- hideRepliedMessages: Boolean,
- hideCurrentMedia: Boolean,
- hidePulsing: Boolean,
- ) {
- val toShow = ArrayList<StatusBarIconView>(notificationEntries.size)
- // Filter out ambient notifications and notification children.
- for (i in notificationEntries.indices) {
- val entry = notificationEntries[i].representativeEntry
- if (entry != null && entry.row != null) {
- if (
- shouldShowNotificationIcon(
- entry,
- showAmbient,
- showLowPriority,
- hideDismissed,
- hideRepliedMessages,
- hideCurrentMedia,
- hidePulsing
- )
- ) {
- val iconView = function.apply(entry)
- if (iconView != null) {
- toShow.add(iconView)
- }
- }
- }
- }
-
- // In case we are changing the suppression of a group, the replacement shouldn't flicker
- // and it should just be replaced instead. We therefore look for notifications that were
- // just replaced by the child or vice-versa to suppress this.
- val replacingIcons = ArrayMap<String, ArrayList<StatusBarIcon>>()
- val toRemove = ArrayList<View>()
- for (i in 0 until hostLayout!!.childCount) {
- val child = hostLayout.getChildAt(i) as? StatusBarIconView ?: continue
- if (!toShow.contains(child)) {
- var iconWasReplaced = false
- val removedGroupKey = child.notification.groupKey
- for (j in toShow.indices) {
- val candidate = toShow[j]
- if (
- candidate.sourceIcon.sameAs(child.sourceIcon) &&
- candidate.notification.groupKey == removedGroupKey
- ) {
- if (!iconWasReplaced) {
- iconWasReplaced = true
- } else {
- iconWasReplaced = false
- break
- }
- }
- }
- if (iconWasReplaced) {
- var statusBarIcons = replacingIcons[removedGroupKey]
- if (statusBarIcons == null) {
- statusBarIcons = ArrayList()
- replacingIcons[removedGroupKey] = statusBarIcons
- }
- statusBarIcons.add(child.statusBarIcon)
- }
- toRemove.add(child)
- }
- }
- // removing all duplicates
- val duplicates = ArrayList<String?>()
- for (key in replacingIcons.keys) {
- val statusBarIcons = replacingIcons[key]!!
- if (statusBarIcons.size != 1) {
- duplicates.add(key)
- }
- }
- replacingIcons.removeAll(duplicates)
- hostLayout.setReplacingIcons(replacingIcons)
- val toRemoveCount = toRemove.size
- for (i in 0 until toRemoveCount) {
- hostLayout.removeView(toRemove[i])
- }
- val params = generateIconLayoutParams()
- for (i in toShow.indices) {
- val v = toShow[i]
- // The view might still be transiently added if it was just removed and added again
- hostLayout.removeTransientView(v)
- if (v.parent == null) {
- if (hideDismissed) {
- v.setOnDismissListener(updateStatusBarIcons)
- }
- hostLayout.addView(v, i, params)
- }
- }
- hostLayout.setChangingViewPositions(true)
- // Re-sort notification icons
- val childCount = hostLayout.childCount
- for (i in 0 until childCount) {
- val actual = hostLayout.getChildAt(i)
- val expected = toShow[i]
- if (actual === expected) {
- continue
- }
- hostLayout.removeView(expected)
- hostLayout.addView(expected, i)
- }
- hostLayout.setChangingViewPositions(false)
- 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 0d2f00a..7592619 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,103 +15,306 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-import android.content.res.Resources
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.graphics.Rect
import android.view.View
-import androidx.annotation.DimenRes
+import android.view.ViewPropertyAnimator
+import android.widget.FrameLayout
+import androidx.collection.ArrayMap
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.internal.policy.SystemBarUtils
+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.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
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.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
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.statusbar.policy.onConfigChanged
+import com.android.systemui.util.children
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.kotlin.stateFlow
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
+import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
object NotificationIconContainerViewBinder {
+ @JvmStatic
fun bind(
view: NotificationIconContainer,
viewModel: NotificationIconContainerViewModel,
+ configuration: ConfigurationState,
configurationController: ConfigurationController,
dozeParameters: DozeParameters,
featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
+ viewStore: IconViewStore,
): DisposableHandle {
+ val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
return view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) }
- launch {
- viewModel.isDozing.collect { (isDozing, animate) ->
- val animateIfNotBlanking = animate && !dozeParameters.displayNeedsBlanking
- view.setDozing(isDozing, animateIfNotBlanking, /* delay= */ 0) {
- viewModel.completeDozeAnimation()
- }
- }
- }
- // TODO(278765923): this should live where AOD is bound, not inside of the NIC
+ launch { bindAnimationsEnabled(viewModel, view) }
+ launch { bindIsDozing(viewModel, view, dozeParameters) }
+ // 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,
- )
bindVisibility(
viewModel,
view,
+ configuration,
featureFlags,
screenOffAnimationController,
- iconAppearTranslation,
- ) {
- viewModel.completeVisibilityAnimation()
+ )
+ }
+ launch { bindIconColors(viewModel, view, contrastColorUtil) }
+ launch {
+ bindIconViewData(
+ viewModel,
+ view,
+ configuration,
+ configurationController,
+ viewStore,
+ )
+ }
+ launch { bindIsolatedIcon(viewModel, view, viewStore) }
+ }
+ }
+ }
+
+ private suspend fun bindAnimationsEnabled(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer
+ ) {
+ viewModel.animationsEnabled.collect(view::setAnimationsEnabled)
+ }
+
+ private suspend fun bindIconColors(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ viewModel.iconColors
+ .mapNotNull { lookup -> lookup.iconColors(view.viewBounds) }
+ .collect { iconLookup -> applyTint(view, iconLookup, contrastColorUtil) }
+ }
+
+ private suspend fun bindIsDozing(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ dozeParameters: DozeParameters,
+ ) {
+ viewModel.isDozing.collect { isDozing ->
+ if (isDozing.isAnimating) {
+ val animate = !dozeParameters.displayNeedsBlanking
+ view.setDozing(
+ /* dozing = */ isDozing.value,
+ /* fade = */ animate,
+ /* delay = */ 0,
+ /* endRunnable = */ isDozing::stopAnimating,
+ )
+ } else {
+ view.setDozing(
+ /* dozing = */ isDozing.value,
+ /* fade= */ false,
+ /* delay= */ 0,
+ )
+ }
+ }
+ }
+
+ private suspend fun bindIsolatedIcon(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ viewStore: IconViewStore,
+ ) {
+ coroutineScope {
+ launch {
+ viewModel.isolatedIconLocation.collect { location ->
+ view.setIsolatedIconLocation(location, true)
+ }
+ }
+ launch {
+ viewModel.isolatedIcon.collect { iconInfo ->
+ val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) }
+ if (iconInfo.isAnimating) {
+ view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating)
+ } else {
+ view.showIconIsolated(iconView)
}
}
}
}
}
+
+ private suspend fun bindIconViewData(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
+ viewStore: IconViewStore,
+ ): Unit = coroutineScope {
+ val iconSizeFlow: Flow<Int> =
+ configuration.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_icon_size_sp,
+ )
+ val iconHorizontalPaddingFlow: Flow<Int> =
+ configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
+ val statusBarHeightFlow: StateFlow<Int> =
+ stateFlow(changedSignals = configurationController.onConfigChanged) {
+ SystemBarUtils.getStatusBarHeight(view.context)
+ }
+ val layoutParams: Flow<FrameLayout.LayoutParams> =
+ combine(iconSizeFlow, iconHorizontalPaddingFlow, statusBarHeightFlow) {
+ iconSize,
+ iconHPadding,
+ statusBarHeight,
+ ->
+ FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
+ }
+
+ launch {
+ layoutParams.collect { params: FrameLayout.LayoutParams ->
+ for (child in view.children) {
+ child.layoutParams = params
+ }
+ }
+ }
+
+ var prevIcons = IconsViewData()
+ viewModel.iconsViewData.sample(layoutParams, ::Pair).collect {
+ (iconsData: IconsViewData, layoutParams: FrameLayout.LayoutParams),
+ ->
+ val iconsDiff = IconsViewData.computeDifference(iconsData, prevIcons)
+ prevIcons = iconsData
+
+ val replacingIcons =
+ iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, v) ->
+ viewStore.iconView(v.notifKey)?.statusBarIcon
+ }
+ view.setReplacingIcons(replacingIcons)
+
+ val childrenByNotifKey: Map<String, StatusBarIconView> =
+ view.children.filterIsInstance<StatusBarIconView>().associateByTo(ArrayMap()) {
+ it.notification.key
+ }
+
+ iconsDiff.removed
+ .mapNotNull { key -> childrenByNotifKey[key] }
+ .forEach { child -> view.removeView(child) }
+
+ val toAdd = iconsDiff.added.mapNotNull { viewStore.iconView(it.notifKey) }
+ for ((i, sbiv) in toAdd.withIndex()) {
+ // The view might still be transiently added if it was just removed
+ // and added again
+ view.removeTransientView(sbiv)
+ view.addView(sbiv, i, layoutParams)
+ }
+
+ view.setChangingViewPositions(true)
+ // Re-sort notification icons
+ val childCount = view.childCount
+ for (i in 0 until childCount) {
+ val actual = view.getChildAt(i)
+ val expected = viewStore.iconView(iconsData.visibleKeys[i].notifKey)!!
+ if (actual === expected) {
+ continue
+ }
+ view.removeView(expected)
+ view.addView(expected, i)
+ }
+ view.setChangingViewPositions(false)
+
+ view.setReplacingIcons(null)
+ }
+ }
+
+ // TODO(b/305739416): Once StatusBarIconView 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>()
+ .filter { it.width != 0 }
+ .forEach { iv -> 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,
+ configuration: ConfigurationState,
featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
- iconAppearTranslation: StateFlow<Int>,
- onAnimationEnd: () -> Unit,
- ) {
+ ): Unit = coroutineScope {
+ val iconAppearTranslation =
+ configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
- viewModel.isVisible.collect { (isVisible, animate) ->
+ viewModel.isVisible.collect { isVisible ->
view.animate().cancel()
+ val animatorListener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ isVisible.stopAnimating()
+ }
+ }
when {
- !animate -> {
+ !isVisible.isAnimating -> {
view.alpha = 1f
if (!statusViewMigrated) {
view.translationY = 0f
}
- view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+ view.visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
}
featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
animateInIconTranslation(view, statusViewMigrated)
- if (isVisible) {
- CrossFadeHelper.fadeIn(view, onAnimationEnd)
+ if (isVisible.value) {
+ CrossFadeHelper.fadeIn(view, animatorListener)
} else {
- CrossFadeHelper.fadeOut(view, onAnimationEnd)
+ CrossFadeHelper.fadeOut(view, animatorListener)
}
}
- !isVisible -> {
+ !isVisible.value -> {
// Let's make sure the icon are translated to 0, since we cancelled it above
animateInIconTranslation(view, statusViewMigrated)
- CrossFadeHelper.fadeOut(view, onAnimationEnd)
+ CrossFadeHelper.fadeOut(view, animatorListener)
}
view.visibility != View.VISIBLE -> {
// No fading here, let's just appear the icons instead!
@@ -122,14 +325,14 @@
animate = screenOffAnimationController.shouldAnimateAodIcons(),
iconAppearTranslation.value,
statusViewMigrated,
+ animatorListener,
)
- onAnimationEnd()
}
else -> {
// Let's make sure the icons are translated to 0, since we cancelled it above
animateInIconTranslation(view, statusViewMigrated)
// We were fading out, let's fade in instead
- CrossFadeHelper.fadeIn(view, onAnimationEnd)
+ CrossFadeHelper.fadeIn(view, animatorListener)
}
}
}
@@ -140,18 +343,20 @@
animate: Boolean,
iconAppearTranslation: Int,
statusViewMigrated: Boolean,
+ animatorListener: Animator.AnimatorListener,
) {
if (animate) {
if (!statusViewMigrated) {
view.translationY = -iconAppearTranslation.toFloat()
}
view.alpha = 0f
- animateInIconTranslation(view, statusViewMigrated)
view
.animate()
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
+ .apply { if (statusViewMigrated) animateInIconTranslation() }
+ .setListener(animatorListener)
.start()
} else {
view.alpha = 1.0f
@@ -163,24 +368,59 @@
private fun animateInIconTranslation(view: View, statusViewMigrated: Boolean) {
if (!statusViewMigrated) {
- view
- .animate()
- .setInterpolator(Interpolators.DECELERATE_QUINT)
- .translationY(0f)
- .setDuration(AOD_ICONS_APPEAR_DURATION)
- .start()
+ view.animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
}
}
+ private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
+ setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
+
private const val AOD_ICONS_APPEAR_DURATION: Long = 200
+
+ 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,
+ )
+ }
+
+ /** External storage for [StatusBarIconView] instances. */
+ fun interface IconViewStore {
+ fun iconView(key: String): StatusBarIconView?
+ }
}
-fun Resources.getConfigAwareDimensionPixelSize(
- scope: CoroutineScope,
- configurationController: ConfigurationController,
- @DimenRes id: Int,
-): StateFlow<Int> =
- scope.stateFlow(
- changedSignals = configurationController.onDensityOrFontScaleChanged,
- getValue = { getDimensionPixelSize(id) }
- )
+/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
+class ShelfNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.shelfIcon
+}
+
+/** [IconViewStore] for the always-on display. */
+class AlwaysOnDisplayNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.aodIcon
+}
+
+/** [IconViewStore] for the status bar. */
+class StatusBarNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.statusBarIcon
+}
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 3289a3c..120d342 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,14 @@
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.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
+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.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.util.kotlin.pairwise
@@ -32,11 +42,13 @@
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
+import com.android.systemui.util.ui.zip
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** View-model for the row of notification icons displayed on the always-on display. */
@@ -44,9 +56,11 @@
class NotificationIconContainerAlwaysOnDisplayViewModel
@Inject
constructor(
+ configuration: ConfigurationState,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val featureFlags: FeatureFlagsClassic,
+ iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
@@ -54,8 +68,10 @@
shadeInteractor: ShadeInteractor,
) : NotificationIconContainerViewModel {
- 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(
@@ -80,7 +96,7 @@
AnimatableEvent(isDozing, animate)
}
.distinctUntilChanged()
- .toAnimatedValueFlow(completionEvents = onDozeAnimationComplete)
+ .toAnimatedValueFlow()
override val isVisible: Flow<AnimatedValue<Boolean>> =
combine(
@@ -90,46 +106,54 @@
isPulseExpandingAnimated(),
) {
onKeyguard: Boolean,
- bypassEnabled: Boolean,
- (notifsFullyHidden: Boolean, isAnimatingHide: Boolean),
- (pulseExpanding: Boolean, isAnimatingPulse: Boolean),
+ isBypassEnabled: Boolean,
+ notifsFullyHidden: AnimatedValue<Boolean>,
+ pulseExpanding: AnimatedValue<Boolean>,
->
- val isAnimating = isAnimatingHide || isAnimatingPulse
when {
// Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
// animation is playing, in which case we want them to be visible if we're
// animating in the AOD UI and will be switching to KEYGUARD shortly.
!onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
- AnimatedValue(false, isAnimating = false)
- // If we're bypassing, then we're visible
- bypassEnabled -> AnimatedValue(true, isAnimating)
- // If we are pulsing (and not bypassing), then we are hidden
- pulseExpanding -> AnimatedValue(false, isAnimating)
- // If notifs are fully gone, then we're visible
- notifsFullyHidden -> AnimatedValue(true, isAnimating)
- // Otherwise, we're hidden
- else -> AnimatedValue(false, isAnimating)
+ AnimatedValue.NotAnimating(false)
+ else ->
+ zip(notifsFullyHidden, pulseExpanding) {
+ areNotifsFullyHidden,
+ isPulseExpanding,
+ ->
+ when {
+ // If we're bypassing, then we're visible
+ isBypassEnabled -> true
+ // If we are pulsing (and not bypassing), then we are hidden
+ isPulseExpanding -> false
+ // If notifs are fully gone, then we're visible
+ areNotifsFullyHidden -> true
+ // Otherwise, we're hidden
+ else -> false
+ }
+ }
}
}
.distinctUntilChanged()
- override fun completeDozeAnimation() {
- onDozeAnimationComplete.tryEmit(Unit)
- }
+ override val iconsViewData: Flow<IconsViewData> =
+ iconsInteractor.aodNotifs.map { entries ->
+ IconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
+ )
+ }
- override fun completeVisibilityAnimation() {
- onVisAnimationComplete.tryEmit(Unit)
- }
+ override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
+ flowOf(AnimatedValue.NotAnimating(null))
+ override val isolatedIconLocation: Flow<Rect> = emptyFlow()
/** Is there an expanded pulse, are we animating in response? */
private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
return notificationsKeyguardInteractor.isPulseExpanding
.pairwise(initialValue = null)
// If pulsing changes, start animating, unless it's the first emission
- .map { (prev, expanding) ->
- AnimatableEvent(expanding!!, startAnimating = prev != null)
- }
- .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+ .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) }
+ .toAnimatedValueFlow()
}
/** Are notifications completely hidden from view, are we animating in response? */
@@ -151,10 +175,18 @@
// We only want the appear animations to happen when the notifications
// get fully hidden, since otherwise the un-hide animation overlaps.
featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
- else -> fullyHidden!!
+ else -> fullyHidden
}
- AnimatableEvent(fullyHidden!!, animate)
+ AnimatableEvent(fullyHidden, animate)
}
- .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+ .toAnimatedValueFlow()
+ }
+
+ 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 c44a2b6..c6aabb7 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,18 +15,37 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
+import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
import com.android.systemui.util.ui.AnimatedValue
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/** View-model for the overflow row of notification icons displayed in the notification shade. */
-class NotificationIconContainerShelfViewModel @Inject constructor() :
- NotificationIconContainerViewModel {
+class NotificationIconContainerShelfViewModel
+@Inject
+constructor(
+ interactor: NotificationIconsInteractor,
+) : NotificationIconContainerViewModel {
+
override val animationsEnabled: Flow<Boolean> = flowOf(true)
override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override fun completeDozeAnimation() {}
- override fun completeVisibilityAnimation() {}
+ override val iconColors: Flow<ColorLookup> = emptyFlow()
+ override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
+ flowOf(AnimatedValue.NotAnimating(null))
+ override val isolatedIconLocation: Flow<Rect> = emptyFlow()
+
+ override val iconsViewData: Flow<IconsViewData> =
+ interactor.filteredNotifSet().map { entries ->
+ IconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.shelfIcon) },
+ )
+ }
}
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 035687a..4d14024 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,19 +15,39 @@
*/
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.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor
+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.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
+import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
/** View-model for the row of notification icons displayed in the status bar, */
class NotificationIconContainerStatusBarViewModel
@Inject
constructor(
+ darkIconInteractor: DarkIconInteractor,
+ iconsInteractor: StatusBarNotificationIconsInteractor,
+ headsUpIconInteractor: HeadsUpNotificationIconInteractor,
keyguardInteractor: KeyguardInteractor,
+ notificationsInteractor: ActiveNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) : NotificationIconContainerViewModel {
override val animationsEnabled: Flow<Boolean> =
@@ -38,8 +58,66 @@
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() {}
+
+ override val iconsViewData: Flow<IconsViewData> =
+ iconsInteractor.statusBarNotifs.map { entries ->
+ IconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
+ )
+ }
+
+ override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
+ headsUpIconInteractor.isolatedNotification
+ .pairwise(initialValue = null)
+ .sample(combine(iconsViewData, shadeInteractor.shadeExpansion, ::Pair)) {
+ (prev, isolatedNotif),
+ (iconsViewData, shadeExpansion),
+ ->
+ val iconInfo =
+ isolatedNotif?.let {
+ iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif }
+ }
+ val animate =
+ when {
+ isolatedNotif == prev -> false
+ isolatedNotif == null || prev == null -> shadeExpansion == 0f
+ else -> false
+ }
+ AnimatableEvent(iconInfo, animate)
+ }
+ .toAnimatedValueFlow()
+
+ override val isolatedIconLocation: Flow<Rect> =
+ headsUpIconInteractor.isolatedIconLocation.filterNotNull()
+
+ 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 65eb220..a611323 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,12 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
+import android.graphics.drawable.Icon
+import androidx.collection.ArrayMap
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
import com.android.systemui.util.ui.AnimatedValue
import kotlinx.coroutines.flow.Flow
@@ -23,6 +29,7 @@
* AOD.
*/
interface NotificationIconContainerViewModel {
+
/** Are changes to the icon container animated? */
val animationsEnabled: Flow<Boolean>
@@ -32,15 +39,158 @@
/** Is the icon container visible? */
val isVisible: Flow<AnimatedValue<Boolean>>
- /**
- * Signal completion of the [isDozing] animation; if [isDozing]'s [AnimatedValue.isAnimating]
- * property was `true`, calling this method will update it to `false.
- */
- fun completeDozeAnimation()
+ /** The colors with which to display the notification icons. */
+ val iconColors: Flow<ColorLookup>
+
+ /** [IconsViewData] indicating which icons to display in the view. */
+ val iconsViewData: Flow<IconsViewData>
+
+ /** An Icon to show "isolated" in the IconContainer. */
+ val isolatedIcon: Flow<AnimatedValue<IconInfo?>>
+
+ /** Location to show an isolated icon, if there is one. */
+ val isolatedIconLocation: Flow<Rect>
/**
- * Signal completion of the [isVisible] animation; if [isVisible]'s [AnimatedValue.isAnimating]
- * property was `true`, calling this method will update it to `false.
+ * 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 completeVisibilityAnimation()
+ 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
+ }
+
+ /** Encapsulates the collection of notification icons present on the device. */
+ data class IconsViewData(
+ /** Icons that are visible in the container. */
+ val visibleKeys: List<IconInfo> = emptyList(),
+ /** Keys of icons that are "behind" the overflow dot. */
+ val collapsedKeys: Set<String> = emptySet(),
+ /** Whether the overflow dot should be shown regardless if [collapsedKeys] is empty. */
+ val forceShowDot: Boolean = false,
+ ) {
+ /** The difference between two [IconsViewData]s. */
+ data class Diff(
+ /** Icons added in the newer dataset. */
+ val added: List<IconInfo> = emptyList(),
+ /** Icons removed from the older dataset. */
+ val removed: List<String> = emptyList(),
+ /**
+ * Groups whose icon was replaced with a single new notification icon. The key of the
+ * [Map] is the notification group key, and the value is the new icon.
+ *
+ * Specifically, this models a difference where the older dataset had notification
+ * groups with a single icon in the set, and the newer dataset has a single, different
+ * icon for the same group. A view binder can use this information for special
+ * animations for this specific change.
+ */
+ val groupReplacements: Map<String, IconInfo> = emptyMap(),
+ )
+
+ companion object {
+ /**
+ * Returns an [IconsViewData.Diff] calculated from a [new] and [previous][prev]
+ * [IconsViewData] state.
+ */
+ fun computeDifference(new: IconsViewData, prev: IconsViewData): Diff {
+ val added: List<IconInfo> =
+ new.visibleKeys.filter {
+ it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey }
+ }
+ val removed: List<IconInfo> =
+ prev.visibleKeys.filter {
+ it.notifKey !in new.visibleKeys.asSequence().map { it.notifKey }
+ }
+ val groupsToShow: Set<IconGroupInfo> =
+ new.visibleKeys.asSequence().map { it.groupInfo }.toSet()
+ val replacements: ArrayMap<String, IconInfo> =
+ removed
+ .asSequence()
+ .filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow }
+ .groupBy { it.groupInfo.groupKey }
+ .mapValuesNotNullTo(ArrayMap()) { (_, vs) ->
+ vs.takeIf { it.size == 1 }?.get(0)
+ }
+ return Diff(added, removed.map { it.notifKey }, replacements)
+ }
+ }
+ }
+
+ /** An Icon, and keys for unique identification. */
+ data class IconInfo(
+ val sourceIcon: Icon,
+ val notifKey: String,
+ val groupKey: String,
+ )
+}
+
+/**
+ * Construct an [IconInfo] out of an [ActiveNotificationModel], or return `null` if one cannot be
+ * created due to missing information.
+ */
+fun ActiveNotificationModel.toIconInfo(sourceIcon: Icon?): IconInfo? {
+ return sourceIcon?.let {
+ groupKey?.let { groupKey ->
+ IconInfo(
+ sourceIcon = sourceIcon,
+ notifKey = key,
+ groupKey = groupKey,
+ )
+ }
+ }
+}
+
+private val IconInfo.groupInfo: IconGroupInfo
+ get() = IconGroupInfo(sourceIcon, groupKey)
+
+private data class IconGroupInfo(
+ val sourceIcon: Icon,
+ val groupKey: String,
+) {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as IconGroupInfo
+
+ if (groupKey != other.groupKey) return false
+ return sourceIcon.sameAs(other.sourceIcon)
+ }
+
+ override fun hashCode(): Int {
+ var result = groupKey.hashCode()
+ result = 31 * result + sourceIcon.type.hashCode()
+ when (sourceIcon.type) {
+ Icon.TYPE_BITMAP,
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ result = 31 * result + sourceIcon.bitmap.hashCode()
+ }
+ Icon.TYPE_DATA -> {
+ result = 31 * result + sourceIcon.dataLength.hashCode()
+ result = 31 * result + sourceIcon.dataOffset.hashCode()
+ }
+ Icon.TYPE_RESOURCE -> {
+ result = 31 * result + sourceIcon.resId.hashCode()
+ result = 31 * result + sourceIcon.resPackage.hashCode()
+ }
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ result = 31 * result + sourceIcon.uriString.hashCode()
+ }
+ }
+ return result
+ }
}
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 0000000..78370ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.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.statusbar.notification.shared
+
+import android.graphics.drawable.Icon
+
+/** Model for entries in the notification stack. */
+data class ActiveNotificationModel(
+ /** Notification key associated with this entry. */
+ val key: String,
+ /** Notification group key associated with this entry. */
+ val groupKey: String?,
+ /** Is this entry in the ambient / minimized section (lowest priority)? */
+ val isAmbient: Boolean,
+ /**
+ * Is this entry dismissed? This is `true` when the user has dismissed the notification in the
+ * UI, but `NotificationManager` has not yet signalled to us that it has received the dismissal.
+ */
+ val isRowDismissed: Boolean,
+ /** Is this entry in the silent section? */
+ val isSilent: Boolean,
+ /**
+ * Does this entry represent a conversation, the last message of which was from a remote input
+ * reply?
+ */
+ val isLastMessageFromReply: Boolean,
+ /** Is this entry suppressed from appearing in the status bar as an icon? */
+ val isSuppressedFromStatusBar: Boolean,
+ /** Is this entry actively pulsing on AOD or bypassed-keyguard? */
+ val isPulsing: Boolean,
+ /** Icon to display on AOD. */
+ val aodIcon: Icon?,
+ /** Icon to display in the notification shelf. */
+ val shelfIcon: Icon?,
+ /** Icon to display in the status bar. */
+ val statusBarIcon: Icon?,
+)
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 e4d96c3..6bb9573 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 @@
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 @@
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 dba93d9..1e9cfa8 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
@@ -107,6 +107,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -324,7 +325,6 @@
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
@@ -695,7 +695,9 @@
super.onFinishInflate();
inflateEmptyShadeView();
- inflateFooterView();
+ if (!FooterViewRefactor.isEnabled()) {
+ inflateFooterView();
+ }
}
/**
@@ -730,9 +732,11 @@
}
void reinflateViews() {
- inflateFooterView();
+ if (!FooterViewRefactor.isEnabled()) {
+ inflateFooterView();
+ updateFooter();
+ }
inflateEmptyShadeView();
- updateFooter();
mSectionsManager.reinflateViews();
}
@@ -747,7 +751,7 @@
@VisibleForTesting
public void updateFooter() {
- if (mFooterView == null) {
+ if (mFooterView == null || mController == null) {
return;
}
// TODO: move this logic to controller, which will invoke updateFooterView directly
@@ -3144,10 +3148,6 @@
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 +3410,6 @@
mAnimationEvents.add(animEvent);
}
mChildrenChangingPositions.clear();
- if (mGenerateChildOrderChangedEvent) {
- mAnimationEvents.add(new AnimationEvent(null,
- AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
- mGenerateChildOrderChangedEvent = false;
- }
}
private void generateChildAdditionEvents() {
@@ -4556,7 +4551,8 @@
return mFooterView != null && mFooterView.isHistoryShown();
}
- void setFooterView(@NonNull FooterView footerView) {
+ /** Bind the {@link FooterView} to the NSSL. */
+ public void setFooterView(@NonNull FooterView footerView) {
int index = -1;
if (mFooterView != null) {
index = indexOfChild(mFooterView);
@@ -4567,6 +4563,16 @@
if (mManageButtonClickListener != null) {
mFooterView.setManageButtonClickListener(mManageButtonClickListener);
}
+ mFooterView.setClearAllButtonClickListener(v -> {
+ if (mFooterClearAllListener != null) {
+ mFooterClearAllListener.onClearAll();
+ }
+ clearNotifications(ROWS_ALL, true /* closeShade */);
+ footerView.setSecondaryVisible(false /* visible */, true /* animate */);
+ });
+ if (FooterViewRefactor.isEnabled()) {
+ updateFooter();
+ }
}
public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4629,7 +4635,9 @@
mFooterView.setVisible(visible, animate);
mFooterView.setSecondaryVisible(showDismissView, animate);
mFooterView.showHistory(showHistory);
- mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
+ if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
+ }
}
@VisibleForTesting
@@ -4764,14 +4772,6 @@
info.setClassName(ScrollView.class.getName());
}
- public void generateChildOrderChangedEvent() {
- if (mIsExpanded && mAnimationsEnabled) {
- mGenerateChildOrderChangedEvent = true;
- mNeedsAnimation = true;
- requestChildrenUpdate();
- }
- }
-
public int getContainerChildCount() {
return getChildCount();
}
@@ -5388,15 +5388,9 @@
@VisibleForTesting
protected void inflateFooterView() {
+ FooterViewRefactor.assertInLegacyMode();
FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_footer, this, false);
- footerView.setClearAllButtonClickListener(v -> {
- if (mFooterClearAllListener != null) {
- mFooterClearAllListener.onClearAll();
- }
- clearNotifications(ROWS_ALL, true /* closeShade */);
- footerView.setSecondaryVisible(false /* visible */, true /* animate */);
- });
setFooterView(footerView);
}
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 6a70815..79448b4 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
@@ -62,7 +62,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
@@ -106,6 +106,7 @@
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.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 @@
private final GroupExpansionManager mGroupExpansionManager;
private final NotifPipelineFlags mNotifPipelineFlags;
- private final NotificationListInteractor mNotificationListInteractor;
+ private final SeenNotificationsInteractor mSeenNotificationsInteractor;
private final KeyguardTransitionRepository mKeyguardTransitionRepo;
private NotificationStackScrollLayout mView;
@@ -206,7 +206,7 @@
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private boolean mIsInTransitionToAod = false;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlags;
private final RefactorFlag mShelfRefactor;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private final SecureSettings mSecureSettings;
@@ -662,14 +662,14 @@
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
- NotificationListInteractor notificationListInteractor,
+ SeenNotificationsInteractor seenNotificationsInteractor,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
NotificationIconAreaController notifIconAreaController,
- FeatureFlags featureFlags,
+ FeatureFlagsClassic featureFlags,
NotificationTargetsHelper notificationTargetsHelper,
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
@@ -715,7 +715,7 @@
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
- mNotificationListInteractor = notificationListInteractor;
+ mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mNotifIconAreaController = notifIconAreaController;
mFeatureFlags = featureFlags;
@@ -832,7 +832,8 @@
mViewModel.ifPresent(
vm -> NotificationListViewBinder
- .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController));
+ .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController,
+ mConfigurationController));
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
this::onKeyguardTransitionChanged);
@@ -1725,28 +1726,11 @@
}
@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 +1825,6 @@
}
@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 +1980,7 @@
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/notification/stack/data/repository/NotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
deleted file mode 100644
index f6ed8c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
+++ /dev/null
@@ -1,35 +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.stack.data.repository
-
-import com.android.systemui.dagger.SysUISingleton
-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. */
-@SysUISingleton
-class NotificationListRepository @Inject constructor() {
- private val _hasFilteredOutSeenNotifications = MutableStateFlow(false)
- val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
- _hasFilteredOutSeenNotifications.asStateFlow()
-
- fun setHasFilteredOutSeenNotifications(value: Boolean) {
- _hasFilteredOutSeenNotifications.value = value
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index dee3973..a3792cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,14 +17,23 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
-import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
+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.traceSection
+import com.android.systemui.util.view.reinflateAndBindLatest
+import kotlinx.coroutines.flow.merge
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
object NotificationListViewBinder {
@@ -33,8 +42,9 @@
view: NotificationStackScrollLayout,
viewModel: NotificationListViewModel,
falsingManager: FalsingManager,
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
iconAreaController: NotificationIconAreaController,
+ configurationController: ConfigurationController,
) {
val shelf =
LayoutInflater.from(view.context)
@@ -47,5 +57,26 @@
iconAreaController
)
view.setShelf(shelf)
+
+ viewModel.footer.ifPresent { footerViewModel ->
+ // The footer needs to be re-inflated every time the theme or the font size changes.
+ view.repeatWhenAttached {
+ LayoutInflater.from(view.context).reinflateAndBindLatest(
+ R.layout.status_bar_notification_footer,
+ view,
+ attachToRoot = false,
+ // TODO(b/305930747): This may lead to duplicate invocations if both flows emit,
+ // find a solution to only emit one event.
+ merge(
+ configurationController.onThemeChanged,
+ configurationController.onDensityOrFontScaleChanged,
+ ),
+ ) { view ->
+ traceSection("bind FooterView") {
+ FooterViewBinder.bind(view as FooterView, footerViewModel)
+ }
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 11f68e0..261371d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -17,8 +17,9 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import dagger.Module
import dagger.Provides
@@ -28,6 +29,7 @@
/** ViewModel for the list of notifications. */
class NotificationListViewModel(
val shelf: NotificationShelfViewModel,
+ val footer: Optional<FooterViewModel>,
)
@Module
@@ -36,12 +38,33 @@
@Provides
@SysUISingleton
fun maybeProvideViewModel(
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
shelfViewModel: Provider<NotificationShelfViewModel>,
- ): Optional<NotificationListViewModel> =
- if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
- Optional.of(NotificationListViewModel(shelfViewModel.get()))
+ footerViewModel: Provider<FooterViewModel>,
+ ): Optional<NotificationListViewModel> {
+ return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ if (com.android.systemui.Flags.notificationsFooterViewRefactor()) {
+ Optional.of(
+ NotificationListViewModel(
+ shelfViewModel.get(),
+ Optional.of(footerViewModel.get())
+ )
+ )
+ } else {
+ Optional.of(NotificationListViewModel(shelfViewModel.get(), Optional.empty()))
+ }
} else {
+ if (com.android.systemui.Flags.notificationsFooterViewRefactor()) {
+ throw IllegalStateException(
+ "The com.android.systemui.notifications_footer_view_refactor flag requires " +
+ "the notification_shelf_refactor flag to be enabled. First disable the " +
+ "footer flag using `adb shell device_config put systemui " +
+ "com.android.systemui.notifications_footer_view_refactor false`, then " +
+ "enable the notification_shelf_refactor flag in Flag Flipper. " +
+ "Afterwards, you can try re-enabling the footer refactor flag via adb."
+ )
+ }
Optional.empty()
}
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 07d3a1c..2d125462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -30,7 +30,6 @@
import android.view.WindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.res.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter
import com.android.systemui.animation.DelegateLaunchAnimatorController
@@ -43,6 +42,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
@@ -134,6 +134,19 @@
)
}
+ override fun startPendingIntentMaybeDismissingKeyguard(
+ intent: PendingIntent,
+ intentSentUiThreadCallback: Runnable?,
+ animationController: ActivityLaunchAnimator.Controller?
+ ) {
+ activityStarterInternal.startPendingIntentDismissingKeyguard(
+ intent = intent,
+ intentSentUiThreadCallback = intentSentUiThreadCallback,
+ animationController = animationController,
+ showOverLockscreen = true,
+ )
+ }
+
/**
* TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
* this.
@@ -454,7 +467,7 @@
!willLaunchResolverActivity &&
shouldAnimateLaunch(isActivityIntent = true)
val animController =
- wrapAnimationController(
+ wrapAnimationControllerForShadeOrStatusBar(
animationController = animationController,
dismissShade = dismissShade,
isLaunchForActivity = true,
@@ -547,12 +560,18 @@
)
}
- /** Starts a pending intent after dismissing keyguard. */
+ /**
+ * Starts a pending intent after dismissing keyguard.
+ *
+ * This can be called in a background thread (to prevent calls in [ActivityIntentHelper] in
+ * the main thread).
+ */
fun startPendingIntentDismissingKeyguard(
intent: PendingIntent,
intentSentUiThreadCallback: Runnable? = null,
associatedView: View? = null,
animationController: ActivityLaunchAnimator.Controller? = null,
+ showOverLockscreen: Boolean = false,
) {
val animationController =
if (associatedView is ExpandableNotificationRow) {
@@ -566,79 +585,103 @@
lockScreenUserManager.currentUserId,
))
+ val actuallyShowOverLockscreen =
+ showOverLockscreen &&
+ intent.isActivity &&
+ activityIntentHelper.wouldPendingShowOverLockscreen(
+ intent,
+ lockScreenUserManager.currentUserId
+ )
+
val animate =
!willLaunchResolverActivity &&
animationController != null &&
- shouldAnimateLaunch(intent.isActivity)
+ shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen)
+
+ // We wrap animationCallback with a StatusBarLaunchAnimatorController so
+ // that the shade is collapsed after the animation (or when it is cancelled,
+ // aborted, etc).
+ val statusBarController =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = true,
+ isLaunchForActivity = intent.isActivity,
+ )
+ val controller =
+ if (actuallyShowOverLockscreen) {
+ wrapAnimationControllerForLockscreen(statusBarController)
+ } else {
+ statusBarController
+ }
// If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
// run the animation on the keyguard). The animation will take care of (instantly)
// collapsing the shade and hiding the keyguard once it is done.
val collapse = !animate
- executeRunnableDismissingKeyguard(
- runnable = {
- try {
- // We wrap animationCallback with a StatusBarLaunchAnimatorController so
- // that the shade is collapsed after the animation (or when it is cancelled,
- // aborted, etc).
- val controller: ActivityLaunchAnimator.Controller? =
- wrapAnimationController(
- animationController = animationController,
- dismissShade = true,
- isLaunchForActivity = intent.isActivity,
- )
- activityLaunchAnimator.startPendingIntentWithAnimation(
- controller,
- animate,
- intent.creatorPackage,
- object : PendingIntentStarter {
- override fun startPendingIntent(
- animationAdapter: RemoteAnimationAdapter?
- ): Int {
- val options =
- ActivityOptions(
- CentralSurfaces.getActivityOptions(
- displayId,
- animationAdapter
- )
+ val runnable = Runnable {
+ try {
+ activityLaunchAnimator.startPendingIntentWithAnimation(
+ controller,
+ animate,
+ intent.creatorPackage,
+ actuallyShowOverLockscreen,
+ object : PendingIntentStarter {
+ override fun startPendingIntent(
+ animationAdapter: RemoteAnimationAdapter?
+ ): Int {
+ val options =
+ ActivityOptions(
+ CentralSurfaces.getActivityOptions(
+ displayId,
+ animationAdapter
)
- // TODO b/221255671: restrict this to only be set for
- // notifications
- options.isEligibleForLegacyPermissionPrompt = true
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
)
- return intent.sendAndReturnResult(
- null,
- 0,
- null,
- null,
- null,
- null,
- options.toBundle()
- )
- }
- },
- )
- } catch (e: PendingIntent.CanceledException) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending intent failed: $e")
- if (!collapse) {
- // executeRunnableDismissingKeyguard did not collapse for us already.
- shadeControllerLazy.get().collapseOnMainThread()
- }
- // TODO: Dismiss Keyguard.
+ // TODO b/221255671: restrict this to only be set for
+ // notifications
+ options.isEligibleForLegacyPermissionPrompt = true
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ return intent.sendAndReturnResult(
+ null,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ options.toBundle()
+ )
+ }
+ },
+ )
+ } catch (e: PendingIntent.CanceledException) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: $e")
+ if (!collapse) {
+ // executeRunnableDismissingKeyguard did not collapse for us already.
+ shadeControllerLazy.get().collapseOnMainThread()
}
- if (intent.isActivity) {
- assistManagerLazy.get().hideAssist()
- }
- intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
- },
- afterKeyguardGone = willLaunchResolverActivity,
- dismissShade = collapse,
- willAnimateOnKeyguard = animate,
- )
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity) {
+ assistManagerLazy.get().hideAssist()
+ }
+ intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
+ }
+
+ if (!actuallyShowOverLockscreen) {
+ postOnUiThread(delay = 0) {
+ executeRunnableDismissingKeyguard(
+ runnable = runnable,
+ afterKeyguardGone = willLaunchResolverActivity,
+ dismissShade = collapse,
+ willAnimateOnKeyguard = animate,
+ )
+ }
+ } else {
+ postOnUiThread(delay = 0, runnable)
+ }
}
/** Starts an Activity. */
@@ -678,71 +721,12 @@
// Wrap the animation controller to dismiss the shade and set
// mIsLaunchingActivityOverLockscreen during the animation.
val delegate =
- wrapAnimationController(
+ wrapAnimationControllerForShadeOrStatusBar(
animationController = animationController,
dismissShade = dismissShade,
isLaunchForActivity = true,
)
- delegate?.let {
- controller =
- object : DelegateLaunchAnimatorController(delegate) {
- override fun onIntentStarted(willAnimate: Boolean) {
- delegate?.onIntentStarted(willAnimate)
- if (willAnimate) {
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
- }
- }
-
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- super.onLaunchAnimationStart(isExpandingFullyAbove)
-
- // Double check that the keyguard is still showing and not going
- // away, but if so set the keyguard occluded. Typically, WM will let
- // KeyguardViewMediator know directly, but we're overriding that to
- // play the custom launch animation, so we need to take care of that
- // here. The unocclude animation is not overridden, so WM will call
- // KeyguardViewMediator's unocclude animation runner when the
- // activity is exited.
- if (
- keyguardStateController.isShowing &&
- !keyguardStateController.isKeyguardGoingAway
- ) {
- Log.d(TAG, "Setting occluded = true in #startActivity.")
- keyguardViewMediatorLazy
- .get()
- .setOccluded(true /* isOccluded */, true /* animate */)
- }
- }
-
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- // Set mIsLaunchingActivityOverLockscreen to false before actually
- // finishing the animation so that we can assume that
- // mIsLaunchingActivityOverLockscreen being true means that we will
- // collapse the shade (or at least run the post collapse runnables)
- // later on.
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
- delegate?.onLaunchAnimationEnd(isExpandingFullyAbove)
- }
-
- override fun onLaunchAnimationCancelled(
- newKeyguardOccludedState: Boolean?
- ) {
- if (newKeyguardOccludedState != null) {
- keyguardViewMediatorLazy
- .get()
- .setOccluded(newKeyguardOccludedState, false /* animate */)
- }
-
- // Set mIsLaunchingActivityOverLockscreen to false before actually
- // finishing the animation so that we can assume that
- // mIsLaunchingActivityOverLockscreen being true means that we will
- // collapse the shade (or at least run the // post collapse
- // runnables) later on.
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
- delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
- }
- }
- }
+ controller = wrapAnimationControllerForLockscreen(delegate)
} else if (dismissShade) {
// The animation will take care of dismissing the shade at the end of the animation.
// If we don't animate, collapse it directly.
@@ -874,7 +858,7 @@
* window.
* @param isLaunchForActivity whether the launch is for an activity.
*/
- private fun wrapAnimationController(
+ private fun wrapAnimationControllerForShadeOrStatusBar(
animationController: ActivityLaunchAnimator.Controller?,
dismissShade: Boolean,
isLaunchForActivity: Boolean,
@@ -909,6 +893,72 @@
return animationController
}
+ /**
+ * Wraps an animation controller so that if an activity would be launched on top of the
+ * lockscreen, the correct flags are set for it to be occluded.
+ */
+ private fun wrapAnimationControllerForLockscreen(
+ animationController: ActivityLaunchAnimator.Controller?
+ ): ActivityLaunchAnimator.Controller? {
+ return animationController?.let {
+ object : DelegateLaunchAnimatorController(it) {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ delegate.onIntentStarted(willAnimate)
+ if (willAnimate) {
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
+ }
+ }
+
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ super.onLaunchAnimationStart(isExpandingFullyAbove)
+
+ // Double check that the keyguard is still showing and not going
+ // away, but if so set the keyguard occluded. Typically, WM will let
+ // KeyguardViewMediator know directly, but we're overriding that to
+ // play the custom launch animation, so we need to take care of that
+ // here. The unocclude animation is not overridden, so WM will call
+ // KeyguardViewMediator's unocclude animation runner when the
+ // activity is exited.
+ if (
+ keyguardStateController.isShowing &&
+ !keyguardStateController.isKeyguardGoingAway
+ ) {
+ Log.d(TAG, "Setting occluded = true in #startActivity.")
+ keyguardViewMediatorLazy
+ .get()
+ .setOccluded(true /* isOccluded */, true /* animate */)
+ }
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ // Set mIsLaunchingActivityOverLockscreen to false before actually
+ // finishing the animation so that we can assume that
+ // mIsLaunchingActivityOverLockscreen being true means that we will
+ // collapse the shade (or at least run the post collapse runnables)
+ // later on.
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ }
+
+ override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ if (newKeyguardOccludedState != null) {
+ keyguardViewMediatorLazy
+ .get()
+ .setOccluded(newKeyguardOccludedState, false /* animate */)
+ }
+
+ // Set mIsLaunchingActivityOverLockscreen to false before actually
+ // finishing the animation so that we can assume that
+ // mIsLaunchingActivityOverLockscreen being true means that we will
+ // collapse the shade (or at least run the // post collapse
+ // runnables) later on.
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+ delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
+ }
+ }
+ }
+ }
+
/** Retrieves the current user handle to start the Activity. */
private fun getActivityUserHandle(intent: Intent): UserHandle {
val packages: Array<String> =
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 59f10ae..daa4f18 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 @@
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 9fb6c1b..fa2e3a4 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.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.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;
@@ -204,7 +202,6 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -237,10 +234,10 @@
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;
@@ -298,7 +295,6 @@
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
private final NotificationListContainer mNotifListContainer;
- private final NotificationExpansionRepository mNotificationExpansionRepository;
private boolean mIsShortcutListSearchEnabled;
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -376,9 +372,6 @@
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();
@@ -655,10 +648,8 @@
Lazy<NotificationPresenter> notificationPresenterLazy,
Lazy<NotificationActivityStarter> notificationActivityStarterLazy,
NotificationLaunchAnimatorControllerProvider notifLaunchAnimatorControllerProvider,
- NotificationExpansionRepository notificationExpansionRepository,
DozeParameters dozeParameters,
ScrimController scrimController,
- Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
AuthRippleController authRippleController,
DozeServiceHost dozeServiceHost,
@@ -765,12 +756,10 @@
mPresenterLazy = notificationPresenterLazy;
mNotificationActivityStarterLazy = notificationActivityStarterLazy;
mNotificationAnimationProvider = notifLaunchAnimatorControllerProvider;
- mNotificationExpansionRepository = notificationExpansionRepository;
mDozeServiceHost = dozeServiceHost;
mPowerManager = powerManager;
mDozeParameters = dozeParameters;
mScrimController = scrimController;
- mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
mDozeScrimController = dozeScrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mAuthRippleController = authRippleController;
@@ -1198,10 +1187,6 @@
createNavigationBar(result);
- if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
- mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
- }
-
mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
R.id.ambient_indication_container);
@@ -1268,24 +1253,6 @@
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 +1324,6 @@
// receive broadcasts
registerBroadcastReceiver();
- IntentFilter demoFilter = new IntentFilter();
- if (DEBUG_MEDIA_FAKE_ARTWORK) {
- demoFilter.addAction(ACTION_FAKE_ARTWORK);
- }
- mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
- android.Manifest.permission.DUMP, null,
- Context.RECEIVER_EXPORTED_UNAUDITED);
-
// listen for USER_SETUP_COMPLETE setting (per-user)
mDeviceProvisionedController.addCallback(mUserSetupObserver);
mUserSetupObserver.onUserSetupChanged();
@@ -1525,6 +1484,7 @@
// regressions, we'll continue standing up the root view in CentralSurfaces.
mNotificationShadeWindowController.fetchWindowRootView();
getNotificationShadeWindowViewController().setupExpandedStatusBar();
+ getNotificationShadeWindowViewController().setupCommunalHubLayout();
mShadeController.setNotificationShadeWindowViewController(
getNotificationShadeWindowViewController());
mBackActionInteractor.setup(mQsController, mShadeSurface);
@@ -1582,7 +1542,6 @@
mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
- mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
Trace.endSection();
}
@@ -1869,7 +1828,6 @@
void updateDisplaySize() {
mDisplay.getMetrics(mDisplayMetrics);
mDisplay.getSize(mCurrentDisplaySize);
- mMediaManager.onDisplayUpdated(mDisplay);
if (DEBUG_GESTURES) {
mGestureRec.tag("display",
String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
@@ -1943,19 +1901,6 @@
}
};
- private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.v(TAG, "onReceive: " + intent);
- String action = intent.getAction();
- if (ACTION_FAKE_ARTWORK.equals(action)) {
- if (DEBUG_MEDIA_FAKE_ARTWORK) {
- mPresenterLazy.get().updateMediaMetaData(true, true);
- }
- }
- }
- };
-
/**
* Reload some of our resources when the configuration changes.
*
@@ -2138,7 +2083,6 @@
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mKeyguardStateController.setLaunchTransitionFadingAway(false);
- mPresenterLazy.get().updateMediaMetaData(true /* metaDataChanged */, true);
}
/**
@@ -2162,7 +2106,6 @@
beforeFading.run();
}
updateScrimController();
- mPresenterLazy.get().updateMediaMetaData(false, true);
mShadeSurface.resetAlpha();
mShadeSurface.fadeOut(
FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
@@ -3159,7 +3102,9 @@
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+ }
}
@Override
@@ -3177,7 +3122,9 @@
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
}
- mNotificationIconAreaController.onThemeChanged();
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mNotificationIconAreaController.onThemeChanged();
+ }
}
@Override
@@ -3221,8 +3168,6 @@
updateDozingState();
checkBarModes();
updateScrimController();
- mPresenterLazy.get()
- .updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index d3d11ea..66341ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -37,6 +37,8 @@
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -82,6 +84,7 @@
private final SysuiStatusBarStateController mStatusBarStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final HeadsUpManager mHeadsUpManager;
+ private final FeatureFlagsClassic mFeatureFlags;
private final BatteryController mBatteryController;
private final ScrimController mScrimController;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@@ -107,6 +110,7 @@
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
DeviceProvisionedController deviceProvisionedController,
+ FeatureFlagsClassic featureFlags,
HeadsUpManager headsUpManager, BatteryController batteryController,
ScrimController scrimController,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
@@ -130,6 +134,7 @@
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mAssistManagerLazy = assistManagerLazy;
mDozeScrimController = dozeScrimController;
+ mFeatureFlags = featureFlags;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPulseExpansionHandler = pulseExpansionHandler;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -173,8 +178,13 @@
void fireNotificationPulse(NotificationEntry entry) {
Runnable pulseSuppressedListener = () -> {
- entry.setPulseSuppressed(true);
- mNotificationIconAreaController.updateAodNotificationIcons();
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mHeadsUpManager.removeNotification(
+ entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
+ } else {
+ entry.setPulseSuppressed(true);
+ mNotificationIconAreaController.updateAodNotificationIcons();
+ }
};
Assert.isMainThread();
for (Callback callback : mCallbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index c493eeda..8fee5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -26,6 +26,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -38,6 +40,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -104,6 +107,8 @@
};
private boolean mAnimationsEnabled = true;
private final KeyguardStateController mKeyguardStateController;
+ private final FeatureFlagsClassic mFeatureFlags;
+ private final HeadsUpNotificationIconInteractor mHeadsUpNotificationIconInteractor;
@VisibleForTesting
@Inject
@@ -122,6 +127,8 @@
NotificationRoundnessManager notificationRoundnessManager,
HeadsUpStatusBarView headsUpStatusBarView,
Clock clockView,
+ FeatureFlagsClassic featureFlags,
+ HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor,
@Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
super(headsUpStatusBarView);
mNotificationIconAreaController = notificationIconAreaController;
@@ -139,6 +146,8 @@
mStackScrollerController = stackScrollerController;
mShadeViewController = shadeViewController;
+ mFeatureFlags = featureFlags;
+ mHeadsUpNotificationIconInteractor = headsUpNotificationIconInteractor;
mStackScrollerController.setHeadsUpAppearanceController(this);
mClockView = clockView;
mOperatorNameViewOptional = operatorNameViewOptional;
@@ -170,6 +179,9 @@
mHeadsUpManager.addListener(this);
mView.setOnDrawingRectChangedListener(
() -> updateIsolatedIconLocation(true /* requireUpdate */));
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ updateIsolatedIconLocation(true);
+ }
mWakeUpCoordinator.addListener(this);
getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(this);
@@ -185,6 +197,9 @@
protected void onViewDetached() {
mHeadsUpManager.removeListener(this);
mView.setOnDrawingRectChangedListener(null);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
+ }
mWakeUpCoordinator.removeListener(this);
getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(null);
@@ -193,8 +208,13 @@
}
private void updateIsolatedIconLocation(boolean requireStateUpdate) {
- mNotificationIconAreaController.setIsolatedIconLocation(
- mView.getIconDrawingRect(), requireStateUpdate);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mHeadsUpNotificationIconInteractor
+ .setIsolatedIconLocation(mView.getIconDrawingRect());
+ } else {
+ mNotificationIconAreaController.setIsolatedIconLocation(
+ mView.getIconDrawingRect(), requireStateUpdate);
+ }
}
@Override
@@ -230,9 +250,14 @@
setShown(true);
animateIsolation = !isExpanded();
}
- updateIsolatedIconLocation(false /* requireUpdate */);
- mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
- : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
+ newEntry == null ? null : newEntry.getKey());
+ } else {
+ updateIsolatedIconLocation(false /* requireUpdate */);
+ mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
+ : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 6b4382f73..f4862c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -175,7 +175,7 @@
if (!hasPinnedHeadsUp() || topEntry == null) {
return null;
} else {
- if (topEntry.isChildInGroup()) {
+ if (topEntry.rowIsChildInGroup()) {
final NotificationEntry groupSummary =
mGroupMembershipManager.getGroupSummary(topEntry);
if (groupSummary != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 2960520..2206be5 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 @@
* (1.0f - mKeyguardHeadsUpShowingAmount);
}
- if (mSystemEventAnimator.isAnimationRunning()
- && !mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
+ if (mSystemEventAnimator.isAnimationRunning()) {
newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha);
} else {
mView.setTranslationX(0);
@@ -704,21 +703,11 @@
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/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index f9856b0..4284c96c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -448,7 +448,7 @@
}
}
replacingIcons.removeAll(duplicates);
- hostLayout.setReplacingIcons(replacingIcons);
+ hostLayout.setReplacingIconsLegacy(replacingIcons);
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
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 00fd9fb..0000000
--- 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/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index b15c0fd..535f6ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -40,6 +40,8 @@
import com.android.app.animation.Interpolators;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.settingslib.Utils;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
@@ -131,6 +133,9 @@
}
}.setDuration(CONTENT_FADE_DURATION);
+ private final RefactorFlag mIconContainerRefactorFlag =
+ RefactorFlag.forView(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
+
/* Maximum number of icons on AOD when also showing overflow dot. */
private int mMaxIconsOnAod;
@@ -156,7 +161,8 @@
private int mIconSize;
private boolean mDisallowNextAnimation;
private boolean mAnimationsEnabled = true;
- private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
+ private ArrayMap<String, StatusBarIcon> mReplacingIcons;
+ private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIconsLegacy;
// Keep track of the last visible icon so collapsed container can report on its location
private IconState mLastVisibleIconState;
private IconState mFirstVisibleIconState;
@@ -167,6 +173,7 @@
private final int[] mAbsolutePosition = new int[2];
private View mIsolatedIconForAnimation;
private int mThemedTextColorPrimary;
+ private Runnable mIsolatedIconAnimationEndRunnable;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -339,23 +346,29 @@
}
private boolean isReplacingIcon(View child) {
- if (mReplacingIcons == null) {
- return false;
- }
if (!(child instanceof StatusBarIconView)) {
return false;
}
StatusBarIconView iconView = (StatusBarIconView) child;
Icon sourceIcon = iconView.getSourceIcon();
String groupKey = iconView.getNotification().getGroupKey();
- ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey);
- if (statusBarIcons != null) {
- StatusBarIcon replacedIcon = statusBarIcons.get(0);
- if (sourceIcon.sameAs(replacedIcon.icon)) {
- return true;
+ if (mIconContainerRefactorFlag.isEnabled()) {
+ if (mReplacingIcons == null) {
+ return false;
}
+ StatusBarIcon replacedIcon = mReplacingIcons.get(groupKey);
+ return replacedIcon != null && sourceIcon.sameAs(replacedIcon.icon);
+ } else {
+ if (mReplacingIconsLegacy == null) {
+ return false;
+ }
+ ArrayList<StatusBarIcon> statusBarIcons = mReplacingIconsLegacy.get(groupKey);
+ if (statusBarIcons != null) {
+ StatusBarIcon replacedIcon = statusBarIcons.get(0);
+ return sourceIcon.sameAs(replacedIcon.icon);
+ }
+ return false;
}
- return false;
}
@Override
@@ -681,14 +694,36 @@
mAnimationsEnabled = enabled;
}
- public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+ public void setReplacingIconsLegacy(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+ mIconContainerRefactorFlag.assertInLegacyMode();
+ mReplacingIconsLegacy = replacingIcons;
+ }
+
+ public void setReplacingIcons(ArrayMap<String, StatusBarIcon> replacingIcons) {
+ if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
mReplacingIcons = replacingIcons;
}
+ @Deprecated
public void showIconIsolated(StatusBarIconView icon, boolean animated) {
+ mIconContainerRefactorFlag.assertInLegacyMode();
if (animated) {
- mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
+ showIconIsolatedAnimated(icon, null);
+ } else {
+ showIconIsolated(icon);
}
+ }
+
+ public void showIconIsolatedAnimated(StatusBarIconView icon,
+ @Nullable Runnable onAnimationEnd) {
+ if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
+ mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
+ mIsolatedIconAnimationEndRunnable = onAnimationEnd;
+ showIconIsolated(icon);
+ }
+
+ public void showIconIsolated(StatusBarIconView icon) {
+ if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
mIsolatedIcon = icon;
updateState();
}
@@ -813,6 +848,11 @@
animationProperties = UNISOLATION_PROPERTY;
animationProperties.setDelay(
mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0);
+ Consumer<Property> endAction = getEndAction();
+ if (endAction != null) {
+ animationProperties.setAnimationEndAction(endAction);
+ animationProperties.setAnimationCancelAction(endAction);
+ }
} else {
animationProperties = UNISOLATION_PROPERTY_OTHERS;
animationProperties.setDelay(
@@ -836,6 +876,18 @@
needsCannedAnimation = false;
}
+ @Nullable
+ private Consumer<Property> getEndAction() {
+ if (mIsolatedIconAnimationEndRunnable == null) return null;
+ final Runnable endRunnable = mIsolatedIconAnimationEndRunnable;
+ return prop -> {
+ endRunnable.run();
+ if (mIsolatedIconAnimationEndRunnable == endRunnable) {
+ mIsolatedIconAnimationEndRunnable = null;
+ }
+ };
+ }
+
@Override
public void initFrom(View view) {
super.initFrom(view);
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 54d81b8..5a8b636 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 @@
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 5b55264..744d70e 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.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.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 @@
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 @@
mScrimBehind.setViewAlpha(mBehindAlpha);
};
- Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
+ Consumer<TransitionStep> mBouncerToGoneTransition;
@Inject
public ScrimController(
@@ -304,6 +309,7 @@
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+ AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
WallpaperRepository wallpaperRepository,
@Main CoroutineDispatcher mainDispatcher,
@@ -349,6 +355,7 @@
});
mColors = new GradientColors();
mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
+ mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mWallpaperRepository = wallpaperRepository;
mMainDispatcher = mainDispatcher;
@@ -405,7 +412,7 @@
// 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 @@
}
};
- 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 90fddd9..267b563 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 @@
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 57a8e6f..07e2571 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.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 @@
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 @@
NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
- NotificationsInteractor notificationsInteractor,
+ NotificationAlertsInteractor notificationAlertsInteractor,
LockscreenShadeTransitionController shadeTransitionController,
PowerInteractor powerInteractor,
CommandQueue commandQueue,
@@ -127,7 +127,7 @@
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
- mNotificationsInteractor = notificationsInteractor;
+ mNotificationAlertsInteractor = notificationAlertsInteractor;
mNsslController = stackScrollerController;
mShadeTransitionController = shadeTransitionController;
mPowerInteractor = powerInteractor;
@@ -205,7 +205,6 @@
// End old BaseStatusBar.userSwitched
mCommandQueue.animateCollapsePanels();
mMediaManager.clearCurrentMediaNotification();
- updateMediaMetaData(true, false);
}
@Override
@@ -220,11 +219,6 @@
}
@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 @@
@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 85fd2af..71e25e9 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 @@
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)
-
- window?.apply {
- setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
- addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-
- 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
- }
- }
+ setupWindow()
+ setupEdgeToEdge()
setCanceledOnTouchOutside(true)
}
+
+ private fun setupWindow() {
+ window?.apply {
+ 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)
+ decorView.setPadding(0, 0, 0, 0)
+ attributes =
+ attributes.apply {
+ fitInsetsSides = 0
+ horizontalMargin = 0f
+ }
+ }
+ }
+
+ private fun setupEdgeToEdge() {
+ val edgeToEdgeHorizontally =
+ context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+ val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
+ val height = WRAP_CONTENT
+ window?.setLayout(width, height)
+ }
+
+ 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 9d627af..2558645 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.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 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 @@
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 @@
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 @@
@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 @@
updateWindowSize();
}
+
+ mDelegate.onConfigurationChanged(this, configuration);
}
/**
@@ -212,7 +308,9 @@
* 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 @@
* 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 @@
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 @@
}
}
+ /**
+ * 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
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
index 9b0c3fa..9645c69 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/phone/data/repository/DarkIconRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
new file mode 100644
index 0000000..ba377497
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * 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
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+/** Dark-mode state for tinting icons. */
+interface DarkIconRepository {
+ val darkState: StateFlow<DarkChange>
+}
+
+@SysUISingleton
+class DarkIconRepositoryImpl
+@Inject
+constructor(
+ darkIconDispatcher: SysuiDarkIconDispatcher,
+) : DarkIconRepository {
+ override val darkState: StateFlow<DarkChange> = darkIconDispatcher.darkChangeFlow()
+}
+
+@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 0000000..246645e
--- /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/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 63c022c..e2a4714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -38,9 +38,13 @@
import com.android.app.animation.InterpolatorsAndroidX;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
+import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -52,8 +56,15 @@
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
@@ -66,6 +77,7 @@
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -83,6 +95,7 @@
import java.util.concurrent.Executor;
import javax.inject.Inject;
+
import kotlin.Unit;
/**
@@ -128,7 +141,7 @@
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlags;
private final NotificationIconAreaController mNotificationIconAreaController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
@@ -142,6 +155,13 @@
private final DumpManager mDumpManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final NotificationIconContainerViewModel mStatusBarIconsViewModel;
+ private final ConfigurationState mConfigurationState;
+ private final ConfigurationController mConfigurationController;
+ private final DozeParameters mDozeParameters;
+ private final ScreenOffAnimationController mScreenOffAnimationController;
+ private final NotificationIconContainerViewBinder.IconViewStore mStatusBarIconViewStore;
+ private final DemoModeController mDemoModeController;
private List<String> mBlockedIcons = new ArrayList<>();
private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
@@ -211,9 +231,9 @@
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
ShadeExpansionStateManager shadeExpansionStateManager,
- FeatureFlags featureFlags,
+ FeatureFlagsClassic featureFlags,
StatusBarIconController statusBarIconController,
- StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
+ DarkIconManager.Factory darkIconManagerFactory,
CollapsedStatusBarViewModel collapsedStatusBarViewModel,
CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
@@ -228,8 +248,14 @@
@Main Executor mainExecutor,
DumpManager dumpManager,
StatusBarWindowStateController statusBarWindowStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor
- ) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
+ ConfigurationState configurationState,
+ ConfigurationController configurationController,
+ DozeParameters dozeParameters,
+ ScreenOffAnimationController screenOffAnimationController,
+ StatusBarNotificationIconViewStore statusBarIconViewStore,
+ DemoModeController demoModeController) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
@@ -254,18 +280,55 @@
mDumpManager = dumpManager;
mStatusBarWindowStateController = statusBarWindowStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mStatusBarIconsViewModel = statusBarIconsViewModel;
+ mConfigurationState = configurationState;
+ mConfigurationController = configurationController;
+ mDozeParameters = dozeParameters;
+ mScreenOffAnimationController = screenOffAnimationController;
+ mStatusBarIconViewStore = statusBarIconViewStore;
+ mDemoModeController = demoModeController;
}
+ private final DemoMode mDemoModeCallback = new DemoMode() {
+ @Override
+ public List<String> demoCommands() {
+ return List.of(DemoMode.COMMAND_NOTIFICATIONS);
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (mNotificationIconAreaInner == null) return;
+ String visible = args.getString("visible");
+ if ("false".equals(visible)) {
+ mNotificationIconAreaInner.setVisibility(View.INVISIBLE);
+ } else {
+ mNotificationIconAreaInner.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onDemoModeFinished() {
+ if (mNotificationIconAreaInner == null) return;
+ mNotificationIconAreaInner.setVisibility(View.VISIBLE);
+ }
+ };
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mDemoModeController.addCallback(mDemoModeCallback);
+ }
}
@Override
public void onDestroy() {
super.onDestroy();
mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mDemoModeController.removeCallback(mDemoModeCallback);
+ }
}
@Override
@@ -405,14 +468,31 @@
/** Initializes views related to the notification icon area. */
public void initNotificationIconArea() {
- ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
- mNotificationIconAreaInner =
- mNotificationIconAreaController.getNotificationInnerAreaView();
- if (mNotificationIconAreaInner.getParent() != null) {
- ((ViewGroup) mNotificationIconAreaInner.getParent())
- .removeView(mNotificationIconAreaInner);
+ ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mNotificationIconAreaInner =
+ LayoutInflater.from(getContext())
+ .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+ NotificationIconContainer notificationIcons =
+ notificationIconArea.requireViewById(R.id.notificationIcons);
+ NotificationIconContainerViewBinder.bind(
+ notificationIcons,
+ mStatusBarIconsViewModel,
+ mConfigurationState,
+ mConfigurationController,
+ mDozeParameters,
+ mFeatureFlags,
+ mScreenOffAnimationController,
+ mStatusBarIconViewStore);
+ } else {
+ mNotificationIconAreaInner =
+ mNotificationIconAreaController.getNotificationInnerAreaView();
+ if (mNotificationIconAreaInner.getParent() != null) {
+ ((ViewGroup) mNotificationIconAreaInner.getParent())
+ .removeView(mNotificationIconAreaInner);
+ }
+ notificationIconArea.addView(mNotificationIconAreaInner);
}
- notificationIconArea.addView(mNotificationIconAreaInner);
updateNotificationIconAreaAndCallChip(/* animate= */ false);
}
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 21acfb4..0a2bbe5 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,8 @@
*/
package com.android.systemui.statusbar.policy
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import android.content.res.Configuration
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -23,14 +24,47 @@
* @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) }
+ }
+
+/**
+ * A [Flow] that emits whenever the configuration has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onConfigChanged
+ */
+val ConfigurationController.onConfigChanged: Flow<Configuration>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySend(newConfig)
+ }
+ }
+ 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 9269df3..8c66c2f 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 @@
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 @@
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 @@
// 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 @@
}
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 @@
}
}
+ 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 @@
// 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 @@
}
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 0000000..ed960f3
--- /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 ed3eacd..71314f1 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 @@
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.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 @@
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/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 0a44bda..f0c7be6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -4,27 +4,74 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.Tracing
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.TraceUtils.Companion.coroutineTracingIsEnabled
+import com.android.systemui.util.tracing.TraceContextElement
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Qualifier
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+/** Key associated with a [Boolean] flag that enables or disables the coroutine tracing feature. */
+@Qualifier
+annotation class CoroutineTracingEnabledKey
+
+/**
+ * Same as [@Application], but does not make use of flags. This should only be used when early usage
+ * of [@Application] would introduce a circular dependency on [FeatureFlagsClassic].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnflaggedApplication
+
+/**
+ * Same as [@Background], but does not make use of flags. This should only be used when early usage
+ * of [@Application] would introduce a circular dependency on [FeatureFlagsClassic].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnflaggedBackground
/** Providers for various coroutines-related constructs. */
@Module
-object CoroutinesModule {
+class CoroutinesModule {
@Provides
@SysUISingleton
@Application
fun applicationScope(
- @Main dispatcher: CoroutineDispatcher,
- ): CoroutineScope = CoroutineScope(dispatcher)
+ @Main dispatcherContext: CoroutineContext,
+ ): CoroutineScope = CoroutineScope(dispatcherContext)
+
+ @Provides
+ @SysUISingleton
+ @UnflaggedApplication
+ fun unflaggedApplicationScope(): CoroutineScope = CoroutineScope(Dispatchers.Main.immediate)
@Provides
@SysUISingleton
@Main
+ @Deprecated(
+ "Use @Main CoroutineContext instead",
+ ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext")
+ )
fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
+ @Provides
+ @SysUISingleton
+ @Main
+ fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
+ return Dispatchers.Main.immediate + tracingCoroutineContext
+ }
+
/**
* Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where
* X is the number of CPU cores available.
@@ -37,5 +84,42 @@
@Provides
@SysUISingleton
@Background
+ @Deprecated(
+ "Use @Background CoroutineContext instead",
+ ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
+ )
fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
+
+
+ @Provides
+ @Background
+ @SysUISingleton
+ fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
+ return Dispatchers.IO + tracingCoroutineContext
+ }
+
+ @Provides
+ @UnflaggedBackground
+ @SysUISingleton
+ fun unflaggedBackgroundCoroutineContext(): CoroutineContext {
+ return Dispatchers.IO
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Provides
+ @Tracing
+ @SysUISingleton
+ fun tracingCoroutineContext(
+ @CoroutineTracingEnabledKey enableTracing: Boolean
+ ): CoroutineContext = if (enableTracing) TraceContextElement() else EmptyCoroutineContext
+
+ companion object {
+ @[Provides CoroutineTracingEnabledKey]
+ fun provideIsCoroutineTracingEnabledKey(featureFlags: FeatureFlagsClassic): Boolean {
+ return if (featureFlags.isEnabled(Flags.COROUTINE_TRACING)) {
+ coroutineTracingIsEnabled = true
+ true
+ } else false
+ }
+ }
}
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 0000000..909a18be
--- /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 83ff789..8fe57e11 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -61,10 +61,10 @@
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T, R> Flow<T>.pairwiseBy(
- initialValue: T,
- transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> = onStart { emit(initialValue) }.pairwiseBy(transform)
+fun <S, T : S, R> Flow<T>.pairwiseBy(
+ initialValue: S,
+ transform: suspend (previousValue: S, newValue: T) -> R,
+): Flow<R> = pairwiseBy(getInitialValue = { initialValue }, transform)
/**
* Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
@@ -75,10 +75,16 @@
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T, R> Flow<T>.pairwiseBy(
- getInitialValue: suspend () -> T,
- transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> = onStart { emit(getInitialValue()) }.pairwiseBy(transform)
+fun <S, T : S, R> Flow<T>.pairwiseBy(
+ getInitialValue: suspend () -> S,
+ transform: suspend (previousValue: S, newValue: T) -> R,
+): Flow<R> = flow {
+ var previousValue: S = getInitialValue()
+ collect { newVal ->
+ emit(transform(previousValue, newVal))
+ previousValue = newVal
+ }
+}
/**
* Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
@@ -86,7 +92,7 @@
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T> Flow<T>.pairwise(): Flow<WithPrev<T>> = pairwiseBy(::WithPrev)
+fun <T> Flow<T>.pairwise(): Flow<WithPrev<T, T>> = pairwiseBy(::WithPrev)
/**
* Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue] will
@@ -94,10 +100,11 @@
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T> Flow<T>.pairwise(initialValue: T): Flow<WithPrev<T>> = pairwiseBy(initialValue, ::WithPrev)
+fun <S, T : S> Flow<T>.pairwise(initialValue: S): Flow<WithPrev<S, T>> =
+ pairwiseBy(initialValue, ::WithPrev)
/** Holds a [newValue] emitted from a [Flow], along with the [previousValue] emitted value. */
-data class WithPrev<T>(val previousValue: T, val newValue: T)
+data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
/**
* Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
@@ -265,7 +272,127 @@
* immediately invoke [getValue] to establish its initial value.
*/
inline fun <T> CoroutineScope.stateFlow(
- changedSignals: Flow<Unit>,
+ changedSignals: Flow<*>,
crossinline getValue: () -> T,
): StateFlow<T> =
changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
+
+inline fun <T1, T2, T3, T4, T5, T6, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*>
+ ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) {
+ args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) {
+ args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[7] as T8
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ flow9: Flow<T9>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(
+ flow,
+ flow2,
+ flow3,
+ flow4,
+ flow5,
+ flow6,
+ flow7,
+ flow8,
+ flow9
+ ) { args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[6] as T8,
+ args[6] as T9,
+ )
+ }
+}
+
+/**
+ * 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/kotlin/MapUtils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
new file mode 100644
index 0000000..41cd95b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.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.util.kotlin
+
+/** Like [mapValues], but discards `null` values returned from [block]. */
+fun <K, V, R> Map<K, V>.mapValuesNotNull(block: (Map.Entry<K, V>) -> R?): Map<K, R> = buildMap {
+ this@mapValuesNotNull.mapValuesNotNullTo(this, block)
+}
+
+/** Like [mapValuesTo], but discards `null` values returned from [block]. */
+fun <K, V, R, M : MutableMap<in K, in R>> Map<out K, V>.mapValuesNotNullTo(
+ destination: M,
+ block: (Map.Entry<K, V>) -> R?,
+): M {
+ for (entry in this) {
+ block(entry)?.also { destination.put(entry.key, it) }
+ }
+ return destination
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
index 51d2afa..1112d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
@@ -17,26 +17,58 @@
package com.android.systemui.util.ui
+import com.android.systemui.util.ui.AnimatedValue.Animating
+import com.android.systemui.util.ui.AnimatedValue.NotAnimating
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transformLatest
/**
* A state comprised of a [value] of type [T] paired with a boolean indicating whether or not the
* [value] [isAnimating] in the UI.
*/
-data class AnimatedValue<T>(
- val value: T,
- val isAnimating: Boolean,
-)
+sealed interface AnimatedValue<out T> {
+
+ /** A [state][value] that is not actively animating in the UI. */
+ data class NotAnimating<out T>(val value: T) : AnimatedValue<T>
+
+ /**
+ * A [state][value] that is actively animating in the UI. Invoking [onStopAnimating] will signal
+ * the source of the state to stop animating.
+ */
+ data class Animating<out T>(
+ val value: T,
+ val onStopAnimating: () -> Unit,
+ ) : AnimatedValue<T>
+}
+
+/** The state held in this [AnimatedValue]. */
+inline val <T> AnimatedValue<T>.value: T
+ get() =
+ when (this) {
+ is Animating -> value
+ is NotAnimating -> value
+ }
+
+/** Returns whether or not this [AnimatedValue] is animating or not. */
+inline val <T> AnimatedValue<T>.isAnimating: Boolean
+ get() = this is Animating<T>
+
+/**
+ * If this [AnimatedValue] [isAnimating], then signal that the animation should be stopped.
+ * Otherwise, do nothing.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun AnimatedValue<*>.stopAnimating() {
+ if (this is Animating) onStopAnimating()
+}
/**
* An event comprised of a [value] of type [T] paired with a [boolean][startAnimating] indicating
* whether or not this event should start an animation.
*/
-data class AnimatableEvent<T>(
+data class AnimatableEvent<out T>(
val value: T,
val startAnimating: Boolean,
)
@@ -47,16 +79,87 @@
* [AnimatableEvent.startAnimating] value is `true`. When [completionEvents] emits a value, the
* [AnimatedValue.isAnimating] will flip to `false`.
*/
-fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(
- completionEvents: Flow<Any?>,
-): Flow<AnimatedValue<T>> = transformLatest { (value, startAnimating) ->
- emit(AnimatedValue(value, isAnimating = startAnimating))
- if (startAnimating) {
- // Wait for a completion now that we've started animating
- completionEvents
- .map { Unit } // replace the event so that it's never `null`
- .firstOrNull() // `null` indicates an empty flow
- // emit the new state if the flow was not empty.
- ?.run { emit(AnimatedValue(value, isAnimating = false)) }
+fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(): Flow<AnimatedValue<T>> =
+ transformLatest { (value, startAnimating) ->
+ if (startAnimating) {
+ val onCompleted = CompletableDeferred<Unit>()
+ emit(Animating(value) { onCompleted.complete(Unit) })
+ // Wait for a completion now that we've started animating
+ onCompleted.await()
+ }
+ emit(NotAnimating(value))
+ }
+
+/**
+ * Zip two [AnimatedValue]s together into a single [AnimatedValue], using [block] to combine the
+ * [value]s of each.
+ *
+ * If either [AnimatedValue] [isAnimating], then the result is also animating. Invoking
+ * [stopAnimating] on the result is equivalent to invoking [stopAnimating] on each input.
+ */
+inline fun <A, B, Z> zip(
+ valueA: AnimatedValue<A>,
+ valueB: AnimatedValue<B>,
+ block: (A, B) -> Z,
+): AnimatedValue<Z> {
+ val zippedValue = block(valueA.value, valueB.value)
+ return when (valueA) {
+ is Animating ->
+ when (valueB) {
+ is Animating ->
+ Animating(zippedValue) {
+ valueA.onStopAnimating()
+ valueB.onStopAnimating()
+ }
+ is NotAnimating -> Animating(zippedValue, valueA.onStopAnimating)
+ }
+ is NotAnimating ->
+ when (valueB) {
+ is Animating -> Animating(zippedValue, valueB.onStopAnimating)
+ is NotAnimating -> NotAnimating(zippedValue)
+ }
}
}
+
+/**
+ * Flattens a nested [AnimatedValue], the result of which holds the [value] of the inner
+ * [AnimatedValue].
+ *
+ * If either the outer or inner [AnimatedValue] [isAnimating], then the flattened result is also
+ * animating. Invoking [stopAnimating] on the result is equivalent to invoking [stopAnimating] on
+ * both the outer and inner values.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> AnimatedValue<AnimatedValue<T>>.flatten(): AnimatedValue<T> = flatMap { it }
+
+/**
+ * Returns an [AnimatedValue], the [value] of which is the result of the given [value] applied to
+ * [block].
+ */
+inline fun <A, B> AnimatedValue<A>.map(block: (A) -> B): AnimatedValue<B> =
+ when (this) {
+ is Animating -> Animating(block(value), ::stopAnimating)
+ is NotAnimating -> NotAnimating(block(value))
+ }
+
+/**
+ * Returns an [AnimatedValue] from the result of [block] being invoked on the [value] original
+ * [AnimatedValue].
+ *
+ * If either the input [AnimatedValue] or the result of [block] [isAnimating], then the flattened
+ * result is also animating. Invoking [stopAnimating] on the result is equivalent to invoking
+ * [stopAnimating] on both values.
+ */
+inline fun <A, B> AnimatedValue<A>.flatMap(block: (A) -> AnimatedValue<B>): AnimatedValue<B> =
+ when (this) {
+ is NotAnimating -> block(value)
+ is Animating ->
+ when (val inner = block(value)) {
+ is Animating ->
+ Animating(inner.value) {
+ onStopAnimating()
+ inner.onStopAnimating()
+ }
+ is NotAnimating -> Animating(inner.value, onStopAnimating)
+ }
+ }
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 0000000..6d45d23
--- /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 9dca013..fdf59664 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@
private WallpaperManager mWallpaperManager;
private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
private SurfaceHolder mSurfaceHolder;
+ private boolean mDrawn = false;
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@VisibleForTesting
@@ -125,8 +126,6 @@
private int mBitmapUsages = 0;
private final Object mLock = new Object();
- private boolean mIsLockscreenLiveWallpaperEnabled;
-
CanvasEngine() {
super();
setFixedSizeAllowed(true);
@@ -170,12 +169,8 @@
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);
@@ -239,6 +234,7 @@
private void drawFrameSynchronized() {
synchronized (mLock) {
+ if (mDrawn) return;
drawFrameInternal();
}
}
@@ -276,6 +272,7 @@
Rect dest = mSurfaceHolder.getSurfaceFrame();
try {
canvas.drawBitmap(bitmap, null, dest, null);
+ mDrawn = true;
} finally {
surface.unlockCanvasAndPost(canvas);
}
@@ -324,10 +321,8 @@
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!");
@@ -338,18 +333,11 @@
// 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;
@@ -370,9 +358,7 @@
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 ea74510..c4b43e1 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 @@
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 167e341..f49ba64 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -22,7 +22,9 @@
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.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
@@ -40,6 +43,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -72,6 +76,7 @@
@get:Provides val keyguardSecurityModel: KeyguardSecurityModel = mock(),
@get:Provides val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(),
@get:Provides val mediaHierarchyManager: MediaHierarchyManager = mock(),
+ @get:Provides val notifCollection: NotifCollection = mock(),
@get:Provides val notificationListener: NotificationListener = mock(),
@get:Provides val notificationLockscreenUserManager: NotificationLockscreenUserManager = mock(),
@get:Provides val notificationMediaManager: NotificationMediaManager = mock(),
@@ -86,6 +91,9 @@
@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/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 22ebd99..f15164e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.atLeast;
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 android.animation.ValueAnimator;
@@ -33,8 +34,8 @@
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -46,13 +47,15 @@
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -61,22 +64,18 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@LargeTest
@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
private static final float DEFAULT_SCALE = 4.0f;
private static final float DEFAULT_CENTER_X = 400.0f;
private static final float DEFAULT_CENTER_Y = 500.0f;
- // The duration and period couldn't too short, otherwise the ValueAnimator and
- // Instrumentation.runOnMainSync won't work in expectation. (b/288926821)
- private static final long ANIMATION_DURATION_MS = 600;
- private static final long WAIT_FULL_ANIMATION_PERIOD = 1000;
- private static final long WAIT_INTERMEDIATE_ANIMATION_PERIOD = 250;
private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
@@ -105,10 +104,11 @@
private WindowMagnificationController mSpyController;
private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
private Instrumentation mInstrumentation;
- private long mWaitingAnimationPeriod;
- private long mWaitIntermediateAnimationPeriod;
+ private long mWaitAnimationDuration;
+ private long mWaitPartialAnimationDuration;
private TestableWindowManager mWindowManager;
+ private ValueAnimator mValueAnimator;
@Before
public void setUp() throws Exception {
@@ -118,10 +118,14 @@
mWindowManager = spy(new TestableWindowManager(wm));
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- mWaitingAnimationPeriod = WAIT_FULL_ANIMATION_PERIOD;
- mWaitIntermediateAnimationPeriod = WAIT_INTERMEDIATE_ANIMATION_PERIOD;
+ // Using the animation duration in WindowMagnificationAnimationController for testing.
+ mWaitAnimationDuration = mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_longAnimTime);
+ mWaitPartialAnimationDuration = mWaitAnimationDuration / 2;
+
+ mValueAnimator = newValueAnimator();
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
- mContext, newValueAnimator());
+ mContext, mValueAnimator);
mController = new SpyWindowMagnificationController(mContext, mHandler,
mWindowMagnificationAnimationController,
mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
@@ -131,13 +135,13 @@
@After
public void tearDown() throws Exception {
- mInstrumentation.runOnMainSync(() -> mController.deleteWindowMagnification());
+ mController.deleteWindowMagnification();
}
@Test
public void enableWindowMagnification_disabled_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -161,16 +165,13 @@
@Test
public void enableWindowMagnificationWithoutCallback_enabled_expectedValues() {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, null);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
}
@@ -178,12 +179,8 @@
@Test
public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback()
throws RemoteException {
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(1,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ mWindowMagnificationAnimationController.enableWindowMagnification(1,
+ DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
verify(mSpyController).enableWindowMagnificationInternal(1, DEFAULT_CENTER_X,
DEFAULT_CENTER_Y, 0f, 0f);
@@ -193,22 +190,19 @@
@Test
public void enableWindowMagnification_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
-
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -226,24 +220,18 @@
@Test
public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, animationCallback);
- });
+ mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
// The callback in 2nd enableWindowMagnification will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in 1st enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
@@ -256,16 +244,13 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
-
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -293,16 +278,13 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
-
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -331,11 +313,9 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, null);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
assertEquals(WindowMagnificationAnimationController.STATE_DISABLED,
@@ -346,17 +326,16 @@
public void
enableMagnificationWithoutCallback_enabling_expectedValuesAndInvokeFormerCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE - 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, null);
+
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
verify(mAnimationCallback).onResult(false);
}
@@ -364,15 +343,13 @@
@Test
public void enableWindowMagnificationWithSameSpec_enabling_NoAnimationAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, mAnimationCallback2);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -383,28 +360,30 @@
@Test
public void enableWindowMagnification_disabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+
// Current spec shouldn't match given spec.
verify(mAnimationCallback2, never()).onResult(anyBoolean());
verify(mAnimationCallback).onResult(false);
- SystemClock.sleep(mWaitingAnimationPeriod);
+ // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it is
+ // using SystemClock in reverse() (b/305731398). Therefore, we call end() on the animator
+ // directly to verify the result of animation is correct instead of querying the animation
+ // frame at a specific timing.
+ mValueAnimator.end();
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -424,18 +403,15 @@
enableMagnificationWithoutCallback_disabling_expectedValuesAndInvokeFormerCallback()
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, null);
verify(mAnimationCallback).onResult(false);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
@@ -444,16 +420,14 @@
@Test
public void enableWindowMagnificationWithSameSpec_disabling_NoAnimationAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, mAnimationCallback2);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -470,16 +444,13 @@
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
-
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -498,129 +469,110 @@
public void enableWindowMagnificationWithOffset_expectedValues() {
final float offsetRatio = -0.1f;
final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- windowBounds.exactCenterX(), windowBounds.exactCenterY(),
- offsetRatio, offsetRatio, mAnimationCallback);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
- final View attachedView = mWindowManager.getAttachedView();
- assertNotNull(attachedView);
- final Rect mirrorViewBound = new Rect();
- final View mirrorView = attachedView.findViewById(R.id.surface_view);
- assertNotNull(mirrorView);
- mirrorView.getBoundsOnScreen(mirrorViewBound);
- assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2),
- (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
- assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
- (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+ windowBounds.exactCenterX(), windowBounds.exactCenterY(),
+ offsetRatio, offsetRatio, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
+ // We delay the time of verifying to wait for the measurement and layout of the view
+ mHandler.postDelayed(() -> {
+ final View attachedView = mWindowManager.getAttachedView();
+ assertNotNull(attachedView);
+ final Rect mirrorViewBound = new Rect();
+ final View mirrorView = attachedView.findViewById(R.id.surface_view);
+ assertNotNull(mirrorView);
+ mirrorView.getBoundsOnScreen(mirrorViewBound);
+
+ assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2),
+ (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
+ assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
+ (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
+ }, 100);
}
@Test
- public void moveWindowMagnifierToPosition_enabled_expectedValues()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ public void moveWindowMagnifierToPosition_enabled_expectedValues() throws RemoteException {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
enableWindowMagnificationWithoutAnimation();
- mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, animationCallback);
- });
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
- assertEquals(1, animationCallback.getSuccessCount());
- assertEquals(0, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback, never()).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
@Test
public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(4);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback);
- });
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
// only the last one callback will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// the others will return false
- assertEquals(3, animationCallback.getFailedCount());
+ verify(mAnimationCallback, times(3)).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40);
}
@Test
public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, animationCallback);
- });
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+
// The callback in moveWindowMagnifierToPosition will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
@Test
public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- Float.NaN, Float.NaN, animationCallback);
- });
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+
// The callback in moveWindowMagnifierToPosition will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
@Test
public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
+ enableWindowMagnificationWithoutAnimation();
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -632,7 +584,7 @@
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -659,7 +611,7 @@
@Test
public void deleteWindowMagnification_disabled_doNothingAndInvokeCallback()
throws RemoteException {
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
Mockito.verifyNoMoreInteractions(mSpyController);
verify(mAnimationCallback).onResult(true);
@@ -668,20 +620,23 @@
@Test
public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.deleteWindowMagnification(
- mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.deleteWindowMagnification(
+ mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it is
+ // using SystemClock in reverse() (b/305731398). Therefore, we call end() on the animator
+ // directly to verify the result of animation is correct instead of querying the animation
+ // frame at a specific timing.
+ mValueAnimator.end();
+
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -702,14 +657,11 @@
@Test
public void deleteWindowMagnificationWithoutCallback_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.deleteWindowMagnification(null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.deleteWindowMagnification(null);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
verify(mAnimationCallback).onResult(false);
@@ -717,13 +669,13 @@
@Test
public void deleteWindowMagnification_disabling_checkStartAndValues() throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback2);
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -738,8 +690,8 @@
@Test
public void deleteWindowMagnificationWithoutCallback_disabling_checkStartAndValues()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
deleteWindowMagnificationAndWaitAnimating(0, null);
@@ -757,9 +709,9 @@
final float offsetX = 50.0f;
final float offsetY =
(float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
- + 1.0f;
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifier(offsetX, offsetY));
+ + 1.0f;
+
+ mController.moveWindowMagnifier(offsetX, offsetY);
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y + offsetY);
@@ -774,8 +726,8 @@
final float offsetY =
(float) Math.floor(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
- 1.0f;
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifier(offsetX, offsetY));
+
+ mController.moveWindowMagnifier(offsetX, offsetY);
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y);
@@ -790,11 +742,8 @@
(float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE);
// while diagonal scrolling enabled,
// should move with both offsetX and offsetY without regrading offsetY/offsetX
- mInstrumentation.runOnMainSync(
- () -> {
- mController.setDiagonalScrolling(true);
- mController.moveWindowMagnifier(offsetX, offsetY);
- });
+ mController.setDiagonalScrolling(true);
+ mController.moveWindowMagnifier(offsetX, offsetY);
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y + offsetY);
@@ -806,14 +755,17 @@
final float targetCenterY = DEFAULT_CENTER_Y + 100;
enableWindowMagnificationWithoutAnimation();
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
- mAnimationCallback));
- SystemClock.sleep(mWaitingAnimationPeriod);
+ mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
+ mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
+ private void advanceTimeBy(long timeDelta) {
+ mAnimatorTestRule.advanceTimeBy(timeDelta);
+ }
+
private void verifyFinalSpec(float expectedScale, float expectedCenterX,
float expectedCenterY) {
assertEquals(expectedScale, mController.getScale(), 0f);
@@ -822,33 +774,24 @@
}
private void enableWindowMagnificationWithoutAnimation() {
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+ DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null);
}
private void enableWindowMagnificationAndWaitAnimating(long duration,
@Nullable IRemoteMagnificationAnimationCallback callback) {
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
- });
- SystemClock.sleep(duration);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+ DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
+ advanceTimeBy(duration);
}
private void deleteWindowMagnificationAndWaitAnimating(long duration,
@Nullable IRemoteMagnificationAnimationCallback callback) {
- mInstrumentation.runOnMainSync(
- () -> {
- resetMockObjects();
- mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
- });
- SystemClock.sleep(duration);
+ resetMockObjects();
+ mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
+ advanceTimeBy(duration);
}
private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
@@ -937,9 +880,9 @@
}
}
- private static ValueAnimator newValueAnimator() {
+ private ValueAnimator newValueAnimator() {
final ValueAnimator valueAnimator = new ValueAnimator();
- valueAnimator.setDuration(ANIMATION_DURATION_MS);
+ valueAnimator.setDuration(mWaitAnimationDuration);
valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
valueAnimator.setFloatValues(0.0f, 1.0f);
return valueAnimator;
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 df7d1b7..aed795a 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 @@
@Test
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
- final float menuTop = IME_TOP + 100;
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+ mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
+ final PointF beforePosition = mMenuView.getMenuPosition();
dispatchShowingImeInsets();
final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
- assertThat(mMenuView.getTranslationX()).isEqualTo(0);
- assertThat(menuBottom).isLessThan(IME_TOP);
+ assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
+ assertThat(menuBottom).isLessThan(beforePosition.y);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
deleted file mode 100644
index 215d635..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.hardware.biometrics.SensorProperties
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.view.ViewGroup.LayoutParams
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenEver
-import org.mockito.junit.MockitoJUnit
-
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() {
-
- @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
- @Mock private lateinit var iconView: LottieAnimationView
- @Mock private lateinit var iconViewOverlay: LottieAnimationView
- @Mock private lateinit var layoutParam: LayoutParams
- @Mock private lateinit var fingerprintManager: FingerprintManager
-
- private lateinit var controller: AuthBiometricFingerprintIconController
-
- @Before
- fun setUp() {
- context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
- whenEver(iconView.layoutParams).thenReturn(layoutParam)
- whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam)
- }
-
- @Test
- fun testIconContentDescription_SfpsDevice() {
- setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON)
- controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
- assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
- .isEqualTo(
- context.resources.getString(
- R.string.security_settings_sfps_enroll_find_sensor_message
- )
- )
- }
-
- @Test
- fun testIconContentDescription_NonSfpsDevice() {
- setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
- controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
- assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
- .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor))
- }
-
- private fun setupFingerprintSensorProperties(sensorType: Int) {
- whenEver(fingerprintManager.sensorPropertiesInternal)
- .thenReturn(
- listOf(
- FingerprintSensorPropertiesInternal(
- SENSOR_ID,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- listOf() /* componentInfo */,
- sensorType,
- true /* halControlsIllumination */,
- true /* resetLockoutRequiresHardwareAuthToken */,
- listOf() /* sensorLocations */
- )
- )
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index d68a313..8c26776 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -72,6 +72,7 @@
runCurrent()
// WHEN shade expands
+ shadeRepository.setLegacyShadeTracking(true)
shadeRepository.setLegacyShadeExpansion(.5f)
runCurrent()
@@ -108,6 +109,7 @@
// WHEN detector is disabled and shade opens
detector.disable()
+ shadeRepository.setLegacyShadeTracking(true)
shadeRepository.setLegacyShadeExpansion(.5f)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 9f24a9f..15633d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -30,6 +30,7 @@
internal fun fingerprintSensorPropertiesInternal(
ids: List<Int> = listOf(0),
strong: Boolean = true,
+ sensorType: Int = FingerprintSensorProperties.TYPE_REAR
): List<FingerprintSensorPropertiesInternal> {
val componentInfo =
listOf(
@@ -54,7 +55,7 @@
if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
5 /* maxEnrollmentsPerUser */,
componentInfo,
- FingerprintSensorProperties.TYPE_REAR,
+ sensorType,
false /* resetLockoutRequiresHardwareAuthToken */
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
deleted file mode 100644
index fd86486..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.content.res.Configuration
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class PromptFingerprintIconViewModelTest : SysuiTestCase() {
-
- @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
- @Mock private lateinit var lockPatternUtils: LockPatternUtils
-
- private lateinit var displayRepository: FakeDisplayRepository
- private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
- private lateinit var promptRepository: FakePromptRepository
- private lateinit var displayStateRepository: FakeDisplayStateRepository
-
- private val testScope = TestScope(StandardTestDispatcher())
- private val fakeExecutor = FakeExecutor(FakeSystemClock())
-
- private lateinit var promptSelectorInteractor: PromptSelectorInteractor
- private lateinit var displayStateInteractor: DisplayStateInteractor
- private lateinit var viewModel: PromptFingerprintIconViewModel
-
- @Before
- fun setup() {
- displayRepository = FakeDisplayRepository()
- fingerprintRepository = FakeFingerprintPropertyRepository()
- promptRepository = FakePromptRepository()
- displayStateRepository = FakeDisplayStateRepository()
-
- promptSelectorInteractor =
- PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
- displayStateInteractor =
- DisplayStateInteractorImpl(
- testScope.backgroundScope,
- mContext,
- fakeExecutor,
- displayStateRepository,
- displayRepository,
- )
- viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
- }
-
- @Test
- fun sfpsIconUpdates_onConfigurationChanged() {
- testScope.runTest {
- runCurrent()
- configureFingerprintPropertyRepository(FingerprintSensorType.POWER_BUTTON)
- val testConfig = Configuration()
- val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
- val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
- val currentIcon = collectLastValue(viewModel.iconAsset)
-
- testConfig.smallestScreenWidthDp = folded
- viewModel.onConfigurationChanged(testConfig)
- val foldedIcon = currentIcon()
-
- testConfig.smallestScreenWidthDp = unfolded
- viewModel.onConfigurationChanged(testConfig)
- val unfoldedIcon = currentIcon()
-
- assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
- }
- }
-
- private fun configureFingerprintPropertyRepository(sensorType: FingerprintSensorType) {
- fingerprintRepository.setProperties(0, SensorStrength.STRONG, sensorType, mapOf())
- }
-}
-
-internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index ca6df40..b695a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.content.res.Configuration
import android.hardware.biometrics.PromptInfo
import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -36,12 +38,15 @@
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -66,6 +71,7 @@
private const val USER_ID = 4
private const val CHALLENGE = 2L
+private const val DELAY = 1000L
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -88,11 +94,22 @@
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
+ private lateinit var iconViewModel: PromptIconViewModel
private val featureFlags = FakeFeatureFlags()
@Before
fun setup() {
fingerprintRepository = FakeFingerprintPropertyRepository()
+ testCase.fingerprint?.let {
+ fingerprintRepository.setProperties(
+ it.sensorId,
+ it.sensorStrength.toSensorStrength(),
+ it.sensorType.toSensorType(),
+ it.allLocations.associateBy { sensorLocationInternal ->
+ sensorLocationInternal.displayId
+ }
+ )
+ }
promptRepository = FakePromptRepository()
displayStateRepository = FakeDisplayStateRepository()
displayRepository = FakeDisplayRepository()
@@ -110,6 +127,7 @@
viewModel =
PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+ iconViewModel = viewModel.iconViewModel
featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@@ -123,7 +141,6 @@
val modalities by collectLastValue(viewModel.modalities)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
assertThat(authenticating).isFalse()
assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -133,7 +150,6 @@
}
assertThat(message).isEqualTo(PromptMessage.Empty)
assertThat(size).isEqualTo(expectedSize)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_IDLE)
val startMessage = "here we go"
viewModel.showAuthenticating(startMessage, isRetry = false)
@@ -143,7 +159,6 @@
assertThat(authenticated?.isNotAuthenticated).isTrue()
assertThat(size).isEqualTo(expectedSize)
assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
}
@Test
@@ -205,6 +220,472 @@
assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
}
+ @Test
+ fun start_idle_and_show_authenticating_iconUpdate() =
+ runGenericTest(doNotStart = true) {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+ if (forceExplicitFlow) {
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+
+ val startMessage = "here we go"
+ viewModel.showAuthenticating(startMessage, isRetry = false)
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(false)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+ val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+ val expectedIconAsset =
+ if (shouldPulseAnimation!!) {
+ if (lastPulseLightToDark!!) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ } else {
+ R.drawable.face_dialog_pulse_dark_to_light
+ }
+ assertThat(iconAsset).isEqualTo(expectedIconAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(true)
+ }
+
+ if (testCase.isCoex) {
+ if (testCase.confirmationRequested || forceExplicitFlow) {
+ // explicit flow
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(false)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ } else {
+ // implicit flow
+ val shouldRepeatAnimation by
+ collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+ val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+ val expectedIconAsset =
+ if (shouldPulseAnimation!!) {
+ if (lastPulseLightToDark!!) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ } else {
+ R.drawable.face_dialog_pulse_dark_to_light
+ }
+ assertThat(iconAsset).isEqualTo(expectedIconAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(true)
+ }
+ }
+ }
+
+ @Test
+ fun start_authenticating_show_and_clear_error_iconUpdate() = runGenericTest {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+ if (forceExplicitFlow) {
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+
+ val errorJob = launch {
+ viewModel.showTemporaryError(
+ "so sad",
+ messageAfterError = "",
+ authenticateAfterError = testCase.isFingerprintOnly || testCase.isCoex,
+ )
+ // Usually done by binder
+ iconViewModel.setPreviousIconWasError(true)
+ iconViewModel.setPreviousIconOverlayWasError(true)
+ }
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+
+ // Clear error, restart authenticating
+ errorJob.join()
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+
+ // Clear error, go to idle
+ errorJob.join()
+
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_error_to_idle)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+
+ // Clear error, restart authenticating
+ errorJob.join()
+
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun shows_authenticated_no_errors_no_confirmation_required_iconUpdate() = runGenericTest {
+ if (!testCase.confirmationRequested) {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ val isAuthenticated by collectLastValue(viewModel.isAuthenticated)
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ // If co-ex, using implicit flow (explicit flow always requires confirmation)
+ if (testCase.isFaceOnly || testCase.isCoex) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun shows_pending_confirmation_iconUpdate() = runGenericTest {
+ if (
+ (testCase.isFaceOnly || testCase.isCoex) &&
+ testCase.authenticatedByFace &&
+ testCase.confirmationRequested
+ ) {
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ // explicit flow because confirmation requested
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest {
+ if (
+ (testCase.isFaceOnly || testCase.isCoex) &&
+ testCase.authenticatedByFace &&
+ testCase.confirmationRequested
+ ) {
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ viewModel.confirmAuthenticated()
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ // explicit flow because confirmation requested
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onConfigurationChanged() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val testConfig = Configuration()
+ val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
+ val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ testConfig.smallestScreenWidthDp = folded
+ iconViewModel.onConfigurationChanged(testConfig)
+ val foldedIcon = currentIcon
+
+ testConfig.smallestScreenWidthDp = unfolded
+ iconViewModel.onConfigurationChanged(testConfig)
+ val unfoldedIcon = currentIcon
+
+ assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onRotation() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+ val iconRotation0 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+ val iconRotation90 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ val iconRotation180 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ val iconRotation270 = currentIcon
+
+ assertThat(iconRotation0).isEqualTo(iconRotation180)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ displayStateRepository.setIsInRearDisplayMode(false)
+ val iconNotRearDisplayMode = currentIcon
+
+ displayStateRepository.setIsInRearDisplayMode(true)
+ val iconRearDisplayMode = currentIcon
+
+ assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+ }
+ }
+
private suspend fun TestScope.showAuthenticated(
authenticatedModality: BiometricModality,
expectConfirmation: Boolean,
@@ -213,7 +694,6 @@
val authenticated by collectLastValue(viewModel.isAuthenticated)
val fpStartMode by collectLastValue(viewModel.fingerprintStartMode)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val authWithSmallPrompt =
testCase.shouldStartAsImplicitFlow &&
@@ -221,14 +701,12 @@
assertThat(authenticating).isTrue()
assertThat(authenticated?.isNotAuthenticated).isTrue()
assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
assertButtonsVisible(negative = !authWithSmallPrompt)
- val delay = 1000L
- viewModel.showAuthenticated(authenticatedModality, delay)
+ viewModel.showAuthenticated(authenticatedModality, DELAY)
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(authenticated?.delay).isEqualTo(delay)
+ assertThat(authenticated?.delay).isEqualTo(DELAY)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
assertThat(size)
.isEqualTo(
@@ -238,14 +716,7 @@
PromptSize.SMALL
}
)
- assertThat(legacyState)
- .isEqualTo(
- if (expectConfirmation) {
- BiometricState.STATE_PENDING_CONFIRMATION
- } else {
- BiometricState.STATE_AUTHENTICATED
- }
- )
+
assertButtonsVisible(
cancel = expectConfirmation,
confirm = expectConfirmation,
@@ -298,7 +769,6 @@
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow)
val errorJob = launch {
@@ -312,7 +782,6 @@
assertThat(size).isEqualTo(PromptSize.MEDIUM)
assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
assertThat(messageVisible).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_ERROR)
// temporary error should disappear after a delay
errorJob.join()
@@ -323,17 +792,6 @@
assertThat(message).isEqualTo(PromptMessage.Empty)
assertThat(messageVisible).isFalse()
}
- val clearIconError = !restart
- assertThat(legacyState)
- .isEqualTo(
- if (restart) {
- BiometricState.STATE_AUTHENTICATING
- } else if (clearIconError) {
- BiometricState.STATE_IDLE
- } else {
- BiometricState.STATE_HELP
- }
- )
assertThat(authenticating).isEqualTo(restart)
assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -488,7 +946,6 @@
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -506,7 +963,6 @@
assertThat(authenticating).isFalse()
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
assertThat(canTryAgain).isFalse()
}
@@ -524,7 +980,6 @@
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticating).isFalse()
@@ -532,8 +987,6 @@
assertThat(authenticated?.isAuthenticated).isTrue()
if (testCase.isFaceOnly && expectConfirmation) {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-
assertThat(size).isEqualTo(PromptSize.MEDIUM)
assertButtonsVisible(
cancel = true,
@@ -543,8 +996,6 @@
viewModel.confirmAuthenticated()
assertThat(message).isEqualTo(PromptMessage.Empty)
assertButtonsVisible()
- } else {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
}
}
@@ -563,7 +1014,6 @@
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -581,7 +1031,6 @@
assertThat(authenticating).isFalse()
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
assertThat(canTryAgain).isFalse()
}
@@ -610,12 +1059,10 @@
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_HELP)
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
@@ -632,7 +1079,6 @@
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
if (testCase.isCoex && testCase.authenticatedByFingerprint) {
@@ -642,11 +1088,7 @@
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- if (confirmationRequired == true) {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
- } else {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
- }
+
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
assertThat(authenticating).isFalse()
@@ -785,6 +1227,15 @@
authenticatedModality = BiometricModality.Fingerprint,
),
TestCase(
+ fingerprint =
+ fingerprintSensorPropertiesInternal(
+ strong = true,
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ )
+ .first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ ),
+ TestCase(
face = faceSensorPropertiesInternal(strong = true).first(),
authenticatedModality = BiometricModality.Face,
confirmationRequested = true,
@@ -794,6 +1245,16 @@
authenticatedModality = BiometricModality.Fingerprint,
confirmationRequested = true,
),
+ TestCase(
+ fingerprint =
+ fingerprintSensorPropertiesInternal(
+ strong = true,
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ )
+ .first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ confirmationRequested = true,
+ ),
)
private val coexTestCases =
@@ -834,7 +1295,9 @@
val modality =
when {
fingerprint != null && face != null -> "coex"
- fingerprint != null -> "fingerprint only"
+ fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps"
+ fingerprint != null && !fingerprint.isAnySidefpsType ->
+ "fingerprint only, non-sideFps"
face != null -> "face only"
else -> "?"
}
@@ -864,6 +1327,8 @@
val isCoex: Boolean
get() = face != null && fingerprint != null
+ @FingerprintSensorProperties.SensorType val sensorType: Int? = fingerprint?.sensorType
+
val shouldStartAsImplicitFlow: Boolean
get() = (isFaceOnly || isCoex) && !confirmationRequested
}
@@ -890,3 +1355,5 @@
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
)
}
+
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index 9373ada..f6b284f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -111,6 +111,27 @@
}
@Test
+ fun show_nullDelegate() {
+ testScope.run {
+ whenever(bouncerView.delegate).thenReturn(null)
+ mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+ // WHEN bouncer show is requested
+ underTest.show(true)
+
+ // WHEN all queued messages are dispatched
+ mainHandler.dispatchQueuedMessages()
+
+ // THEN primary bouncer state doesn't update to show since delegate was null
+ verify(repository, never()).setPrimaryShow(true)
+ verify(repository, never()).setPrimaryShowingSoon(false)
+ verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+ verify(mPrimaryBouncerCallbackInteractor, never())
+ .dispatchVisibilityChanged(View.VISIBLE)
+ }
+ }
+
+ @Test
fun testShow_isScrimmed() {
underTest.show(true)
verify(repository).setKeyguardAuthenticatedBiometrics(null)
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 80b281e..882bcab 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 @@
}
@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/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 91409a3..fcb191b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -12,9 +12,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.FakeLogBuffer
@@ -38,6 +39,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -58,10 +60,16 @@
@Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var featureFlags: FeatureFlagsClassic
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
+ @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
+
+ @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+
+ @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
+
private lateinit var communalRepository: FakeCommunalRepository
private lateinit var logBuffer: LogBuffer
@@ -70,29 +78,34 @@
private val testScope = TestScope(testDispatcher)
+ private val fakeAllowlist =
+ listOf(
+ "com.android.fake/WidgetProviderA",
+ "com.android.fake/WidgetProviderB",
+ "com.android.fake/WidgetProviderC",
+ )
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
logBuffer = FakeLogBuffer.Factory.create()
-
- featureFlagEnabled(true)
communalRepository = FakeCommunalRepository()
- communalRepository.setIsCommunalEnabled(true)
- overrideResource(
- R.array.config_communalWidgetAllowlist,
- arrayOf(componentName1, componentName2)
- )
+ communalEnabled(true)
+ widgetOnKeyguardEnabled(true)
+ setAppWidgetIds(emptyList())
+
+ overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
whenever(userTracker.userHandle).thenReturn(userHandle)
}
@Test
- fun broadcastReceiver_featureDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+ fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
testScope.runTest {
- featureFlagEnabled(false)
+ communalEnabled(false)
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
verifyBroadcastReceiverNeverRegistered()
@@ -129,7 +142,7 @@
job.cancel()
runCurrent()
- Mockito.verify(broadcastDispatcher).unregisterReceiver(receiver)
+ verify(broadcastDispatcher).unregisterReceiver(receiver)
}
@Test
@@ -166,7 +179,7 @@
installedProviders(listOf(stopwatchProviderInfo))
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).allocateAppWidgetId()
+ verify(appWidgetHost).allocateAppWidgetId()
}
@Test
@@ -185,8 +198,8 @@
// Verify app widget id allocated
assertThat(lastStopwatchProviderInfo()?.appWidgetId).isEqualTo(123456)
- Mockito.verify(appWidgetHost).allocateAppWidgetId()
- Mockito.verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
+ verify(appWidgetHost).allocateAppWidgetId()
+ verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
// User locked again
userUnlocked(false)
@@ -194,7 +207,7 @@
// Verify app widget id deleted
assertThat(lastStopwatchProviderInfo()).isNull()
- Mockito.verify(appWidgetHost).deleteAppWidgetId(123456)
+ verify(appWidgetHost).deleteAppWidgetId(123456)
}
@Test
@@ -203,13 +216,13 @@
userUnlocked(false)
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost, Mockito.never()).startListening()
+ verify(appWidgetHost, Mockito.never()).startListening()
userUnlocked(true)
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).startListening()
+ verify(appWidgetHost).startListening()
}
@Test
@@ -223,14 +236,14 @@
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).startListening()
- Mockito.verify(appWidgetHost, Mockito.never()).stopListening()
+ verify(appWidgetHost).startListening()
+ verify(appWidgetHost, Mockito.never()).stopListening()
userUnlocked(false)
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).stopListening()
+ verify(appWidgetHost).stopListening()
}
@Test
@@ -241,21 +254,80 @@
assertThat(
listOf(
CommunalWidgetMetadata(
- componentName = componentName1,
- priority = 2,
- sizes = listOf(CommunalContentSize.HALF)
+ componentName = fakeAllowlist[0],
+ priority = 3,
+ sizes = listOf(CommunalContentSize.HALF),
),
CommunalWidgetMetadata(
- componentName = componentName2,
+ componentName = fakeAllowlist[1],
+ priority = 2,
+ sizes = listOf(CommunalContentSize.HALF),
+ ),
+ CommunalWidgetMetadata(
+ componentName = fakeAllowlist[2],
priority = 1,
- sizes = listOf(CommunalContentSize.HALF)
- )
+ sizes = listOf(CommunalContentSize.HALF),
+ ),
)
)
.containsExactly(*communalWidgetAllowlist.toTypedArray())
}
}
+ // This behavior is temporary before the local database is set up.
+ @Test
+ fun communalWidgets_withPreviouslyBoundWidgets_removeEachBinding() =
+ testScope.runTest {
+ whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(1, 2, 3)
+ setAppWidgetIds(listOf(1, 2, 3))
+ whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
+ userUnlocked(true)
+
+ val repository = initCommunalWidgetRepository()
+
+ collectLastValue(repository.communalWidgets)()
+
+ verify(appWidgetHost).deleteAppWidgetId(1)
+ verify(appWidgetHost).deleteAppWidgetId(2)
+ verify(appWidgetHost).deleteAppWidgetId(3)
+ }
+
+ @Test
+ fun communalWidgets_allowlistNotEmpty_bindEachWidgetFromTheAllowlist() =
+ testScope.runTest {
+ whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(0, 1, 2)
+ userUnlocked(true)
+
+ whenever(appWidgetManager.getAppWidgetInfo(0)).thenReturn(providerInfoA)
+ whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfoB)
+ whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfoC)
+
+ val repository = initCommunalWidgetRepository()
+
+ val inventory by collectLastValue(repository.communalWidgets)
+
+ assertThat(
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 0,
+ providerInfo = providerInfoA,
+ priority = 3,
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ providerInfo = providerInfoB,
+ priority = 2,
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 2,
+ providerInfo = providerInfoC,
+ priority = 1,
+ ),
+ )
+ )
+ .containsExactly(*inventory!!.toTypedArray())
+ }
+
private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
return CommunalWidgetRepositoryImpl(
context,
@@ -272,7 +344,7 @@
}
private fun verifyBroadcastReceiverRegistered() {
- Mockito.verify(broadcastDispatcher)
+ verify(broadcastDispatcher)
.registerReceiver(
any(),
any(),
@@ -284,7 +356,7 @@
}
private fun verifyBroadcastReceiverNeverRegistered() {
- Mockito.verify(broadcastDispatcher, Mockito.never())
+ verify(broadcastDispatcher, Mockito.never())
.registerReceiver(
any(),
any(),
@@ -297,7 +369,7 @@
private fun broadcastReceiverUpdate(): BroadcastReceiver {
val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
- Mockito.verify(broadcastDispatcher)
+ verify(broadcastDispatcher)
.registerReceiver(
broadcastReceiverCaptor.capture(),
any(),
@@ -310,7 +382,11 @@
return broadcastReceiverCaptor.value
}
- private fun featureFlagEnabled(enabled: Boolean) {
+ private fun communalEnabled(enabled: Boolean) {
+ communalRepository.setIsCommunalEnabled(enabled)
+ }
+
+ private fun widgetOnKeyguardEnabled(enabled: Boolean) {
whenever(featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)).thenReturn(enabled)
}
@@ -322,8 +398,7 @@
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
- companion object {
- const val componentName1 = "component name 1"
- const val componentName2 = "component name 2"
+ private fun setAppWidgetIds(ids: List<Int>) {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cdc42e0..8e21f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -22,7 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
index 0e14591..52c6e22 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 @@
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 @@
@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 0000000..db6f85f
--- /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 647b05a..7d7abab 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 @@
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 @@
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 f7a773e..1f04828 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 @@
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 @@
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 bb6786a..e16b8d4 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 @@
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 7a13a0a..489665c 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 @@
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/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 819d08a..9bb2434 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -43,7 +43,6 @@
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -82,6 +81,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.FakeKeyguardStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
@@ -94,6 +94,8 @@
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
@@ -116,8 +118,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.StringWriter
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -195,9 +195,11 @@
}
powerRepository = FakePowerRepository()
- powerInteractor = PowerInteractorFactory.create(
- repository = powerRepository,
- ).powerInteractor
+ powerInteractor =
+ PowerInteractorFactory.create(
+ repository = powerRepository,
+ )
+ .powerInteractor
val withDeps =
KeyguardInteractorFactory.create(
@@ -210,10 +212,10 @@
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- keyguardInteractor = keyguardInteractor,
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = keyguardTransitionRepository,
+ keyguardInteractor = keyguardInteractor,
)
.keyguardTransitionInteractor
@@ -635,11 +637,7 @@
@Test
fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
- testScope.runTest {
- testGatingCheckForFaceAuth {
- powerInteractor.setAsleepForTest()
- }
- }
+ testScope.runTest { testGatingCheckForFaceAuth { powerInteractor.setAsleepForTest() } }
@Test
fun authenticateDoesNotRunWhenSecureCameraIsActive() =
@@ -736,17 +734,21 @@
allPreconditionsToRunFaceAuthAreTrue()
Log.i("TEST", "started waking")
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OFF,
transitionState = TransitionState.FINISHED,
- ))
+ )
+ )
runCurrent()
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
transitionState = TransitionState.STARTED,
- ))
+ )
+ )
runCurrent()
Log.i("TEST", "sending display off")
@@ -766,11 +768,13 @@
testScope.runTest {
testGatingCheckForFaceAuth {
powerInteractor.onFinishedWakingUp()
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
transitionState = TransitionState.FINISHED,
- ))
+ )
+ )
runCurrent()
displayRepository.emit(
@@ -919,11 +923,7 @@
@Test
fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
- testScope.runTest {
- testGatingCheckForDetect {
- powerInteractor.setAsleepForTest()
- }
- }
+ testScope.runTest { testGatingCheckForDetect { powerInteractor.setAsleepForTest() } }
@Test
fun detectDoesNotRunWhenSecureCameraIsActive() =
@@ -1114,6 +1114,32 @@
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
faceAuthenticateIsCalled()
}
+ @Test
+ fun authFailedCallAfterAuthLockedOutErrorShouldBeIgnored() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+ runCurrent()
+ assertThat(canFaceAuthRun()).isTrue()
+
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false)
+ runCurrent()
+
+ faceAuthenticateIsCalled()
+ authenticationCallback.value.onAuthenticationError(
+ FACE_ERROR_LOCKOUT_PERMANENT,
+ "Too many attempts, face not available"
+ )
+
+ val lockoutError = authStatus() as ErrorFaceAuthenticationStatus
+ assertThat(lockedOut()).isTrue()
+ assertThat(lockoutError.isLockoutError()).isTrue()
+
+ authenticationCallback.value.onAuthenticationFailed()
+ runCurrent()
+
+ assertThat(authStatus()).isEqualTo(lockoutError)
+ }
private suspend fun TestScope.testGatingCheckForFaceAuth(
gatingCheckModifier: suspend () -> Unit
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 5afc405..ad80a06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRunner
@@ -86,7 +87,7 @@
}
@Test
- fun startingSecondTransitionWillCancelTheFirstTransition() =
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseLastValue() =
TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
@@ -100,12 +101,19 @@
val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
runner.startTransition(
this,
- TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()),
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.LAST_VALUE
+ ),
)
val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+ // Second transition starts from .1 (LAST_VALUE)
val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1))
assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
@@ -114,6 +122,76 @@
}
@Test
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReset() =
+ TestScope().runTest {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 3,
+ )
+
+ // Now start 2nd transition, which will interrupt the first
+ val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.RESET
+ ),
+ )
+
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+ assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+ // Second transition starts from 0 (RESET)
+ val secondTransitionSteps = listWithStep(start = BigDecimal(0), step = BigDecimal(.1))
+ assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+ job.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReverse() =
+ TestScope().runTest {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 3,
+ )
+
+ // Now start 2nd transition, which will interrupt the first
+ val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.REVERSE
+ ),
+ )
+
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+ assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+ // Second transition starts from .9 (REVERSE)
+ val secondTransitionSteps = listWithStep(start = BigDecimal(0.9), step = BigDecimal(.1))
+ assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+ job.cancel()
+ job2.cancel()
+ }
+
+ @Test
fun nullAnimatorEnablesManualControlWithUpdateTransition() =
TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index f3930a3..275ac80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -57,7 +57,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
@@ -244,7 +243,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -271,7 +270,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -298,7 +297,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -328,7 +327,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -359,7 +358,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -386,7 +385,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -413,7 +412,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -446,7 +445,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to Lockscreen should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -474,7 +473,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to Gone should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -504,7 +503,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -537,7 +536,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -569,7 +568,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -593,7 +592,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -625,7 +624,7 @@
advanceUntilIdle()
// THEN the transition is ignored
- verify(transitionRepository, never()).startTransition(any(), anyBoolean())
+ verify(transitionRepository, never()).startTransition(any())
coroutineContext.cancelChildren()
}
@@ -642,7 +641,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -669,7 +668,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -696,7 +695,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -719,7 +718,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -749,7 +748,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -780,7 +779,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -806,7 +805,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -838,7 +837,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -871,7 +870,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -901,7 +900,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -929,7 +928,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -957,7 +956,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -981,7 +980,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1011,7 +1010,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1042,7 +1041,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to GONE should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1071,7 +1070,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1096,7 +1095,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AlternateBouncer should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1121,7 +1120,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AlternateBouncer should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1147,7 +1146,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1172,7 +1171,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -1199,7 +1198,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
@@ -1223,7 +1222,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -1253,7 +1252,7 @@
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
@@ -1287,7 +1286,7 @@
// THEN a transition from LOCKSCREEN => OCCLUDED should occur
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1318,7 +1317,7 @@
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1339,7 +1338,7 @@
// THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
val info2 =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
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 0000000..1ff46db
--- /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 d7802aa..6cab023 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.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.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 @@
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 @@
primaryBouncerInteractor,
keyguardDismissActionInteractor,
featureFlags,
+ bouncerToGoneFlows,
)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
@@ -148,59 +153,6 @@
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/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 54fc493..1abb441 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -42,7 +42,7 @@
private var frameCount = 1L
private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
private var job: Job? = null
- private var isTerminated = false
+ @Volatile private var isTerminated = false
/**
* For transitions being directed by an animator. Will control the number of frames being
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 0000000..fd1e2c7
--- /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 6cdf4ef..5255f71 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 @@
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 @@
callerPackageName,
thumbnailLoader,
isFirstStart,
- logger
+ logger,
+ hostUid,
)
@Before
@@ -212,20 +213,22 @@
@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/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 43cf1b5..ae47a7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -37,7 +37,6 @@
import android.view.IWindowManager
import android.view.View
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.view.LaunchableFrameLayout
@@ -48,6 +47,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.res.R
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -343,8 +343,7 @@
testableLooper.processAllMessages()
verify(activityStarter, never())
- .startPendingIntentDismissingKeyguard(
- any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
}
@Test
@@ -357,8 +356,7 @@
testableLooper.processAllMessages()
verify(activityStarter, never())
- .startPendingIntentDismissingKeyguard(
- any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
}
@Test
@@ -373,7 +371,7 @@
testableLooper.processAllMessages()
verify(activityStarter)
- .startPendingIntentDismissingKeyguard(
+ .startPendingIntentMaybeDismissingKeyguard(
eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 0552ced..0e4b113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -124,11 +124,44 @@
}
@Test
- fun testLongClickIntent() {
+ fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ val cameraTile = CameraToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ cameraTile.destroy()
+ testableLooper.processAllMessages()
+ }
+ @Test
+ fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+ val cameraTile = CameraToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ cameraTile.destroy()
+ testableLooper.processAllMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index 0fcfdb6..b98a7570 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -123,11 +123,44 @@
}
@Test
- fun testLongClickIntent() {
+ fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ val micTile = MicrophoneToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ micTile.destroy()
+ testableLooper.processAllMessages()
+ }
+ @Test
+ fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ val micTile = MicrophoneToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ micTile.destroy()
+ testableLooper.processAllMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 5b3d45b..c6d156f 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
@@ -22,11 +22,13 @@
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Dialog;
import android.os.Handler;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -35,11 +37,11 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -48,7 +50,9 @@
import com.android.systemui.qs.logging.QSLogger;
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;
@@ -89,6 +93,12 @@
private PanelInteractor mPanelInteractor;
@Mock
private QsEventLogger mUiEventLogger;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ @Mock
+ private Dialog mPermissionDialogPrompt;
+ @Mock
+ private UserContextProvider mUserContextProvider;
private TestableLooper mTestableLooper;
private ScreenRecordTile mTile;
@@ -99,6 +109,7 @@
mTestableLooper = TestableLooper.get(this);
+ when(mUserContextProvider.getUserContext()).thenReturn(mContext);
when(mHost.getContext()).thenReturn(mContext);
mTile = new ScreenRecordTile(
@@ -116,7 +127,9 @@
mKeyguardDismissUtil,
mKeyguardStateController,
mDialogLaunchAnimator,
- mPanelInteractor
+ mPanelInteractor,
+ mMediaProjectionMetricsLogger,
+ mUserContextProvider
);
mTile.initialize();
@@ -280,4 +293,28 @@
assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
}
+ @Test
+ public void showingDialogPrompt_logsMediaProjectionPermissionRequested() {
+ when(mController.isStarting()).thenReturn(false);
+ when(mController.isRecording()).thenReturn(false);
+ when(mController.createScreenRecordDialog(any(), any(), any(), any(), any()))
+ .thenReturn(mPermissionDialogPrompt);
+
+ mTile.handleClick(null /* view */);
+ mTestableLooper.processAllMessages();
+
+ verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
+ eq(mDialogLaunchAnimator), eq(mActivityStarter), any());
+ var onDismissAction = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction.class);
+ verify(mKeyguardDismissUtil).executeWhenUnlocked(
+ onDismissAction.capture(), anyBoolean(), anyBoolean());
+ assertNotNull(onDismissAction.getValue());
+
+ onDismissAction.getValue().onDismiss();
+
+ verify(mPermissionDialogPrompt).show();
+ 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
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index 5659f01..95ee3b7 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 @@
@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 f1fcee3..31d02ed 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
@@ -23,11 +23,13 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dump.LogcatEchoTrackerAlways
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
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -42,6 +44,7 @@
class QSTileLoggerTest : SysuiTestCase() {
@Mock private lateinit var statusBarController: StatusBarStateController
+ @Mock private lateinit var logBufferFactory: LogBufferFactory
private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways())
private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways())
@@ -51,10 +54,11 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer)
underTest =
QSTileLogger(
mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
- { logBuffer },
+ logBufferFactory,
statusBarController
)
}
@@ -139,7 +143,6 @@
fun testLogStateUpdate() {
underTest.logStateUpdate(
TileSpec.create("test_spec"),
- StateUpdateTrigger.ForceUpdate,
QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
"test_data",
)
@@ -147,21 +150,36 @@
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 2084aeb..9b85012 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.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 @@
@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 @@
assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
assertThat(fakeQSTileDataInteractor.dataRequests.first())
- .isEqualTo(QSTileDataRequest(1, StateUpdateTrigger.InitialRequest))
+ .isEqualTo(FakeQSTileDataInteractor.DataRequest(1))
}
private fun createViewModel(
@@ -102,9 +103,11 @@
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 90d2e78..c439cfe 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 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 @@
*/
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 @@
@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 @@
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 @@
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 da4dc85..bf12d7d 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 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.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 @@
ScreenRecordPermissionDialog(
context,
UserHandle.of(0),
+ TEST_HOST_UID,
controller,
starter,
userContextProvider,
@@ -105,6 +112,19 @@
}
@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 @@
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 4f3216d..4e3e165 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,6 +21,7 @@
import android.testing.TestableLooper.RunWithLooper
import android.view.KeyEvent
import android.view.MotionEvent
+import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardMessageAreaController
@@ -43,11 +44,20 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import com.android.systemui.flags.Flags.MIGRATE_NSSL
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -69,7 +79,8 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -83,6 +94,7 @@
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -97,6 +109,7 @@
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.util.Optional
@@ -135,6 +148,8 @@
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock private lateinit var mCommunalViewModel: CommunalViewModel
+ private lateinit var mCommunalRepository: FakeCommunalRepository
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -149,7 +164,9 @@
@Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- private val notificationExpansionRepository = NotificationExpansionRepository()
+ private val notificationLaunchAnimationRepository = NotificationLaunchAnimationRepository()
+ private val notificationLaunchAnimationInteractor =
+ NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -159,7 +176,7 @@
private lateinit var testScope: TestScope
- private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
@Before
fun setUp() {
@@ -174,14 +191,16 @@
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
.thenReturn(emptyFlow<TransitionStep>())
- featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
- featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
- featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
- featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
- featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
- featureFlags.set(Flags.MIGRATE_NSSL, false)
- featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false)
+ featureFlagsClassic = FakeFeatureFlagsClassic()
+ featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
+ featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
+ featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+ featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
+ featureFlagsClassic.set(MIGRATE_NSSL, false)
+ featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
+
+ mCommunalRepository = FakeCommunalRepository()
testScope = TestScope()
fakeClock = FakeSystemClock()
@@ -216,14 +235,16 @@
mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
- notificationExpansionRepository,
- featureFlags,
+ mCommunalViewModel,
+ mCommunalRepository,
+ notificationLaunchAnimationInteractor,
+ featureFlagsClassic,
fakeClock,
BouncerMessageInteractor(
repository = BouncerMessageRepositoryImpl(),
userRepository = FakeUserRepository(),
countDownTimerUtil = mock(CountDownTimerUtil::class.java),
- featureFlags = featureFlags,
+ featureFlags = featureFlagsClassic,
updateMonitor = mock(KeyguardUpdateMonitor::class.java),
biometricSettingsRepository = FakeBiometricSettingsRepository(),
applicationScope = testScope.backgroundScope,
@@ -443,7 +464,7 @@
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
.thenReturn(true)
- featureFlags.set(Flags.MIGRATE_NSSL, true)
+ featureFlagsClassic.set(MIGRATE_NSSL, true)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
@@ -460,7 +481,7 @@
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
.thenReturn(false)
- featureFlags.set(Flags.MIGRATE_NSSL, true)
+ featureFlagsClassic.set(MIGRATE_NSSL, true)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
@@ -474,6 +495,48 @@
}
@Test
+ fun setsUpCommunalHubLayout_whenFlagEnabled() {
+ if (!isComposeAvailable()) {
+ return
+ }
+
+ mCommunalRepository.setIsCommunalEnabled(true)
+
+ val mockCommunalPlaceholder = mock(View::class.java)
+ val fakeViewIndex = 20
+ whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+ whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+ whenever(view.context).thenReturn(context)
+
+ underTest.setupCommunalHubLayout()
+
+ // Communal view added as a child of the container at the proper index, the stub is removed.
+ verify(view).removeView(mockCommunalPlaceholder)
+ verify(view).addView(any(), eq(fakeViewIndex))
+ }
+
+ @Test
+ fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
+ if (!isComposeAvailable()) {
+ return
+ }
+
+ mCommunalRepository.setIsCommunalEnabled(false)
+
+ val mockCommunalPlaceholder = mock(View::class.java)
+ val fakeViewIndex = 20
+ whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+ whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+ whenever(view.context).thenReturn(context)
+
+ underTest.setupCommunalHubLayout()
+
+ // No adding or removing of views occurs.
+ verify(view, times(0)).removeView(mockCommunalPlaceholder)
+ verify(view, times(0)).addView(any(), eq(fakeViewIndex))
+ }
+
+ @Test
fun forwardsDispatchKeyEvent() {
val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
interactionEventHandler.dispatchKeyEvent(keyEvent)
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 4d3eab4..3d5d26a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -42,6 +42,8 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
@@ -67,7 +69,8 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -142,6 +145,8 @@
private lateinit var unfoldTransitionProgressProvider:
Optional<UnfoldTransitionProgressProvider>
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock private lateinit var mCommunalViewModel: CommunalViewModel
+ private lateinit var mCommunalRepository: FakeCommunalRepository
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -178,6 +183,8 @@
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
.thenReturn(emptyFlow())
+ mCommunalRepository = FakeCommunalRepository()
+
val featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
@@ -218,7 +225,9 @@
Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
- NotificationExpansionRepository(),
+ mCommunalViewModel,
+ mCommunalRepository,
+ NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
featureFlags,
FakeSystemClock(),
BouncerMessageInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 81382a4..3a260ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -445,105 +445,6 @@
}
@Test
- fun expanding_shadeDraggedDown_expandingTrue() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade and QS collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade partially expanded
- shadeRepository.setLegacyShadeExpansion(.5f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
- }
-
- @Test
- fun expanding_qsDraggedDown_expandingTrue() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade and QS collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade partially expanded
- shadeRepository.setQsExpansion(.5f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
- }
-
- @Test
- fun expanding_shadeDraggedUpAndDown() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // WHEN shade starts collapsed then partially expanded
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setLegacyShadeExpansion(.5f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
-
- // WHEN shade dragged up a bit
- shadeRepository.setLegacyShadeExpansion(.2f)
- runCurrent()
-
- // THEN anyExpanding is still true
- assertThat(actual).isTrue()
-
- // WHEN shade dragged down a bit
- shadeRepository.setLegacyShadeExpansion(.7f)
- runCurrent()
-
- // THEN anyExpanding is still true
- assertThat(actual).isTrue()
-
- // WHEN shade fully expanded
- shadeRepository.setLegacyShadeExpansion(1f)
- runCurrent()
-
- // THEN anyExpanding is now false
- assertThat(actual).isFalse()
-
- // WHEN shade dragged up a bit
- shadeRepository.setLegacyShadeExpansion(.7f)
- runCurrent()
-
- // THEN anyExpanding is still false
- assertThat(actual).isFalse()
- }
-
- @Test
- fun expanding_shadeDraggedDownThenUp_expandingFalse() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade starts collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade expands but doesn't complete
- shadeRepository.setLegacyShadeExpansion(.5f)
- runCurrent()
- shadeRepository.setLegacyShadeExpansion(0f)
- runCurrent()
-
- // THEN anyExpanding is false
- assertThat(actual).isFalse()
- }
-
- @Test
fun lockscreenShadeExpansion_idle_onScene() =
testScope.runTest() {
// GIVEN an expansion flow based on transitions to and from a scene
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 3412679..2b3fd34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -37,8 +37,12 @@
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.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
+import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -68,9 +72,14 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
+ FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
+ featureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
mListener = new NotificationListener(
mContext,
+ featureFlags,
mNotificationManager,
+ new SilentNotificationStatusIconsVisibilityInteractor(
+ new NotificationListenerSettingsRepository()),
mFakeSystemClock,
mFakeExecutor,
mPluginManager);
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 a5f5fc7..43adc69 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.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 @@
@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 @@
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 cfcf425..0000000
--- 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 8397702..df8afde 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 @@
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 4fcccf8..fee8b82 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.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 @@
}
@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 @@
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/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index 235ac5c..6059363 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -11,7 +11,8 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -44,7 +45,8 @@
private lateinit var notificationTestHelper: NotificationTestHelper
private lateinit var notification: ExpandableNotificationRow
private lateinit var controller: NotificationLaunchAnimatorController
- private val notificationExpansionRepository = NotificationExpansionRepository()
+ private val notificationLaunchAnimationInteractor =
+ NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository())
private val testScope = TestScope()
@@ -57,16 +59,17 @@
fun setUp() {
allowTestableLooperAsMainThread()
notificationTestHelper =
- NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
notification = notificationTestHelper.createRow()
- controller = NotificationLaunchAnimatorController(
- notificationExpansionRepository,
+ controller =
+ NotificationLaunchAnimatorController(
+ notificationLaunchAnimationInteractor,
notificationListContainer,
headsUpManager,
notification,
jankMonitor,
onFinishAnimationCallback
- )
+ )
}
private fun flagNotificationAsHun() {
@@ -80,13 +83,14 @@
assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
assertFalse(notification.entry.isExpandAnimationRunning)
- val isExpandAnimationRunning by testScope.collectLastValue(
- notificationExpansionRepository.isExpandAnimationRunning
- )
+ val isExpandAnimationRunning by
+ testScope.collectLastValue(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+ )
assertFalse(isExpandAnimationRunning!!)
- verify(headsUpManager).removeNotification(
- notificationKey, true /* releaseImmediately */, true /* animate */)
+ verify(headsUpManager)
+ .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */)
verify(onFinishAnimationCallback).run()
}
@@ -97,13 +101,14 @@
assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
assertFalse(notification.entry.isExpandAnimationRunning)
- val isExpandAnimationRunning by testScope.collectLastValue(
- notificationExpansionRepository.isExpandAnimationRunning
- )
+ val isExpandAnimationRunning by
+ testScope.collectLastValue(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+ )
assertFalse(isExpandAnimationRunning!!)
- verify(headsUpManager).removeNotification(
- notificationKey, true /* releaseImmediately */, true /* animate */)
+ verify(headsUpManager)
+ .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */)
verify(onFinishAnimationCallback).run()
}
@@ -114,13 +119,14 @@
assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
assertFalse(notification.entry.isExpandAnimationRunning)
- val isExpandAnimationRunning by testScope.collectLastValue(
- notificationExpansionRepository.isExpandAnimationRunning
- )
+ val isExpandAnimationRunning by
+ testScope.collectLastValue(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+ )
assertFalse(isExpandAnimationRunning!!)
- verify(headsUpManager).removeNotification(
- notificationKey, true /* releaseImmediately */, false /* animate */)
+ verify(headsUpManager)
+ .removeNotification(notificationKey, true /* releaseImmediately */, false /* animate */)
verify(onFinishAnimationCallback).run()
}
@@ -128,15 +134,21 @@
fun testAlertingSummaryHunRemovedOnNonAlertingChildLaunch() {
val GROUP_KEY = "test_group_key"
- val summary = NotificationEntryBuilder().setGroup(mContext, GROUP_KEY).setId(0).apply {
- modifyNotification(mContext).setSmallIcon(R.drawable.ic_person)
- }.build()
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, GROUP_KEY)
+ .setId(0)
+ .apply { modifyNotification(mContext).setSmallIcon(R.drawable.ic_person) }
+ .build()
assertNotSame(summary.key, notification.entry.key)
notificationTestHelper.createRow(summary)
- GroupEntryBuilder().setKey(GROUP_KEY).setSummary(summary).addChild(notification.entry)
- .build()
+ GroupEntryBuilder()
+ .setKey(GROUP_KEY)
+ .setSummary(summary)
+ .addChild(notification.entry)
+ .build()
assertSame(summary, notification.entry.parent?.summary)
`when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false)
@@ -147,10 +159,14 @@
controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
- verify(headsUpManager).removeNotification(
- summary.key, true /* releaseImmediately */, false /* animate */)
- verify(headsUpManager, never()).removeNotification(
- notification.entry.key, true /* releaseImmediately */, false /* animate */)
+ verify(headsUpManager)
+ .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */)
+ verify(headsUpManager, never())
+ .removeNotification(
+ notification.entry.key,
+ true /* releaseImmediately */,
+ false /* animate */
+ )
}
@Test
@@ -158,9 +174,10 @@
controller.onIntentStarted(willAnimate = true)
assertTrue(notification.entry.isExpandAnimationRunning)
- val isExpandAnimationRunning by testScope.collectLastValue(
- notificationExpansionRepository.isExpandAnimationRunning
- )
+ val isExpandAnimationRunning by
+ testScope.collectLastValue(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+ )
assertTrue(isExpandAnimationRunning!!)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index c664c39..2ef4374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,10 +18,16 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.test.suitebuilder.annotation.SmallTest;
@@ -33,8 +39,8 @@
import android.view.animation.Interpolator;
import com.android.app.animation.Interpolators;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -85,7 +91,7 @@
return mEffectiveProperty;
}
};
- private AnimatorListenerAdapter mFinishListener = mock(AnimatorListenerAdapter.class);
+ private AnimatorListenerAdapter mFinishListener;
private AnimationProperties mAnimationProperties = new AnimationProperties() {
@Override
public AnimationFilter getAnimationFilter() {
@@ -104,6 +110,7 @@
@Before
public void setUp() {
mView = new View(getContext());
+ mFinishListener = mock(AnimatorListenerAdapter.class);
}
@Test
@@ -229,6 +236,32 @@
}
@Test
+ public void testListenerCallbackOrderAndTagState() {
+ mAnimationFilter.reset();
+ mAnimationFilter.animate(mProperty.getProperty());
+ mAnimationProperties.setCustomInterpolator(mEffectiveProperty, mTestInterpolator);
+ mAnimationProperties.setDuration(500);
+
+ // Validates that the onAnimationEnd function set by PropertyAnimator was run first.
+ doAnswer(invocation -> {
+ assertNull(mView.getTag(mProperty.getAnimatorTag()));
+ return null;
+ })
+ .when(mFinishListener)
+ .onAnimationEnd(any(Animator.class), anyBoolean());
+
+ // Begin the animation and verify it set state correctly
+ PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
+ ValueAnimator animator = ViewState.getChildTag(mView, mProperty.getAnimatorTag());
+ assertNotNull(animator);
+ assertNotNull(mView.getTag(mProperty.getAnimatorTag()));
+
+ // Terminate the animation to run end runners, and validate they executed.
+ animator.end();
+ verify(mFinishListener).onAnimationEnd(animator, false);
+ }
+
+ @Test
public void testIsAnimating() {
mAnimationFilter.reset();
mAnimationFilter.animate(mProperty.getProperty());
@@ -236,4 +269,4 @@
PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 546abd4..6c1f537 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.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 @@
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 @@
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 @@
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
fakeSettings,
- notificationListInteractor,
+ seenNotificationsInteractor,
statusBarStateController,
)
keyguardCoordinator.attach(notifPipeline)
@@ -618,7 +619,7 @@
KeyguardCoordinatorTestScope(
keyguardCoordinator,
testScope,
- notificationListInteractor,
+ seenNotificationsInteractor,
fakeSettings,
)
.testBlock()
@@ -628,7 +629,7 @@
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 655bd72..a736182 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.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.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.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 @@
@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 @@
@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/data/repository/NotificationExpansionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt
deleted file mode 100644
index f28d9ab..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt
+++ /dev/null
@@ -1,57 +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.data.repository
-
-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.test.runTest
-import org.junit.Test
-
-@SmallTest
-class NotificationExpansionRepositoryTest : SysuiTestCase() {
- private val underTest = NotificationExpansionRepository()
-
- @Test
- fun setIsExpandAnimationRunning_startsAsFalse() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun setIsExpandAnimationRunning_false_emitsTrue() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
-
- underTest.setIsExpandAnimationRunning(true)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun setIsExpandAnimationRunning_false_emitsFalse() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
- underTest.setIsExpandAnimationRunning(true)
-
- // WHEN the animation is no longer running
- underTest.setIsExpandAnimationRunning(false)
-
- // THEN the flow emits false
- assertThat(latest).isFalse()
- }
-}
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 0000000..683d0aa
--- /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/NotificationLaunchAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
new file mode 100644
index 0000000..a0faab5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.data.repository.NotificationLaunchAnimationRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class NotificationLaunchAnimationInteractorTest : SysuiTestCase() {
+ private val repository = NotificationLaunchAnimationRepository()
+ private val underTest = NotificationLaunchAnimationInteractor(repository)
+
+ @Test
+ fun setIsLaunchAnimationRunning_startsAsFalse() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun setIsLaunchAnimationRunning_false_emitsTrue() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+
+ underTest.setIsLaunchAnimationRunning(true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun setIsLaunchAnimationRunning_false_emitsFalse() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+ underTest.setIsLaunchAnimationRunning(true)
+
+ // WHEN the animation is no longer running
+ underTest.setIsLaunchAnimationRunning(false)
+
+ // THEN the flow emits false
+ assertThat(latest).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 fe49016..0000000
--- 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 0000000..ca8ea4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -0,0 +1,49 @@
+/*
+ * 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,
+ sectionStyleProvider = mock(),
+ )
+
+ @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 0000000..2a3c1a5
--- /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/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index f72142f..cc87d7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -22,8 +22,15 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,6 +38,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
@@ -44,9 +52,13 @@
FooterView mView;
+ Context mSpyContext = spy(mContext);
+
@Before
public void setUp() {
- mView = (FooterView) LayoutInflater.from(mContext).inflate(
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
+
+ mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
R.layout.status_bar_notification_footer, null, false);
mView.setDuration(0);
}
@@ -102,6 +114,37 @@
}
@Test
+ public void testSetMessageString_resourceOnlyFetchedOnce() {
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+ verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.unlock_prompt_footer))
+ .getText().toString()).contains("Unlock");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ public void testSetMessageIcon_resourceOnlyFetchedOnce() {
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+ verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed));
+
+ clearInvocations(mSpyContext);
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+
+ verify(mSpyContext, never()).getDrawable(anyInt());
+ }
+
+ @Test
public void testSetFooterLabelVisible() {
mView.setFooterLabelVisible(true);
assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
new file mode 100644
index 0000000..57a7c3c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.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.footer.ui.viewmodel
+
+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.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+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 FooterViewModelTest : SysuiTestCase() {
+ private val repository = ActiveNotificationListRepository()
+ private val interactor = SeenNotificationsInteractor(repository)
+ private val underTest = FooterViewModel(interactor)
+
+ @Test
+ fun testMessageVisible_whenFilteredNotifications() = runTest {
+ val message by collectLastValue(underTest.message)
+
+ repository.hasFilteredOutSeenNotifications.value = true
+
+ assertThat(message?.visible).isTrue()
+ }
+
+ @Test
+ fun testMessageVisible_whenNoFilteredNotifications() = runTest {
+ val message by collectLastValue(underTest.message)
+
+ repository.hasFilteredOutSeenNotifications.value = false
+
+ assertThat(message?.visible).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
new file mode 100644
index 0000000..ec80e5f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -0,0 +1,418 @@
+/*
+ * 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.icon.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.shared.activeNotificationModel
+import com.android.systemui.statusbar.notification.shared.byIsAmbient
+import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
+import com.android.systemui.statusbar.notification.shared.byIsPulsing
+import com.android.systemui.statusbar.notification.shared.byIsRowDismissed
+import com.android.systemui.statusbar.notification.shared.byIsSilent
+import com.android.systemui.statusbar.notification.shared.byIsSuppressedFromStatusBar
+import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: NotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet())
+ assertThat(filteredSet).containsExactlyElementsIn(testIcons)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.filteredNotifSet())
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showAmbient = false))
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noLowPriority() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showLowPriority = false))
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showDismissed = false))
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showRepliedMessages = false))
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsFullyHidden() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: AlwaysOnDisplayNotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val deviceEntryRepository: FakeDeviceEntryRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerAlwaysOnDisplayNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsNotFullyHidden_bypassDisabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(false)
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassDisabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(false)
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsNotFullyHidden_bypassEnabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(true)
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassEnabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(true)
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: StatusBarNotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerStatusBarNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noLowPriority_whenDontShowSilentIcons() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ notificationListenerSettingsRepository.showSilentStatusIcons.value = false
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showLowPriority_whenShowSilentIcons() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ notificationListenerSettingsRepository.showSilentStatusIcons.value = true
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+}
+
+private val testIcons =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ ),
+ activeNotificationModel(
+ key = "notif2",
+ isAmbient = true,
+ ),
+ activeNotificationModel(
+ key = "notif3",
+ isRowDismissed = true,
+ ),
+ activeNotificationModel(
+ key = "notif4",
+ isSilent = true,
+ ),
+ activeNotificationModel(
+ key = "notif5",
+ isLastMessageFromReply = true,
+ ),
+ activeNotificationModel(
+ key = "notif6",
+ isSuppressedFromStatusBar = true,
+ ),
+ activeNotificationModel(
+ key = "notif7",
+ isPulsing = true,
+ ),
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
deleted file mode 100644
index e57986d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
+++ /dev/null
@@ -1,106 +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.icon.ui.viewbinder
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.SysUITestModule
-import com.android.TestMocksModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
-
- @Mock private lateinit var dozeParams: DozeParameters
-
- private lateinit var testComponent: TestComponent
- private val underTest
- get() = testComponent.underTest
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- allowTestableLooperAsMainThread()
-
- testComponent =
- DaggerNotificationIconAreaControllerViewBinderWrapperImplTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(Flags.FACE_AUTH_REFACTOR, value = false)
- set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, value = false)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParams,
- ),
- )
- }
-
- @Test
- fun testNotificationIcons_settingHideIcons() {
- underTest.settingsListener.onStatusBarIconsBehaviorChanged(true)
- assertFalse(underTest.shouldShowLowPriorityIcons())
- }
-
- @Test
- fun testNotificationIcons_settingShowIcons() {
- underTest.settingsListener.onStatusBarIconsBehaviorChanged(false)
- assertTrue(underTest.shouldShowLowPriorityIcons())
- }
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent {
-
- val underTest: NotificationIconAreaControllerViewBinderWrapperImpl
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- featureFlags: FakeFeatureFlagsClassicModule,
- ): TestComponent
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 31efebb..41c7071 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -44,7 +44,9 @@
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.user.domain.UserDomainLayerModule
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -243,6 +245,7 @@
)
)
val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
@@ -266,6 +269,7 @@
fun isDozing_startAodTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.GONE,
@@ -274,13 +278,15 @@
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isDozing_startDozeTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.GONE,
@@ -289,13 +295,15 @@
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isFalse()
}
@Test
fun isDozing_startDozeToAodTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.DOZING,
@@ -304,13 +312,15 @@
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isNotDozing_startAodToGoneTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.AOD,
@@ -319,13 +329,15 @@
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(false, isAnimating = true))
+ assertThat(isDozing?.value).isFalse()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isDozing_stopAnimation() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.AOD,
@@ -335,7 +347,8 @@
)
runCurrent()
- underTest.completeDozeAnimation()
+ assertThat(isDozing?.isAnimating).isEqualTo(true)
+ isDozing?.stopAnimating()
runCurrent()
assertThat(isDozing?.isAnimating).isEqualTo(false)
@@ -345,6 +358,7 @@
fun isNotVisible_pulseExpanding() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(true)
runCurrent()
@@ -355,6 +369,7 @@
fun isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
to = KeyguardState.GONE,
@@ -364,13 +379,15 @@
whenever(screenOffAnimController.shouldShowAodIconsWhenShade()).thenReturn(false)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(false, isAnimating = false))
+ assertThat(isVisible?.value).isFalse()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
fun isVisible_bypassEnabled() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
deviceEntryRepository.setBypassEnabled(true)
runCurrent()
@@ -381,6 +398,7 @@
fun isNotVisible_pulseExpanding_notBypassing() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(true)
deviceEntryRepository.setBypassEnabled(false)
runCurrent()
@@ -398,26 +416,30 @@
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
}
@Test
fun isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(false)
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
fun isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(true)
@@ -425,7 +447,8 @@
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
@@ -440,13 +463,15 @@
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
}
@Test
fun isVisible_stopAnimation() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(true)
@@ -454,7 +479,8 @@
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- underTest.completeVisibilityAnimation()
+ assertThat(isVisible?.isAnimating).isEqualTo(true)
+ isVisible?.stopAnimating()
runCurrent()
assertThat(isVisible?.isAnimating).isEqualTo(false)
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 e1e7f92..ba68fbb 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,8 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
+import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.SysUITestModule
@@ -34,13 +36,23 @@
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.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
+import com.android.systemui.statusbar.notification.shared.activeNotificationModel
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.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -61,18 +73,6 @@
@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 +82,6 @@
DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
.create(
test = this,
- // Configurable bindings
featureFlags =
FakeFeatureFlagsClassicModule {
set(Flags.FACE_AUTH_REFACTOR, value = false)
@@ -93,155 +92,299 @@
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,
)
- )
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- to = DozeStateModel.DOZE_AOD,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ to = DozeStateModel.DOZE_AOD,
+ )
+ )
+ 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,
)
- )
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- to = DozeStateModel.DOZE_PULSING,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ to = DozeStateModel.DOZE_PULSING,
+ )
+ )
+ 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,
)
- )
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ 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()
+ }
}
@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,
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ 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()
+ assertThat(animationsEnabled).isFalse()
- keyguardRepository.setKeyguardShowing(false)
- runCurrent()
+ keyguardRepository.setKeyguardShowing(false)
+ runCurrent()
- assertThat(animationsEnabled).isTrue()
+ 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)
+
+ val staticDrawableColor = iconColors.staticDrawableColor(Rect(), isColorized = true)
+
+ assertThat(staticDrawableColor).isEqualTo(0xAABBCC)
+ }
+ }
+
+ @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()
+ }
+ }
+
+ @Test
+ fun isolatedIcon_animateOnAppear_shadeCollapsed() =
+ with(testComponent) {
+ scope.runTest {
+ val icon: Icon = mock()
+ shadeRepository.setLegacyShadeExpansion(0f)
+ activeNotificationsRepository.activeNotifications.value =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
+ )
+ .associateBy { it.key }
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
+
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ runCurrent()
+
+ assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+ assertThat(isolatedIcon?.isAnimating).isTrue()
+ }
+ }
+
+ @Test
+ fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() =
+ with(testComponent) {
+ scope.runTest {
+ val icon: Icon = mock()
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ activeNotificationsRepository.activeNotifications.value =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
+ )
+ .associateBy { it.key }
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
+
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ runCurrent()
+
+ assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+ assertThat(isolatedIcon?.isAnimating).isFalse()
+ }
}
@SysUISingleton
@@ -249,7 +392,6 @@
modules =
[
SysUITestModule::class,
- // Real impls
BiometricsDomainLayerModule::class,
UserDomainLayerModule::class,
]
@@ -258,10 +400,14 @@
val underTest: NotificationIconContainerStatusBarViewModel
+ val activeNotificationsRepository: ActiveNotificationListRepository
+ val darkIconRepository: FakeDarkIconRepository
val deviceProvisioningRepository: FakeDeviceProvisioningRepository
+ val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository
val keyguardTransitionRepository: FakeKeyguardTransitionRepository
val keyguardRepository: FakeKeyguardRepository
val powerRepository: FakePowerRepository
+ val shadeRepository: FakeShadeRepository
val scope: TestScope
@Component.Factory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
index a0f5048..4bb28ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -50,8 +50,8 @@
@Test
fun testViewWalker_plainNotification_withPublicView() {
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888))
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL)
val row =
testHelper.createRow(
@@ -122,9 +122,9 @@
@Test
fun testViewWalker_bigPictureNotification() {
- val bigPicture = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888))
+ val bigPicture = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888))
val row =
testHelper.createRow(
Notification.Builder(mContext)
@@ -182,8 +182,8 @@
@Test
fun testViewWalker_customView() {
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
val views = RemoteViews(mContext.packageName, R.layout.custom_view_dark)
views.setImageViewBitmap(R.id.custom_view_dark_image, bitmap)
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 23ae26c..1bb7b61 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.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.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 @@
@Before
fun setUp() {
allowTestableLooperAsMainThread()
+ overrideMaxImageSizes()
iconManager =
BigPictureIconManager(
context,
@@ -430,6 +432,17 @@
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 0000000..ca105f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
@@ -0,0 +1,67 @@
+/*
+ * 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 android.graphics.drawable.Icon
+import com.google.common.truth.Correspondence
+
+val byKey: Correspondence<ActiveNotificationModel, String> =
+ Correspondence.transforming({ it?.key }, "has a key of")
+val byIsAmbient: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isAmbient }, "has an isAmbient value of")
+val byIsSuppressedFromStatusBar: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming(
+ { it?.isSuppressedFromStatusBar },
+ "has an isSuppressedFromStatusBar value of",
+ )
+val byIsSilent: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isSilent }, "has an isSilent value of")
+val byIsRowDismissed: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isRowDismissed }, "has an isRowDismissed value of")
+val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming(
+ { it?.isLastMessageFromReply },
+ "has an isLastMessageFromReply value of"
+ )
+val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of")
+
+fun activeNotificationModel(
+ key: String,
+ groupKey: String? = null,
+ isAmbient: Boolean = false,
+ isRowDismissed: Boolean = false,
+ isSilent: Boolean = false,
+ isLastMessageFromReply: Boolean = false,
+ isSuppressedFromStatusBar: Boolean = false,
+ isPulsing: Boolean = false,
+ aodIcon: Icon? = null,
+ shelfIcon: Icon? = null,
+ statusBarIcon: Icon? = null,
+) =
+ ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
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 20197e3..3dafb23 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.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 @@
@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 @@
@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 @@
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
- mNotificationListInteractor,
+ mSeenNotificationsInteractor,
mShadeController,
mJankMonitor,
mStackLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 033c96a..8f36d4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -19,6 +19,8 @@
import static android.view.View.GONE;
import static android.view.WindowInsets.Type.ime;
+import static com.android.systemui.Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -165,6 +167,11 @@
mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR);
mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION);
mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
+ // Some tests in this file test the FooterView. Since we're refactoring the FooterView
+ // business logic out of the NSSL, the behavior tested in this file will eventually be
+ // tested directly in the new FooterView stack. For now, we just want to make sure that the
+ // old behavior is preserved when the flag is off.
+ setFlagDefault(mSetFlagsRule, FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
// Inject dependencies before initializing the layout
mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 49906dc..08ef477 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -62,6 +62,10 @@
)
private val testableResources = mContext.getOrCreateTestableResources()
+ private val maxPanelHeight =
+ mContext.resources.displayMetrics.heightPixels -
+ px(R.dimen.notification_panel_margin_top) -
+ px(R.dimen.notification_panel_margin_bottom)
private fun px(@DimenRes id: Int): Float =
testableResources.resources.getDimensionPixelSize(id).toFloat()
@@ -147,7 +151,7 @@
stackScrollAlgorithm.initView(context)
hostView.removeAllViews()
hostView.addView(emptyShadeView)
- ambientState.layoutMaxHeight = 1280
+ ambientState.layoutMaxHeight = maxPanelHeight.toInt()
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
@@ -155,7 +159,7 @@
context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
- assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
+ assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY)
}
@Test
@@ -356,7 +360,7 @@
whenever(notificationRow.canViewBeCleared()).thenReturn(false)
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.addView(footerView)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
@@ -370,7 +374,7 @@
whenever(notificationRow.canViewBeCleared()).thenReturn(true)
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.addView(footerView)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
@@ -382,7 +386,7 @@
fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() {
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.removeAllViews() // remove all rows
hostView.addView(footerView)
@@ -1006,7 +1010,7 @@
}
private fun resetViewStates_stackMargin_changesHunYTranslation() {
- val stackTopMargin = 50
+ val stackTopMargin = bigGap.toInt() // a gap smaller than the headsUpInset
val headsUpTranslationY = stackScrollAlgorithm.mHeadsUpInset - stackTopMargin
// we need the shelf to mock the real-life behaviour of StackScrollAlgorithm#updateChild
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 68f2728..7de05ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -19,11 +19,14 @@
import android.os.RemoteException
import android.os.UserHandle
import android.testing.AndroidTestingRunner
+import android.view.View
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchableView
import com.android.systemui.assist.AssistManager
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -41,6 +44,7 @@
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -49,6 +53,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.mock
@@ -116,16 +121,51 @@
@Test
fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
val pendingIntent = mock(PendingIntent::class.java)
+ whenever(pendingIntent.isActivity).thenReturn(true)
whenever(keyguardStateController.isShowing).thenReturn(true)
whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
underTest.startPendingIntentDismissingKeyguard(pendingIntent)
+ mainExecutor.runAllReady()
verify(statusBarKeyguardViewManager)
.dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
}
@Test
+ fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityLaunchAnimator.Controller.fromView(view)
+ whenever(pendingIntent.isActivity).thenReturn(true)
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+ whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(true)
+
+ underTest.startPendingIntentMaybeDismissingKeyguard(
+ intent = pendingIntent,
+ animationController = controller,
+ intentSentUiThreadCallback = null,
+ )
+ mainExecutor.runAllReady()
+
+ verify(activityLaunchAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(),
+ eq(true),
+ nullable(),
+ eq(true),
+ any(),
+ )
+ }
+
+ @Test
fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() {
val pendingIntent = mock(PendingIntent::class.java)
val associatedView = mock(ExpandableNotificationRow::class.java)
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 c8cbe42..41eaf85 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
@@ -158,7 +158,6 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -277,8 +276,6 @@
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 +401,6 @@
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())
@@ -505,10 +501,8 @@
(Lazy<NotificationPresenter>) () -> mNotificationPresenter,
(Lazy<NotificationActivityStarter>) () -> mNotificationActivityStarter,
mNotifLaunchAnimControllerProvider,
- new NotificationExpansionRepository(),
mDozeParameters,
mScrimController,
- mLockscreenWallpaperLazy,
mBiometricUnlockControllerLazy,
mAuthRippleController,
mDozeServiceHost,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 593c587..472709c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -42,6 +42,8 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -96,18 +98,21 @@
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private AuthController mAuthController;
@Mock private DozeHost.Callback mCallback;
-
@Mock private DozeInteractor mDozeInteractor;
+
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
- mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
- mBatteryController, mScrimController, () -> mBiometricUnlockController,
- () -> mAssistManager, mDozeScrimController,
- mKeyguardUpdateMonitor, mPulseExpansionHandler,
- mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
- mAuthController, mNotificationIconAreaController, mDozeInteractor);
+ mStatusBarStateController, mDeviceProvisionedController, mFeatureFlags,
+ mHeadsUpManager, mBatteryController, mScrimController,
+ () -> mBiometricUnlockController, () -> mAssistManager, mDozeScrimController,
+ mKeyguardUpdateMonitor, mPulseExpansionHandler, mNotificationShadeWindowController,
+ mNotificationWakeUpCoordinator, mAuthController, mNotificationIconAreaController,
+ mDozeInteractor);
mDozeServiceHost.initialize(
mCentralSurfaces,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index d84bb72..529e2c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -34,6 +34,8 @@
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.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeHeadsUpTracker;
@@ -42,6 +44,7 @@
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -82,10 +85,12 @@
private KeyguardStateController mKeyguardStateController;
private CommandQueue mCommandQueue;
private NotificationRoundnessManager mNotificationRoundnessManager;
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
+ mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
mTestHelper = new NotificationTestHelper(
mContext,
mDependency,
@@ -119,6 +124,8 @@
mNotificationRoundnessManager,
mHeadsUpStatusBarView,
new Clock(mContext, null),
+ mFeatureFlags,
+ mock(HeadsUpNotificationIconInteractor.class),
Optional.of(mOperatorNameView));
mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f);
}
@@ -203,6 +210,7 @@
mNotificationRoundnessManager,
mHeadsUpStatusBarView,
new Clock(mContext, null),
+ mFeatureFlags, mock(HeadsUpNotificationIconInteractor.class),
Optional.empty());
assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
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 47671fb..0000000
--- 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 6b3bd22..15c09b5 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.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 @@
@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 @@
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 @@
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
+ mAlternateBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
mWallpaperRepository,
mMainDispatcher,
@@ -992,6 +998,7 @@
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
+ mAlternateBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
mWallpaperRepository,
mMainDispatcher,
@@ -1775,7 +1782,7 @@
@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 @@
@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/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index beac995..1e31977 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -86,7 +86,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -222,7 +223,8 @@
HeadsUpManager headsUpManager = mock(HeadsUpManager.class);
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
new NotificationLaunchAnimatorControllerProvider(
- new NotificationExpansionRepository(),
+ new NotificationLaunchAnimationInteractor(
+ new NotificationLaunchAnimationRepository()),
mock(NotificationListContainer.class),
headsUpManager,
mJankMonitor);
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 ee4f208..53c621d 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.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 @@
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 @@
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
mKeyguardStateController,
- mNotificationsInteractor,
+ mNotificationAlertsInteractor,
mock(LockscreenShadeTransitionController.class),
mock(PowerInteractor.class),
mCommandQueue,
@@ -226,7 +226,7 @@
.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 0000000..1e628bd
--- /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/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index bcb34d6..9a77f0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -48,23 +48,29 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.common.ui.ConfigurationState;
+import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
@@ -72,6 +78,7 @@
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -682,7 +689,7 @@
mLocationPublisher,
mMockNotificationAreaController,
mShadeExpansionStateManager,
- mock(FeatureFlags.class),
+ mock(FeatureFlagsClassic.class),
mStatusBarIconController,
mIconManagerFactory,
mCollapsedStatusBarViewModel,
@@ -702,7 +709,14 @@
mExecutor,
mDumpManager,
mStatusBarWindowStateController,
- mKeyguardUpdateMonitor);
+ mKeyguardUpdateMonitor,
+ mock(NotificationIconContainerStatusBarViewModel.class),
+ mock(ConfigurationState.class),
+ mock(ConfigurationController.class),
+ mock(DozeParameters.class),
+ mock(ScreenOffAnimationController.class),
+ mock(StatusBarNotificationIconViewStore.class),
+ mock(DemoModeController.class));
}
private void setUpDaggerComponent() {
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 0000000..4eb1591
--- /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 0000000..0651323
--- /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 0000000..1c8465a
--- /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 aaf8d07..94100fe 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,9 +22,8 @@
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
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -35,64 +36,193 @@
@Test
fun animatableEvent_updatesValue() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = false))
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isFalse()
}
@Test
fun animatableEvent_startAnimation() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = true))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isTrue()
}
@Test
fun animatableEvent_startAnimation_alreadyAnimating() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
events.emit(AnimatableEvent(value = 2, startAnimating = true))
- assertThat(value).isEqualTo(AnimatedValue(value = 2, isAnimating = true))
+ assertThat(value?.value).isEqualTo(2)
+ assertThat(value?.isAnimating).isTrue()
}
@Test
fun animatedValue_stopAnimating() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val stopEvent = MutableSharedFlow<Unit>()
- val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
- stopEvent.emit(Unit)
+ assertThat(value?.isAnimating).isTrue()
+ value?.stopAnimating()
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isFalse()
}
@Test
- fun animatedValue_stopAnimating_notAnimating() = runTest {
+ fun animatedValue_stopAnimatingPrevValue_doesNothing() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val stopEvent = MutableSharedFlow<Unit>()
- val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
- values.launchIn(backgroundScope)
+ val values = events.toAnimatedValueFlow()
+ val value by collectLastValue(values)
runCurrent()
- events.emit(AnimatableEvent(value = 1, startAnimating = false))
+ events.emit(AnimatableEvent(value = 1, startAnimating = true))
+ val prevValue = value
+ assertThat(prevValue?.isAnimating).isTrue()
- assertThat(stopEvent.subscriptionCount.value).isEqualTo(0)
+ events.emit(AnimatableEvent(value = 2, startAnimating = true))
+ assertThat(value?.isAnimating).isTrue()
+ prevValue?.stopAnimating()
+
+ assertThat(value?.value).isEqualTo(2)
+ assertThat(value?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun zipValues_applyTransform() {
+ val animating = AnimatedValue.Animating(1) {}
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(animating, notAnimating) { a, b -> a + b }
+ assertThat(sum.value).isEqualTo(3)
+ }
+
+ @Test
+ fun zipValues_firstIsAnimating_resultIsAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(1) { stopped = true }
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(animating, notAnimating) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun zipValues_secondIsAnimating_resultIsAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(1) { stopped = true }
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(notAnimating, animating) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun zipValues_bothAnimating_resultIsAnimating() {
+ var firstStopped = false
+ var secondStopped = false
+ val first = AnimatedValue.Animating(1) { firstStopped = true }
+ val second = AnimatedValue.Animating(2) { secondStopped = true }
+ val sum = zip(first, second) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(firstStopped).isTrue()
+ assertThat(secondStopped).isTrue()
+ }
+
+ @Test
+ fun zipValues_neitherAnimating_resultIsNotAnimating() {
+ val first = AnimatedValue.NotAnimating(1)
+ val second = AnimatedValue.NotAnimating(2)
+ val sum = zip(first, second) { a, b -> a + b }
+ assertThat(sum.isAnimating).isFalse()
+ }
+
+ @Test
+ fun mapAnimatedValue_isAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(3) { stopped = true }
+ val squared = animating.map { it * it }
+ assertThat(squared.value).isEqualTo(9)
+ assertThat(squared.isAnimating).isTrue()
+ squared.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun mapAnimatedValue_notAnimating() {
+ val notAnimating = AnimatedValue.NotAnimating(3)
+ val squared = notAnimating.map { it * it }
+ assertThat(squared.value).isEqualTo(9)
+ assertThat(squared.isAnimating).isFalse()
+ }
+
+ @Test
+ fun flattenAnimatingValue_neitherAnimating() {
+ val nested = AnimatedValue.NotAnimating(AnimatedValue.NotAnimating(10))
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isFalse()
+ }
+
+ @Test
+ fun flattenAnimatingValue_outerAnimating() {
+ var stopped = false
+ val inner = AnimatedValue.NotAnimating(10)
+ val nested = AnimatedValue.Animating(inner) { stopped = true }
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun flattenAnimatingValue_innerAnimating() {
+ var stopped = false
+ val inner = AnimatedValue.Animating(10) { stopped = true }
+ val nested = AnimatedValue.NotAnimating(inner)
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun flattenAnimatingValue_bothAnimating() {
+ var innerStopped = false
+ var outerStopped = false
+ val inner = AnimatedValue.Animating(10) { innerStopped = true }
+ val nested = AnimatedValue.Animating(inner) { outerStopped = true }
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(innerStopped).isTrue()
+ assertThat(outerStopped).isTrue()
}
}
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 468c5a7..fc2030f 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 @@
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 @@
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 @@
}
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 @@
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 5dcc742..8353cf7 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 @@
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 @@
_scaleForResolution.value = scale
}
- override fun getResolutionScale(): Float {
- return _scaleForResolution.value
- }
+ override fun getResolutionScale(): Float = _scaleForResolution.value
- override fun getDimensionPixelSize(id: Int): Int {
- return 0
+ override fun getDimensionPixelSize(id: Int): Int = pixelSizes[id]?.value ?: 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/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 30132f7..08adda3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,7 +1,8 @@
package com.android.systemui.communal.data.repository
import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -11,7 +12,14 @@
override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()
+ private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
+ override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
+
fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {
_stopwatchAppWidgetInfo.value = appWidgetInfo
}
+
+ fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
+ _communalWidgets.value = inventory
+ }
}
diff --git a/packages/SystemUI/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 5cd09d8..5fd0b4f 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 @@
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 @@
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/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index e160548..71e2bc1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -42,7 +42,7 @@
_transitions.emit(step)
}
- override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? {
+ override fun startTransition(info: TransitionInfo): UUID? {
return if (info.animator == null) UUID.randomUUID() else null
}
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 1cb4ab7..5593596 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 @@
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 9c99cb5..597d52d 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 e59f642..8f18e13 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 @@
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
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 61ba464..f25d282 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 @@
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 @@
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 0000000..d2c3b7a
--- /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 0000000..50d3f0a
--- /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 5ffc094..7473ca6 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.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 @@
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 6743515..003013e 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 @@
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 @@
"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 6e87bee..ea6786e 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 @@
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 0000000..61b0b40
--- /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 3406102..98421a9 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -367,11 +367,7 @@
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 @@
// 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 @@
}
@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 @@
// 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 @@
// 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 @@
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 dc1126e..4c224fb 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 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 1a735f8..75ecdb7 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -1,32 +1,6 @@
package: "com.android.server.accessibility"
-flag {
- name: "proxy_use_apps_on_virtual_device_listener"
- namespace: "accessibility"
- description: "Fixes race condition described in b/286587811"
- bug: "286587811"
-}
-
-flag {
- name: "enable_magnification_multiple_finger_multiple_tap_gesture"
- namespace: "accessibility"
- description: "Whether to enable multi-finger-multi-tap gesture for magnification"
- bug: "257274411"
-}
-
-flag {
- name: "enable_magnification_joystick"
- namespace: "accessibility"
- description: "Whether to enable joystick controls for magnification"
- bug: "297211257"
-}
-
-flag {
- name: "send_a11y_events_based_on_state"
- namespace: "accessibility"
- description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
- bug: "295575684"
-}
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
flag {
name: "add_window_token_without_lock"
@@ -36,20 +10,6 @@
}
flag {
- name: "pinch_zoom_zero_min_span"
- namespace: "accessibility"
- description: "Whether to set min span of ScaleGestureDetector to zero."
- bug: "295327792"
-}
-
-flag {
- name: "disable_continuous_shortcut_on_force_stop"
- namespace: "accessibility"
- description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
- bug: "198018180"
-}
-
-flag {
name: "deprecate_package_list_observer"
namespace: "accessibility"
description: "Stops using the deprecated PackageListObserver."
@@ -57,10 +17,38 @@
}
flag {
- name: "scan_packages_without_lock"
+ name: "disable_continuous_shortcut_on_force_stop"
namespace: "accessibility"
- description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
- bug: "295969873"
+ description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
+ bug: "198018180"
+}
+
+flag {
+ name: "enable_magnification_joystick"
+ namespace: "accessibility"
+ description: "Whether to enable joystick controls for magnification"
+ bug: "297211257"
+}
+
+flag {
+ name: "enable_magnification_multiple_finger_multiple_tap_gesture"
+ namespace: "accessibility"
+ description: "Whether to enable multi-finger-multi-tap gesture for magnification"
+ bug: "257274411"
+}
+
+flag {
+ name: "pinch_zoom_zero_min_span"
+ namespace: "accessibility"
+ description: "Whether to set min span of ScaleGestureDetector to zero."
+ bug: "295327792"
+}
+
+flag {
+ name: "proxy_use_apps_on_virtual_device_listener"
+ namespace: "accessibility"
+ description: "Fixes race condition described in b/286587811"
+ bug: "286587811"
}
flag {
@@ -69,3 +57,17 @@
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 {
+ name: "scan_packages_without_lock"
+ namespace: "accessibility"
+ description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
+ bug: "295969873"
+}
+
+flag {
+ name: "send_a11y_events_based_on_state"
+ namespace: "accessibility"
+ 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 e65a185..87f9cf1 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 @@
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 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 @@
if (readTouchExplorationEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
- } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
+ } else if (mMagnificationmSingleFingerTripleTapEnabledUri.equals(uri)) {
if (readMagnificationEnabledSettingsLocked(userState)) {
onUserStateChangedLocked(userState);
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 65975e4..76ebdf4 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -103,9 +103,8 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
-import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionAllowlistManager;
import com.android.server.contentprotection.ContentProtectionConsentManager;
-import com.android.server.contentprotection.ContentProtectionPackageManager;
import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -207,9 +206,6 @@
boolean mDevCfgEnableContentProtectionReceiver;
@GuardedBy("mLock")
- int mDevCfgContentProtectionAppsBlocklistSize;
-
- @GuardedBy("mLock")
int mDevCfgContentProtectionBufferSize;
@GuardedBy("mLock")
@@ -237,7 +233,7 @@
@Nullable private final ComponentName mContentProtectionServiceComponentName;
- @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+ @Nullable private final ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
@Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
@@ -287,17 +283,15 @@
if (getEnableContentProtectionReceiverLocked()) {
mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
if (mContentProtectionServiceComponentName != null) {
- mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
- mContentProtectionBlocklistManager.updateBlocklist(
- mDevCfgContentProtectionAppsBlocklistSize);
+ mContentProtectionAllowlistManager = createContentProtectionAllowlistManager();
mContentProtectionConsentManager = createContentProtectionConsentManager();
} else {
- mContentProtectionBlocklistManager = null;
+ mContentProtectionAllowlistManager = null;
mContentProtectionConsentManager = null;
}
} else {
mContentProtectionServiceComponentName = null;
- mContentProtectionBlocklistManager = null;
+ mContentProtectionAllowlistManager = null;
mContentProtectionConsentManager = null;
}
}
@@ -445,8 +439,6 @@
case ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER:
case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
- case ContentCaptureManager
- .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD:
@@ -502,14 +494,6 @@
ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
- mDevCfgContentProtectionAppsBlocklistSize =
- DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager
- .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
- ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
- // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
- // it immutable at this point
mDevCfgContentProtectionBufferSize =
DeviceConfig.getInt(
DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -542,7 +526,7 @@
+ mDevCfgMaxBufferSize
+ ", idleFlush="
+ mDevCfgIdleFlushingFrequencyMs
- + ", textFluxh="
+ + ", textFlush="
+ mDevCfgTextChangeFlushingFrequencyMs
+ ", logHistory="
+ mDevCfgLogHistorySize
@@ -552,8 +536,6 @@
+ mDevCfgDisableFlushForViewTreeAppearing
+ ", enableContentProtectionReceiver="
+ mDevCfgEnableContentProtectionReceiver
- + ", contentProtectionAppsBlocklistSize="
- + mDevCfgContentProtectionAppsBlocklistSize
+ ", contentProtectionBufferSize="
+ mDevCfgContentProtectionBufferSize
+ ", contentProtectionRequiredGroupsConfig="
@@ -844,9 +826,6 @@
pw.print("enableContentProtectionReceiver: ");
pw.println(mDevCfgEnableContentProtectionReceiver);
pw.print(prefix2);
- pw.print("contentProtectionAppsBlocklistSize: ");
- pw.println(mDevCfgContentProtectionAppsBlocklistSize);
- pw.print(prefix2);
pw.print("contentProtectionBufferSize: ");
pw.println(mDevCfgContentProtectionBufferSize);
pw.print(prefix2);
@@ -877,9 +856,8 @@
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
- protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
- return new ContentProtectionBlocklistManager(
- new ContentProtectionPackageManager(getContext()));
+ protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+ return new ContentProtectionAllowlistManager();
}
/** @hide */
@@ -1429,7 +1407,7 @@
private boolean isContentProtectionReceiverEnabled(
@UserIdInt int userId, @NonNull String packageName) {
if (mContentProtectionServiceComponentName == null
- || mContentProtectionBlocklistManager == null
+ || mContentProtectionAllowlistManager == null
|| mContentProtectionConsentManager == null) {
return false;
}
@@ -1443,7 +1421,7 @@
}
}
return mContentProtectionConsentManager.isConsentGranted(userId)
- && mContentProtectionBlocklistManager.isAllowed(packageName);
+ && mContentProtectionAllowlistManager.isAllowed(packageName);
}
}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
new file mode 100644
index 0000000..59af526
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
@@ -0,0 +1,38 @@
+/*
+ * 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.contentprotection;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+/**
+ * Manages whether the content protection is enabled for an app using a allowlist.
+ *
+ * @hide
+ */
+public class ContentProtectionAllowlistManager {
+
+ private static final String TAG = "ContentProtectionAllowlistManager";
+
+ public ContentProtectionAllowlistManager() {}
+
+ /** Returns true if the package is allowed. */
+ public boolean isAllowed(@NonNull String packageName) {
+ Slog.v(TAG, packageName);
+ return false;
+ }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
deleted file mode 100644
index a0fd28b..0000000
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
+++ /dev/null
@@ -1,111 +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.contentprotection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Manages whether the content protection is enabled for an app using a blocklist.
- *
- * @hide
- */
-public class ContentProtectionBlocklistManager {
-
- private static final String TAG = "ContentProtectionBlocklistManager";
-
- private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
- "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
-
- @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;
-
- @Nullable private Set<String> mPackageNameBlocklist;
-
- public ContentProtectionBlocklistManager(
- @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
- mContentProtectionPackageManager = contentProtectionPackageManager;
- }
-
- public boolean isAllowed(@NonNull String packageName) {
- if (mPackageNameBlocklist == null) {
- // List not loaded or failed to load, don't run on anything
- return false;
- }
- if (mPackageNameBlocklist.contains(packageName)) {
- return false;
- }
- PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
- if (packageInfo == null) {
- return false;
- }
- if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
- return false;
- }
- if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
- return false;
- }
- if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
- return false;
- }
- return true;
- }
-
- public void updateBlocklist(int blocklistSize) {
- Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
- mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
- }
-
- @Nullable
- private Set<String> readPackageNameBlocklist(int blocklistSize) {
- if (blocklistSize <= 0) {
- // Explicitly requested an empty blocklist
- return Collections.emptySet();
- }
- List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
- if (lines == null) {
- return null;
- }
- return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
- }
-
- @VisibleForTesting
- @Nullable
- protected List<String> readLinesFromRawFile(@NonNull String filename) {
- try (FileReader fileReader = new FileReader(filename);
- BufferedReader bufferedReader = new BufferedReader(fileReader)) {
- return bufferedReader
- .lines()
- .map(line -> line.trim())
- .filter(line -> !line.isBlank())
- .collect(Collectors.toList());
- } catch (Exception ex) {
- Slog.e(TAG, "Failed to read: " + filename, ex);
- return null;
- }
- }
-}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
deleted file mode 100644
index 4ebac07..0000000
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ /dev/null
@@ -1,82 +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.contentprotection;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.util.Slog;
-
-import java.util.Arrays;
-
-/**
- * Basic package manager for content protection using content capture.
- *
- * @hide
- */
-public class ContentProtectionPackageManager {
-
- private static final String TAG = "ContentProtectionPackageManager";
-
- private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
- PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
-
- @NonNull private final PackageManager mPackageManager;
-
- public ContentProtectionPackageManager(@NonNull Context context) {
- mPackageManager = context.getPackageManager();
- }
-
- @Nullable
- public PackageInfo getPackageInfo(@NonNull String packageName) {
- try {
- return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
- } catch (NameNotFoundException ex) {
- Slog.w(TAG, "Package info not found for: " + packageName, ex);
- return null;
- }
- }
-
- public boolean isSystemApp(@NonNull PackageInfo packageInfo) {
- return packageInfo.applicationInfo != null && isSystemApp(packageInfo.applicationInfo);
- }
-
- private boolean isSystemApp(@NonNull ApplicationInfo applicationInfo) {
- return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
- public boolean isUpdatedSystemApp(@NonNull PackageInfo packageInfo) {
- return packageInfo.applicationInfo != null
- && isUpdatedSystemApp(packageInfo.applicationInfo);
- }
-
- private boolean isUpdatedSystemApp(@NonNull ApplicationInfo applicationInfo) {
- return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- }
-
- public boolean hasRequestedInternetPermissions(@NonNull PackageInfo packageInfo) {
- return packageInfo.requestedPermissions != null
- && Arrays.asList(packageInfo.requestedPermissions)
- .contains(Manifest.permission.INTERNET);
- }
-}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 898cdcc..4dca5a5 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -148,6 +148,7 @@
"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 beea063..3af0e8c 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -386,15 +386,14 @@
private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_STORAGE);
/**
- * mLocalUnlockedUsers affects the return value of isUserUnlocked. If
- * any value in the array changes, then the binder cache for
- * isUserUnlocked must be invalidated. When adding mutating methods to
- * WatchedLockedUsers, be sure to invalidate the cache in the new
- * methods.
+ * mCeUnlockedUsers affects the return value of {@link UserManager#isUserUnlocked}. If any
+ * value in the array changes, then the binder cache for {@link UserManager#isUserUnlocked} must
+ * be invalidated. When adding mutating methods to this class, be sure to invalidate the cache
+ * in the new methods.
*/
- private static class WatchedLockedUsers {
+ private static class WatchedUnlockedUsers {
private int[] users = EmptyArray.INT;
- public WatchedLockedUsers() {
+ public WatchedUnlockedUsers() {
invalidateIsUserUnlockedCache();
}
public void append(int userId) {
@@ -426,10 +425,14 @@
}
}
- /** Set of users that we know are unlocked. */
+ /** Set of users whose CE storage is unlocked. */
@GuardedBy("mLock")
- private WatchedLockedUsers mLocalUnlockedUsers = new WatchedLockedUsers();
- /** Set of users that system knows are unlocked. */
+ private WatchedUnlockedUsers mCeUnlockedUsers = new WatchedUnlockedUsers();
+
+ /**
+ * Set of users that are in the RUNNING_UNLOCKED state. This differs from {@link
+ * mCeUnlockedUsers} in that a user can be stopped but still have its CE storage unlocked.
+ */
@GuardedBy("mLock")
private int[] mSystemUnlockedUsers = EmptyArray.INT;
@@ -1144,11 +1147,10 @@
}
}
- // If vold knows that some users have their storage unlocked already (which
- // can happen after a "userspace reboot"), then add those users to
- // mLocalUnlockedUsers. Do this right away and don't wait until
- // PHASE_BOOT_COMPLETED, since the system may unlock users before then.
- private void restoreLocalUnlockedUsers() {
+ // If vold knows that some users have their CE storage unlocked already (which can happen after
+ // a "userspace reboot"), then add those users to mCeUnlockedUsers. Do this right away and
+ // don't wait until PHASE_BOOT_COMPLETED, since the system may unlock users before then.
+ private void restoreCeUnlockedUsers() {
final int[] userIds;
try {
userIds = mVold.getUnlockedUsers();
@@ -1164,7 +1166,7 @@
// reconnecting to vold after it crashed and was restarted, in
// which case things will be the other way around --- we'll know
// about the unlocked users but vold won't.
- mLocalUnlockedUsers.appendAll(userIds);
+ mCeUnlockedUsers.appendAll(userIds);
}
}
}
@@ -1236,7 +1238,7 @@
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 +1322,7 @@
unlockedUsers.add(userId);
}
}
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
for (Integer userId : unlockedUsers) {
try {
@@ -2075,7 +2077,7 @@
connectVold();
}, DateUtils.SECOND_IN_MILLIS);
} else {
- restoreLocalUnlockedUsers();
+ restoreCeUnlockedUsers();
onDaemonConnected();
}
}
@@ -2341,7 +2343,7 @@
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 +2474,7 @@
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 +2493,7 @@
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 +2512,7 @@
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);
@@ -3235,9 +3237,9 @@
try {
mVold.createUserKey(userId, serialNumber, ephemeral);
- // New keys are always unlocked.
+ // Since the user's CE key was just created, the user's CE storage is now unlocked.
synchronized (mLock) {
- mLocalUnlockedUsers.append(userId);
+ mCeUnlockedUsers.append(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -3252,9 +3254,9 @@
try {
mVold.destroyUserKey(userId);
- // Destroying a key also locks it.
+ // Since the user's CE key was just destroyed, the user's CE storage is now locked.
synchronized (mLock) {
- mLocalUnlockedUsers.remove(userId);
+ mCeUnlockedUsers.remove(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -3281,7 +3283,7 @@
mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
}
synchronized (mLock) {
- mLocalUnlockedUsers.append(userId);
+ mCeUnlockedUsers.append(userId);
}
}
@@ -3310,14 +3312,14 @@
}
synchronized (mLock) {
- mLocalUnlockedUsers.remove(userId);
+ mCeUnlockedUsers.remove(userId);
}
}
@Override
public boolean isUserKeyUnlocked(int userId) {
synchronized (mLock) {
- return mLocalUnlockedUsers.contains(userId);
+ return mCeUnlockedUsers.contains(userId);
}
}
@@ -3620,7 +3622,7 @@
@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 +3636,7 @@
@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 +3648,7 @@
@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);
@@ -4716,7 +4718,7 @@
}
pw.println();
- pw.println("Local unlocked users: " + mLocalUnlockedUsers);
+ pw.println("CE unlocked users: " + mCeUnlockedUsers);
pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 55aa716..003046a 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -250,7 +250,7 @@
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 @@
* @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 @@
mCompleted = false;
mCurrentMonitor = null;
- mStartTimeMillis = SystemClock.uptimeMillis();
+ mStartTimeMillis = nowMillis;
+ mPauseEndTimeMillis = 0;
mHandler.postAtFrontOfQueue(this);
}
@@ -360,20 +360,19 @@
}
/**
- * Sets the timeout of the HandlerChecker for one run.
+ * Pauses the checks for the given time.
*
- * <p>The current run will be ignored and the next run will be set to this timeout.
- *
- * <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 @@
}
/**
- * 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 @@
* 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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1650a96..bdda95e 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -190,6 +190,7 @@
final MessageHandler mHandler;
+ private static final int TIMEOUT_DELAY_MS = 1000 * 60;
// Messages that can be sent on mHandler
private static final int MESSAGE_TIMED_OUT = 3;
private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
@@ -4942,6 +4943,7 @@
synchronized (mSessions) {
mSessions.put(toString(), this);
}
+ scheduleTimeout();
if (response != null) {
try {
response.asBinder().linkToDeath(this, 0 /* flags */);
@@ -5109,6 +5111,11 @@
}
}
+ private void scheduleTimeout() {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
+ }
+
public void cancelTimeout() {
mHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
@@ -5146,6 +5153,9 @@
public void onTimedOut() {
IAccountManagerResponse response = getResponseAndClose();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Session.onTimedOut");
+ }
if (response != null) {
try {
response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 31817f1..ef67cbe 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -77,8 +77,10 @@
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_BOOT_COMPLETED;
import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_BTOP;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -4840,6 +4842,7 @@
if (!mConstants.mEnableWaitForFinishAttachApplication) {
finishAttachApplicationInner(startSeq, callingUid, pid);
}
+ maybeSendBootCompletedLocked(app);
} catch (Exception e) {
// We need kill the process group here. (b/148588589)
Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
@@ -5066,6 +5069,45 @@
}
}
+ /**
+ * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped
+ */
+ private void maybeSendBootCompletedLocked(ProcessRecord app) {
+ // Nothing to do if it wasn't previously stopped
+ if (!android.content.pm.Flags.stayStopped() || !app.wasForceStopped()) return;
+
+ // Send LOCKED_BOOT_COMPLETED, if necessary
+ if (app.getApplicationInfo().isEncryptionAware()) {
+ sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED),
+ REASON_LOCKED_BOOT_COMPLETED);
+ }
+ // Send BOOT_COMPLETED if the user is unlocked
+ if (StorageManager.isUserKeyUnlocked(app.userId)) {
+ sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_BOOT_COMPLETED),
+ REASON_BOOT_COMPLETED);
+ }
+ app.setWasForceStopped(false);
+ }
+
+ /** Send a boot_completed broadcast to app */
+ private void sendBootBroadcastToAppLocked(ProcessRecord app, Intent intent,
+ @PowerExemptionManager.ReasonCode int reason) {
+ intent.setPackage(app.info.packageName);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, app.userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ final BroadcastOptions bOptions = mUserController.getTemporaryAppAllowlistBroadcastOptions(
+ reason);
+
+ broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
+ new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ null, null, AppOpsManager.OP_NONE,
+ bOptions.toBundle(), true,
+ false, MY_PID, SYSTEM_UID,
+ SYSTEM_UID, MY_PID, app.userId);
+ }
+
@Override
public void showBootMessage(final CharSequence msg, final boolean always) {
if (Binder.getCallingUid() != myUid()) {
@@ -5476,7 +5518,7 @@
+ " 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 9b5f18c..710278d 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 @@
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/LowMemDetector.java b/services/core/java/com/android/server/am/LowMemDetector.java
index 8f79133..016d3cd 100644
--- a/services/core/java/com/android/server/am/LowMemDetector.java
+++ b/services/core/java/com/android/server/am/LowMemDetector.java
@@ -23,6 +23,7 @@
import static com.android.internal.app.procstats.ProcessStats.ADJ_NOTHING;
import android.annotation.IntDef;
+import android.os.Trace;
import com.android.internal.annotations.GuardedBy;
@@ -90,17 +91,31 @@
private native int waitForPressure();
private final class LowMemThread extends Thread {
+ private boolean mIsTracingMemCriticalLow;
+
+ LowMemThread() {
+ super("LowMemThread");
+ }
+
public void run() {
while (true) {
// sleep waiting for a PSI event
int newPressureState = waitForPressure();
+ // PSI event detected
+ boolean isCriticalLowMemory = newPressureState == ADJ_MEM_FACTOR_CRITICAL;
+ if (isCriticalLowMemory && !mIsTracingMemCriticalLow) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "criticalLowMemory");
+ } else if (!isCriticalLowMemory && mIsTracingMemCriticalLow) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ mIsTracingMemCriticalLow = isCriticalLowMemory;
if (newPressureState == -1) {
// epoll broke, tear this down
mAvailable = false;
break;
}
- // got a PSI event? let's update lowmem info
+ // got an actual PSI event? let's update lowmem info
synchronized (mPressureStateLock) {
mPressureState = newPressureState;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e0e6cad..f04198e 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 @@
ApplicationExitInfo.REASON_LOW_MEMORY,
ApplicationExitInfo.SUBREASON_OOM_KILL,
"oom");
+
+ oomKill.logKillOccurred();
}
}
}
@@ -3255,6 +3257,17 @@
hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
final ProcessStateRecord state = r.mState;
+ // Check if we should mark the processrecord for first launch after force-stopping
+ if ((r.getApplicationInfo().flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+ final boolean wasPackageEverLaunched = mService.getPackageManagerInternal()
+ .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId);
+ // If the package was launched in the past but is currently stopped, only then it
+ // should be considered as stopped after use. Do not mark it if it's the first launch.
+ if (wasPackageEverLaunched) {
+ r.setWasForceStopped(true);
+ }
+ }
+
if (!isolated && !isSdkSandbox
&& userId == UserHandle.USER_SYSTEM
&& (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 940c58b..354f3d3 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.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 @@
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 @@
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 @@
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/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f02b8c7..2c6e598 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -438,6 +438,9 @@
final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
+ /** Whether the app was launched from a stopped state and is being unstopped. */
+ volatile boolean mWasForceStopped;
+
void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
long startUptime, long startElapsedTime) {
this.mStartUid = startUid;
@@ -1602,4 +1605,12 @@
List<ProcessRecord> getLruProcessList() {
return mService.mProcessList.getLruProcessesLOSP();
}
+
+ public void setWasForceStopped(boolean stopped) {
+ mWasForceStopped = stopped;
+ }
+
+ public boolean wasForceStopped() {
+ return mWasForceStopped;
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index a165e88..f5f2b10 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -347,8 +347,10 @@
mHasAboveClient = false;
for (int i = mConnections.size() - 1; i >= 0; i--) {
ConnectionRecord cr = mConnections.valueAt(i);
- if (cr.binding.service.app.mServices != this
- && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+
+ final boolean isSameProcess = cr.binding.service.app != null
+ && cr.binding.service.app.mServices == this;
+ if (!isSameProcess && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
mHasAboveClient = true;
break;
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 2d231b3..a57a785 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -118,6 +118,7 @@
// The list is sorted.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
+ "accessibility",
"android_core_networking",
"angle",
"arc_next",
@@ -144,6 +145,7 @@
"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/UserController.java b/services/core/java/com/android/server/am/UserController.java
index de4ad20..0dd579f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -3387,7 +3387,7 @@
}
- private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
+ BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@PowerWhitelistManager.ReasonCode int reasonCode) {
long duration = 10_000;
final ActivityManagerInternal amInternal =
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index bb9ea28..cbaf05b 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 @@
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 ba43c8d..292fc14 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 @@
* {@link AdiDeviceState#toPersistableString()}.
*/
public static int getPeristedMaxSize() {
- return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ return 36; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
+ (SETTINGS_FIELD_SEPARATOR)5 */
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 35260ed..7abd9c7 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -39,10 +39,9 @@
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.ISpatializerOutputCallback;
import android.media.MediaMetrics;
-import android.media.SpatializationLevel;
-import android.media.SpatializationMode;
import android.media.Spatializer;
-import android.media.SpatializerHeadTrackingMode;
+import android.media.audio.common.HeadTracking;
+import android.media.audio.common.Spatialization;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.text.TextUtils;
@@ -84,22 +83,22 @@
/*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
{
- append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
- append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL);
+ append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_WIRED_HEADSET, Spatialization.Mode.BINAURAL);
+ append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, Spatialization.Mode.BINAURAL);
// assumption for A2DP: mostly headsets
- append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL);
- append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
- append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
- append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, Spatialization.Mode.BINAURAL);
+ append(AudioDeviceInfo.TYPE_DOCK, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_USB_ACCESSORY, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_USB_DEVICE, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_USB_HEADSET, Spatialization.Mode.BINAURAL);
+ append(AudioDeviceInfo.TYPE_LINE_ANALOG, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_LINE_DIGITAL, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_AUX_LINE, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_BLE_HEADSET, Spatialization.Mode.BINAURAL);
+ append(AudioDeviceInfo.TYPE_BLE_SPEAKER, Spatialization.Mode.TRANSAURAL);
// assumption that BLE broadcast would be mostly consumed on headsets
- append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL);
+ append(AudioDeviceInfo.TYPE_BLE_BROADCAST, Spatialization.Mode.BINAURAL);
}
};
@@ -226,12 +225,12 @@
ArrayList<Integer> list = new ArrayList<>(0);
for (byte value : values) {
switch (value) {
- case SpatializerHeadTrackingMode.OTHER:
- case SpatializerHeadTrackingMode.DISABLED:
+ case HeadTracking.Mode.OTHER:
+ case HeadTracking.Mode.DISABLED:
// not expected here, skip
break;
- case SpatializerHeadTrackingMode.RELATIVE_WORLD:
- case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ case HeadTracking.Mode.RELATIVE_WORLD:
+ case HeadTracking.Mode.RELATIVE_SCREEN:
list.add(headTrackingModeTypeToSpatializerInt(value));
break;
default:
@@ -254,10 +253,10 @@
byte[] spatModes = spat.getSupportedModes();
for (byte mode : spatModes) {
switch (mode) {
- case SpatializationMode.SPATIALIZER_BINAURAL:
+ case Spatialization.Mode.BINAURAL:
mBinauralSupported = true;
break;
- case SpatializationMode.SPATIALIZER_TRANSAURAL:
+ case Spatialization.Mode.TRANSAURAL:
mTransauralSupported = true;
break;
default:
@@ -274,8 +273,8 @@
// initialize list of compatible devices
for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) {
int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i);
- if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
- || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL
+ if ((mode == (int) Spatialization.Mode.BINAURAL && mBinauralSupported)
+ || (mode == (int) Spatialization.Mode.TRANSAURAL
&& mTransauralSupported)) {
mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
}
@@ -577,9 +576,9 @@
int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
Integer.MIN_VALUE);
- device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+ device.setSAEnabled(spatMode == Spatialization.Mode.BINAURAL
? mBinauralEnabledDefault
- : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+ : spatMode == Spatialization.Mode.TRANSAURAL
? mTransauralEnabledDefault
: false);
device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
@@ -629,9 +628,9 @@
if (isBluetoothDevice(internalDeviceType)) return deviceType;
final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
- if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
+ if (spatMode == Spatialization.Mode.TRANSAURAL) {
return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
- } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) {
+ } else if (spatMode == Spatialization.Mode.BINAURAL) {
return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
}
return AudioDeviceInfo.TYPE_UNKNOWN;
@@ -690,8 +689,7 @@
// since their physical characteristics are unknown
if (deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_UNKNOWN
|| deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES) {
- available = (spatMode == SpatializationMode.SPATIALIZER_BINAURAL)
- && mBinauralSupported;
+ available = (spatMode == Spatialization.Mode.BINAURAL) && mBinauralSupported;
} else {
available = false;
}
@@ -804,8 +802,8 @@
// not be included.
final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
/*default when type not found*/ -1);
- if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
- || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
+ if ((modeForDevice == Spatialization.Mode.BINAURAL && mBinauralSupported)
+ || (modeForDevice == Spatialization.Mode.TRANSAURAL
&& mTransauralSupported)) {
return true;
}
@@ -1479,7 +1477,7 @@
}
synchronized void onInitSensors() {
- final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE);
+ final boolean init = mFeatureEnabled && (mSpatLevel != Spatialization.Level.NONE);
final String action = init ? "initializing" : "releasing";
if (mSpat == null) {
logloge("not " + action + " sensors, null spatializer");
@@ -1545,13 +1543,13 @@
// SDK <-> AIDL converters
private static int headTrackingModeTypeToSpatializerInt(byte mode) {
switch (mode) {
- case SpatializerHeadTrackingMode.OTHER:
+ case HeadTracking.Mode.OTHER:
return Spatializer.HEAD_TRACKING_MODE_OTHER;
- case SpatializerHeadTrackingMode.DISABLED:
+ case HeadTracking.Mode.DISABLED:
return Spatializer.HEAD_TRACKING_MODE_DISABLED;
- case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+ case HeadTracking.Mode.RELATIVE_WORLD:
return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
- case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ case HeadTracking.Mode.RELATIVE_SCREEN:
return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
default:
throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode));
@@ -1561,13 +1559,13 @@
private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
switch (sdkMode) {
case Spatializer.HEAD_TRACKING_MODE_OTHER:
- return SpatializerHeadTrackingMode.OTHER;
+ return HeadTracking.Mode.OTHER;
case Spatializer.HEAD_TRACKING_MODE_DISABLED:
- return SpatializerHeadTrackingMode.DISABLED;
+ return HeadTracking.Mode.DISABLED;
case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
- return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+ return HeadTracking.Mode.RELATIVE_WORLD;
case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
- return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+ return HeadTracking.Mode.RELATIVE_SCREEN;
default:
throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
}
@@ -1575,11 +1573,11 @@
private static int spatializationLevelToSpatializerInt(byte level) {
switch (level) {
- case SpatializationLevel.NONE:
+ case Spatialization.Level.NONE:
return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
- case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
+ case Spatialization.Level.MULTICHANNEL:
return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
- case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
+ case Spatialization.Level.BED_PLUS_OBJECTS:
return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
default:
throw (new IllegalArgumentException("Unexpected spatializer level:" + level));
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 3d347be..f9bc8dc 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.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 @@
* 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 @@
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 @@
}
}
- 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 @@
}
}
+ 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/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 4f1df3f..70d4ad2 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -120,14 +120,14 @@
}
public static Display.Mode createMode(int width, int height, float refreshRate) {
- return createMode(width, height, refreshRate, new float[0], new int[0]);
+ return createMode(width, height, refreshRate, refreshRate, new float[0], new int[0]);
}
- public static Display.Mode createMode(int width, int height, float refreshRate,
+ public static Display.Mode createMode(int width, int height, float refreshRate, float vsyncRate,
float[] alternativeRefreshRates,
@Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
- alternativeRefreshRates, supportedHdrTypes);
+ vsyncRate, alternativeRefreshRates, supportedHdrTypes);
}
public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 425a1af..6695801 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -27,6 +27,8 @@
* the DisplayBrightnessModeStrategies when updating the brightness.
*/
public final class DisplayBrightnessState {
+ public static final float CUSTOM_ANIMATION_RATE_NOT_SET = -1f;
+
private final float mBrightness;
private final float mSdrBrightness;
@@ -37,6 +39,8 @@
private final boolean mIsSlowChange;
+ private final float mCustomAnimationRate;
+
private DisplayBrightnessState(Builder builder) {
mBrightness = builder.getBrightness();
mSdrBrightness = builder.getSdrBrightness();
@@ -45,6 +49,7 @@
mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
mIsSlowChange = builder.isSlowChange();
mMaxBrightness = builder.getMaxBrightness();
+ mCustomAnimationRate = builder.getCustomAnimationRate();
}
/**
@@ -97,7 +102,12 @@
return mMaxBrightness;
}
-
+ /**
+ * @return custom animation rate
+ */
+ public float getCustomAnimationRate() {
+ return mCustomAnimationRate;
+ }
@Override
public String toString() {
@@ -112,6 +122,7 @@
stringBuilder.append(getShouldUseAutoBrightness());
stringBuilder.append("\n isSlowChange:").append(mIsSlowChange);
stringBuilder.append("\n maxBrightness:").append(mMaxBrightness);
+ stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate);
return stringBuilder.toString();
}
@@ -137,13 +148,14 @@
otherState.getDisplayBrightnessStrategyName())
&& mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
&& mIsSlowChange == otherState.isSlowChange()
- && mMaxBrightness == otherState.getMaxBrightness();
+ && mMaxBrightness == otherState.getMaxBrightness()
+ && mCustomAnimationRate == otherState.getCustomAnimationRate();
}
@Override
public int hashCode() {
return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
- mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness);
+ mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate);
}
/**
@@ -164,6 +176,7 @@
private boolean mShouldUseAutoBrightness;
private boolean mIsSlowChange;
private float mMaxBrightness;
+ private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
/**
* Create a builder starting with the values from the specified {@link
@@ -180,6 +193,7 @@
builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
builder.setIsSlowChange(state.isSlowChange());
builder.setMaxBrightness(state.getMaxBrightness());
+ builder.setCustomAnimationRate(state.getCustomAnimationRate());
return builder;
}
@@ -303,6 +317,22 @@
return mMaxBrightness;
}
+
+ /**
+ * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+ */
+ public Builder setCustomAnimationRate(float animationRate) {
+ this.mCustomAnimationRate = animationRate;
+ return this;
+ }
+
+ /**
+ * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+ */
+ public float getCustomAnimationRate() {
+ return mCustomAnimationRate;
+ }
+
/**
* This is used to construct an immutable DisplayBrightnessState object from its builder
*/
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index cb2302a..57b2c24 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -586,7 +586,8 @@
mSystemReady = false;
mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
- mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
+ // TODO: b/306170135 - return TextUtils package name check instead
+ mExtraDisplayEventLogging = true;
}
public void setupSchedulerPolicies() {
@@ -1131,6 +1132,7 @@
new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
overriddenInfo.refreshRateOverride,
+ currentMode.getVsyncRate(),
new float[0], currentMode.getSupportedHdrTypes());
overriddenInfo.modeId =
overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
@@ -2306,8 +2308,10 @@
@GuardedBy("mSyncRoot")
private boolean hdrConversionIntroducesLatencyLocked() {
+ HdrConversionMode mode = getHdrConversionModeSettingInternal();
final int preferredHdrOutputType =
- getHdrConversionModeSettingInternal().getPreferredHdrOutputType();
+ mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
+ ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType();
if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) {
int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency();
return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType);
@@ -2588,16 +2592,14 @@
// TODO(b/202378408) set minimal post-processing only if it's supported once we have a
// separate API for disabling on-device processing.
boolean mppRequest = isMinimalPostProcessingAllowed() && preferMinimalPostProcessing;
- boolean disableHdrConversionForLatency = false;
+ // If HDR conversion introduces latency, disable that in case minimal
+ // post-processing is requested
+ boolean disableHdrConversionForLatency =
+ mppRequest ? hdrConversionIntroducesLatencyLocked() : false;
if (display.getRequestedMinimalPostProcessingLocked() != mppRequest) {
display.setRequestedMinimalPostProcessingLocked(mppRequest);
shouldScheduleTraversal = true;
- // If HDR conversion introduces latency, disable that in case minimal
- // post-processing is requested
- if (mppRequest) {
- disableHdrConversionForLatency = hdrConversionIntroducesLatencyLocked();
- }
}
if (shouldScheduleTraversal) {
@@ -2933,8 +2935,15 @@
// Send a display event if the display is enabled
private void sendDisplayEventIfEnabledLocked(@NonNull LogicalDisplay display,
@DisplayEvent int event) {
+ final boolean displayIsEnabled = display.isEnabledLocked();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "sendDisplayEventLocked#event=" + event + ",displayEnabled="
+ + displayIsEnabled);
+ }
+
// Only send updates outside of DisplayManagerService for enabled displays
- if (display.isEnabledLocked()) {
+ if (displayIsEnabled) {
sendDisplayEventLocked(display, event);
} else if (mExtraDisplayEventLogging) {
Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);
@@ -2991,7 +3000,11 @@
+ displayId + ", event=" + event
+ (uids != null ? ", uids=" + uids : ""));
}
-
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "deliverDisplayEvent#event=" + event + ",displayId="
+ + displayId + (uids != null ? ", uids=" + uids : ""));
+ }
// Grab the lock and copy the callbacks.
final int count;
synchronized (mSyncRoot) {
@@ -3031,7 +3044,8 @@
}
private boolean extraLogging(String packageName) {
- return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
+ // TODO: b/306170135 - return mExtraDisplayLoggingPackageName & package name check instead
+ return true;
}
// Runs on Handler thread.
@@ -3498,10 +3512,13 @@
@Override
public void binderDied() {
- if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
- mPackageName)) {
+ if (DEBUG || extraLogging(mPackageName)) {
Slog.d(TAG, "Display listener for pid " + mPid + " died.");
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "displayManagerBinderDied#mPid=" + mPid);
+ }
onCallbackDied(this);
}
@@ -3510,11 +3527,15 @@
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
if (!shouldSendEvent(event)) {
- if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
- mPackageName)) {
+ if (extraLogging(mPackageName)) {
Slog.i(TAG,
"Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask="
+ + mEventsMask);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index d97c8e7..8c39d7d 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 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 @@
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 @@
}
private int getDisplays() {
- String category = getNextArg();
+ String opt = "", requestedType, category = null;
+ PrintWriter out = getOutPrintWriter();
+
+ List<Integer> displayTypeList = new ArrayList<>();
+ boolean showIdsOnly = false, filterByType = false;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-i":
+ case "--ids-only":
+ showIdsOnly = true;
+ break;
+ case "-t":
+ case "--type":
+ requestedType = getNextArgRequired();
+ int displayType = getType(requestedType, out);
+ if (displayType == -1) {
+ return 1;
+ }
+ displayTypeList.add(displayType);
+ filterByType = true;
+ break;
+ case "-c":
+ case "--category":
+ if (category != null) {
+ out.println("Error: the category has been specified more than one time. "
+ + "Please select only one category.");
+ return 1;
+ }
+ category = getNextArgRequired();
+ break;
+ case "":
+ break;
+ default:
+ out.println("Error: unknown option '" + opt + "'");
+ return 1;
+ }
+ }
+
+ String lastCategoryArgument = getNextArg();
+ if (lastCategoryArgument != null) {
+ if (category != null) {
+ out.println("Error: the category has been specified both with the -c option and "
+ + "the positional argument. Please select only one category.");
+ return 1;
+ }
+ category = lastCategoryArgument;
+ }
+
DisplayManager dm = mService.getContext().getSystemService(DisplayManager.class);
Display[] displays = dm.getDisplays(category);
- PrintWriter out = getOutPrintWriter();
- out.println("Displays:");
+
+ if (filterByType) {
+ displays = Arrays.stream(displays).filter(d -> displayTypeList.contains(d.getType()))
+ .toArray(Display[]::new);
+ }
+
+ if (!showIdsOnly) {
+ out.println("Displays:");
+ }
for (int i = 0; i < displays.length; i++) {
- out.println(" " + displays[i]);
+ out.println((showIdsOnly ? displays[i].getDisplayId() : displays[i]));
}
return 0;
}
+ private int getType(String type, PrintWriter out) {
+ type = type.toUpperCase(Locale.ENGLISH);
+ switch (type) {
+ case "UNKNOWN":
+ return TYPE_UNKNOWN;
+ case "INTERNAL":
+ return TYPE_INTERNAL;
+ case "EXTERNAL":
+ return TYPE_EXTERNAL;
+ case "WIFI":
+ return TYPE_WIFI;
+ case "OVERLAY":
+ return TYPE_OVERLAY;
+ case "VIRTUAL":
+ return TYPE_VIRTUAL;
+ default:
+ out.println("Error: argument for display type should be "
+ + "one of 'UNKNOWN', 'INTERNAL', 'EXTERNAL', 'WIFI', 'OVERLAY', 'VIRTUAL', "
+ + "but got '" + type + "' instead.");
+ return -1;
+ }
+ }
+
private int showNotification() {
final String notificationType = getNextArg();
if (notificationType == null) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 7d9c018..0f00027 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -561,8 +561,9 @@
brightnessSetting, () -> postBrightnessChangeRunnable(),
new HandlerExecutor(mHandler));
- mBrightnessClamperController = new BrightnessClamperController(mHandler,
- modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+ mBrightnessClamperController = mInjector.getBrightnessClamperController(
+ mHandler, modeChangeCallback::run,
+ new BrightnessClamperController.DisplayDeviceData(
mUniqueDisplayId,
mThermalBrightnessThrottlingDataId,
logicalDisplay.getPowerThrottlingDataIdLocked(),
@@ -1353,6 +1354,8 @@
float rawBrightnessState = displayBrightnessState.getBrightness();
mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
boolean slowChange = displayBrightnessState.isSlowChange();
+ // custom transition duration
+ float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
// Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
// doesn't yet have a valid lux value to use with auto-brightness.
@@ -1485,6 +1488,9 @@
brightnessState = clampedState.getBrightness();
slowChange = clampedState.isSlowChange();
+ // faster rate wins, at this point customAnimationRate == -1, strategy does not control
+ // customAnimationRate. Should be revisited if strategy start setting this value
+ customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
if (updateScreenBrightnessSetting) {
@@ -1553,9 +1559,6 @@
// allowed range.
float animateValue = clampScreenBrightness(brightnessState);
- // custom transition duration
- float customTransitionRate = -1f;
-
// If there are any HDR layers on the screen, we have a special brightness value that we
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
@@ -1570,10 +1573,21 @@
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
animateValue = mBrightnessRangeController.getHdrBrightnessValue();
- customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
+ customAnimationRate = Math.max(customAnimationRate,
+ mBrightnessRangeController.getHdrTransitionRate());
mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
}
+ // if doze or suspend state is requested, we want to finish brightnes animation fast
+ // to allow state animation to start
+ if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+ && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN // dozing
+ || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+ || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
+ customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ slowChange = false;
+ }
+
final float currentBrightness = mPowerState.getScreenBrightness();
final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
@@ -1601,9 +1615,9 @@
if (skipAnimation) {
animateScreenBrightness(animateValue, sdrAnimateValue,
SCREEN_ANIMATION_RATE_MINIMUM);
- } else if (customTransitionRate > 0) {
+ } else if (customAnimationRate > 0) {
animateScreenBrightness(animateValue, sdrAnimateValue,
- customTransitionRate, /* ignoreAnimationLimits = */true);
+ customAnimationRate, /* ignoreAnimationLimits = */true);
} else {
boolean isIncreasing = animateValue > currentBrightness;
final float rampSpeed;
@@ -3059,6 +3073,15 @@
modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
}
+ BrightnessClamperController getBrightnessClamperController(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+
+ return new BrightnessClamperController(handler, clamperChangeListener, data, context,
+ flags);
+ }
+
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return DisplayWhiteBalanceFactory.create(handler,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index e5d38cb..be3207d 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -319,10 +319,10 @@
SurfaceControl.DisplayMode other = displayModes[j];
boolean isAlternative = j != i && other.width == mode.width
&& other.height == mode.height
- && other.refreshRate != mode.refreshRate
+ && other.peakRefreshRate != mode.peakRefreshRate
&& other.group == mode.group;
if (isAlternative) {
- alternativeRefreshRates.add(displayModes[j].refreshRate);
+ alternativeRefreshRates.add(displayModes[j].peakRefreshRate);
}
}
Collections.sort(alternativeRefreshRates);
@@ -1360,7 +1360,7 @@
DisplayModeRecord(SurfaceControl.DisplayMode mode,
float[] alternativeRefreshRates) {
- mMode = createMode(mode.width, mode.height, mode.refreshRate,
+ mMode = createMode(mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate,
alternativeRefreshRates, mode.supportedHdrTypes);
}
@@ -1375,7 +1375,9 @@
return mMode.getPhysicalWidth() == mode.width
&& mMode.getPhysicalHeight() == mode.height
&& Float.floatToIntBits(mMode.getRefreshRate())
- == Float.floatToIntBits(mode.refreshRate);
+ == Float.floatToIntBits(mode.peakRefreshRate)
+ && Float.floatToIntBits(mMode.getVsyncRate())
+ == Float.floatToIntBits(mode.vsyncRate);
}
public String toString() {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 68f72d3..dfcda40 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.os.PowerManager;
+import com.android.server.display.DisplayBrightnessState;
+
import java.io.PrintWriter;
/**
@@ -33,6 +35,10 @@
return mBrightnessCap;
}
+ float getCustomAnimationRate() {
+ return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ }
+
boolean isActive() {
return mIsActive;
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 787f786..b574919 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -51,6 +51,7 @@
*/
public class BrightnessClamperController {
private static final String TAG = "BrightnessClamperController";
+
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
private final ClamperChangeListener mClamperChangeListenerExternal;
@@ -60,6 +61,8 @@
private final List<BrightnessModifier> mModifiers;
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+
+ private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@Nullable
private Type mClamperType = null;
private boolean mClamperApplied = false;
@@ -113,6 +116,7 @@
builder.setIsSlowChange(slowChange);
builder.setBrightness(cappedBrightness);
builder.setMaxBrightness(mBrightnessCap);
+ builder.setCustomAnimationRate(mCustomAnimationRate);
if (mClamperType != null) {
builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -182,6 +186,7 @@
private void recalculateBrightnessCap() {
float brightnessCap = PowerManager.BRIGHTNESS_MAX;
Type clamperType = null;
+ float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
BrightnessClamper<?> minClamper = mClampers.stream()
.filter(BrightnessClamper::isActive)
@@ -191,11 +196,14 @@
if (minClamper != null) {
brightnessCap = minClamper.getBrightnessCap();
clamperType = minClamper.getType();
+ customAnimationRate = minClamper.getCustomAnimationRate();
}
- if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+ if (mBrightnessCap != brightnessCap || mClamperType != clamperType
+ || mCustomAnimationRate != customAnimationRate) {
mBrightnessCap = brightnessCap;
mClamperType = clamperType;
+ mCustomAnimationRate = customAnimationRate;
mClamperChangeListenerExternal.onChanged();
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 39f0b13..200d88a 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,7 +24,6 @@
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.BrightnessUtils;
import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
@@ -176,21 +175,14 @@
} else if (mDesiredMaxBrightness != expectedMaxBrightness) {
mDesiredMaxBrightness = expectedMaxBrightness;
long debounceTime;
- long transitionDuration;
if (mDesiredMaxBrightness > mMaxBrightness) {
debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
- transitionDuration = mHdrBrightnessData.mBrightnessIncreaseDurationMillis;
+ mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
} else {
debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
- transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis;
+ mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
}
- float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness);
- float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness);
-
- mDesiredTransitionRate = Math.abs(
- (maxHlg - desiredMaxHlg) * 1000f / transitionDuration);
-
mHandler.removeCallbacks(mDebouncer);
mHandler.postDelayed(mDebouncer, debounceTime);
}
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index 48d671d..837fbf7 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -40,9 +40,9 @@
public final long mBrightnessIncreaseDebounceMillis;
/**
- * Brightness increase animation duration
+ * Brightness increase animation speed
*/
- public final long mBrightnessIncreaseDurationMillis;
+ public final float mScreenBrightnessRampIncrease;
/**
* Debounce time for brightness decrease
@@ -50,19 +50,19 @@
public final long mBrightnessDecreaseDebounceMillis;
/**
- * Brightness decrease animation duration
+ * Brightness decrease animation speed
*/
- public final long mBrightnessDecreaseDurationMillis;
+ public final float mScreenBrightnessRampDecrease;
@VisibleForTesting
public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
- long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis,
- long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) {
+ long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
+ long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease) {
mMaxBrightnessLimits = maxBrightnessLimits;
mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
- mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis;
+ mScreenBrightnessRampIncrease = screenBrightnessRampIncrease;
mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
- mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis;
+ mScreenBrightnessRampDecrease = screenBrightnessRampDecrease;
}
@Override
@@ -70,9 +70,9 @@
return "HdrBrightnessData {"
+ "mMaxBrightnessLimits: " + mMaxBrightnessLimits
+ ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
- + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis
+ + ", mScreenBrightnessRampIncrease: " + mScreenBrightnessRampIncrease
+ ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
- + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis
+ + ", mScreenBrightnessRampDecrease: " + mScreenBrightnessRampDecrease
+ "} ";
}
@@ -94,8 +94,8 @@
return new HdrBrightnessData(brightnessLimits,
hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
- hdrConfig.getBrightnessIncreaseDurationMillis().longValue(),
+ hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
- hdrConfig.getBrightnessDecreaseDurationMillis().longValue());
+ hdrConfig.getScreenBrightnessRampDecrease().floatValue());
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3435e56..0c4ecbc 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5529,6 +5529,42 @@
return canAccess;
}
+ @GuardedBy("ImfLock.class")
+ private void switchKeyboardLayoutLocked(int direction) {
+ final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
+ if (currentImi == null) {
+ return;
+ }
+ final InputMethodSubtypeHandle currentSubtypeHandle =
+ InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
+ final InputMethodSubtypeHandle nextSubtypeHandle =
+ mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
+ direction > 0);
+ if (nextSubtypeHandle == null) {
+ return;
+ }
+ final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
+ if (nextImi == null) {
+ return;
+ }
+
+ final int subtypeCount = nextImi.getSubtypeCount();
+ if (subtypeCount == 0) {
+ if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
+ setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
+ }
+ return;
+ }
+
+ for (int i = 0; i < subtypeCount; ++i) {
+ if (nextSubtypeHandle.equals(
+ InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
+ setInputMethodLocked(nextImi.getId(), i);
+ return;
+ }
+ }
+ }
+
private void publishLocalService() {
LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
}
@@ -5734,38 +5770,7 @@
@Override
public void switchKeyboardLayout(int direction) {
synchronized (ImfLock.class) {
- final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
- if (currentImi == null) {
- return;
- }
- final InputMethodSubtypeHandle currentSubtypeHandle =
- InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
- final InputMethodSubtypeHandle nextSubtypeHandle =
- mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
- direction > 0);
- if (nextSubtypeHandle == null) {
- return;
- }
- final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
- if (nextImi == null) {
- return;
- }
-
- final int subtypeCount = nextImi.getSubtypeCount();
- if (subtypeCount == 0) {
- if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
- setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
- }
- return;
- }
-
- for (int i = 0; i < subtypeCount; ++i) {
- if (nextSubtypeHandle.equals(
- InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
- setInputMethodLocked(nextImi.getId(), i);
- return;
- }
- }
+ switchKeyboardLayoutLocked(direction);
}
}
@@ -6767,5 +6772,21 @@
public void resetStylusHandwriting(int requestId) {
mImms.resetStylusHandwriting(requestId);
}
+
+ @BinderThread
+ @Override
+ public void switchKeyboardLayoutAsync(int direction) {
+ synchronized (ImfLock.class) {
+ if (!mImms.calledWithValidTokenLocked(mToken)) {
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mImms.switchKeyboardLayoutLocked(direction);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 74b7f08..323cdc5 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -474,13 +474,14 @@
// If we have a GNSS provider override, add the hardware provider as a standalone
// option for use by apps with the correct permission. Note the GNSS HAL can only
// support a single client, so mGnssManagerService.getGnssLocationProvider() can
- // only be installed with a single provider.
+ // only be installed with a single provider. Locations from this provider won't
+ // be reported through the passive provider.
LocationProviderManager gnssHardwareManager =
new LocationProviderManager(
mContext,
mInjector,
GPS_HARDWARE_PROVIDER,
- mPassiveManager,
+ /*passiveManager=*/ null,
Collections.singletonList(Manifest.permission.LOCATION_HARDWARE));
addLocationProviderManager(
gnssHardwareManager, mGnssManagerService.getGnssLocationProvider());
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
index 5d5d59b..8cb334d 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.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 @@
@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 @@
}
}
-
/* 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 33190ad..360a6a7 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.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 @@
@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 @@
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 @@
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/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 0e8f907..2c59511 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -268,7 +268,14 @@
}
if (record.isSystemPriority()) {
if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
+ Log.d(
+ TAG,
+ "Global priority session updated - user id="
+ + record.getUserId()
+ + " package="
+ + record.getPackageName()
+ + " active="
+ + record.isActive());
}
user.pushAddressedPlayerChangedLocked();
} else {
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 0000000..5bad067
--- /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 8cbc368..58927d1 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.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 @@
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 @@
return Looper.getMainLooper();
}
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
- return MediaProjectionMetricsLogger.getInstance();
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+ return MediaProjectionMetricsLogger.getInstance(context);
}
}
@@ -293,6 +292,12 @@
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 @@
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 @@
.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 @@
}
@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 f18ecad..55a30bf 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 ff70cb3..244de0b 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 @@
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 4026d0c..bfec58c 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 @@
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 b4d36db..7ca5699 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_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.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 @@
@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 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 @@
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 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 @@
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 @@
}
}
}
+
+ 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 @@
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 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 @@
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 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
|| 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 @@
});
}
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.clearEffectsLocked(canceledKey);
} else {
// sound
@@ -9917,7 +9926,7 @@
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/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 30017be..510c06e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -24,7 +24,6 @@
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_HOME;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -1525,9 +1524,6 @@
ai.secondaryCpuAbi = ps.getSecondaryCpuAbiLegacy();
ai.volumeUuid = ps.getVolumeUuid();
ai.storageUuid = StorageManager.convert(ai.volumeUuid);
- if (ps.isDefaultToDeviceProtectedStorage()) {
- ai.privateFlags |= PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
- }
ai.setVersionCode(ps.getVersionCode());
ai.flags = ps.getFlags();
ai.privateFlags = ps.getPrivateFlags();
@@ -4596,6 +4592,7 @@
flags = updateFlagsForApplication(flags, userId);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
final boolean listApex = (flags & MATCH_APEX) != 0;
+ final boolean listArchivedOnly = !listUninstalled && (flags & MATCH_ARCHIVED_PACKAGES) != 0;
enforceCrossUserPermission(
callingUid,
@@ -4607,7 +4604,7 @@
ArrayList<ApplicationInfo> list;
final ArrayMap<String, ? extends PackageStateInternal> packageStates =
getPackageStates();
- if (listUninstalled) {
+ if (listUninstalled || listArchivedOnly) {
list = new ArrayList<>(packageStates.size());
for (PackageStateInternal ps : packageStates.values()) {
ApplicationInfo ai;
@@ -4619,6 +4616,11 @@
if (!listApex && ps.getPkg().isApex()) {
continue;
}
+ PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+ if (listArchivedOnly && !userState.isInstalled()
+ && userState.getArchiveState() == null) {
+ continue;
+ }
if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
continue;
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2967818..7cac870 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;
@@ -996,7 +996,7 @@
}
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));
@@ -1630,7 +1630,8 @@
synchronized (mPm.mLock) {
if (DEBUG_INSTALL) {
Slog.d(TAG,
- "replacePackageLI: new=" + parsedPackage + ", old=" + oldPackage);
+ "replacePackageLI: new=" + parsedPackage
+ + ", old=" + oldPackageState.getName());
}
ps = mPm.mSettings.getPackageLPr(pkgName11);
@@ -1789,7 +1790,7 @@
if (DEBUG_INSTALL) {
Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage
- + ", old=" + oldPackage);
+ + ", old=" + oldPackageState.getName());
}
request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
request.setApexModuleName(oldPackageState.getApexModuleName());
@@ -1799,7 +1800,7 @@
if (DEBUG_INSTALL) {
Slog.d(TAG,
"replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
- + oldPackage);
+ + oldPackageState.getName());
}
}
} else { // new package install
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a161e8c..1135466 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.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 @@
}
@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 0fb1f7a..781e5f8 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.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.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 @@
});
}
- /**
- * 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 @@
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 d0e5f96..5dc7dab 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 @@
}
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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 839b699..61b6b24 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4566,6 +4566,7 @@
final Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, pmi.getPackageUid(packageName, 0, userId));
extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+ extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras,
@@ -6969,6 +6970,7 @@
final Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, uid);
extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+ extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
packageName, extras,
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 42f4cfb..ad26d1f 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
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.COMPONENT_ENABLED_STATE_ENABLED;
@@ -89,17 +90,14 @@
private static class Booleans {
@IntDef({
INSTALL_PERMISSION_FIXED,
- DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
UPDATE_AVAILABLE,
FORCE_QUERYABLE_OVERRIDE
})
public @interface Flags {
}
private static final int INSTALL_PERMISSION_FIXED = 1;
- private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
- private static final int UPDATE_AVAILABLE = 1 << 2;
- private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
- private static final int PERSISTENT = 1 << 4;
+ private static final int UPDATE_AVAILABLE = 1 << 1;
+ private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
}
private int mBooleans;
@@ -500,13 +498,6 @@
return this;
}
- public PackageSetting setDefaultToDeviceProtectedStorage(
- boolean defaultToDeviceProtectedStorage) {
- setBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE, defaultToDeviceProtectedStorage);
- onChanged();
- return this;
- }
-
@Override
public boolean isExternalStorage() {
return (getFlags() & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
@@ -524,12 +515,6 @@
return this;
}
- public PackageSetting setIsPersistent(boolean isPersistent) {
- setBoolean(Booleans.PERSISTENT, isPersistent);
- onChanged();
- return this;
- }
-
public PackageSetting setTargetSdkVersion(int targetSdkVersion) {
mTargetSdkVersion = targetSdkVersion;
onChanged();
@@ -1585,12 +1570,12 @@
*/
@Override
public boolean isDefaultToDeviceProtectedStorage() {
- return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
+ return (getPrivateFlags() & PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0;
}
@Override
public boolean isPersistent() {
- return getBoolean(Booleans.PERSISTENT);
+ return (getFlags() & ApplicationInfo.FLAG_PERSISTENT) != 0;
}
@@ -1746,10 +1731,10 @@
}
@DataClass.Generated(
- time = 1696979728639L,
+ time = 1698097434269L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 8d8acfd4..7ea9e3f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -220,7 +220,7 @@
UserManagerService.getInstance(), usesSdkLibraries,
parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
- newDomainSetId, parsedPackage.isPersistent(),
+ newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
} else {
// make a deep copy to avoid modifying any existing system state.
@@ -241,7 +241,7 @@
UserManagerService.getInstance(),
usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
- parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getMimeGroups(), newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
}
@@ -464,8 +464,6 @@
+ " to " + volumeUuid);
pkgSetting.setVolumeUuid(volumeUuid);
}
- pkgSetting.setDefaultToDeviceProtectedStorage(
- parsedPackage.isDefaultToDeviceProtectedStorage());
SharedLibraryInfo sdkLibraryInfo = null;
if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a39178e..440823c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -946,7 +946,6 @@
ret.setMimeGroups(p.getMimeGroups());
ret.setAppMetadataFilePath(p.getAppMetadataFilePath());
ret.getPkgState().setUpdatedSystemApp(false);
- ret.setIsPersistent(p.isPersistent());
ret.setTargetSdkVersion(p.getTargetSdkVersion());
ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
}
@@ -1061,7 +1060,7 @@
boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ Set<String> mimeGroupNames, @NonNull UUID domainSetId,
int targetSdkVersion, byte[] restrictUpdatedHash) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
@@ -1083,7 +1082,6 @@
// Update new package state.
.setLastModifiedTime(codePath.lastModified())
.setDomainSetId(domainSetId)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdatedHash);
pkgSetting.setFlags(pkgFlags)
@@ -1103,7 +1101,6 @@
.setSecondaryCpuAbi(secondaryCpuAbi)
.setLongVersionCode(versionCode)
.setMimeGroups(createMimeGroups(mimeGroupNames))
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdatedHash)
.setLastModifiedTime(codePath.lastModified());
@@ -1219,7 +1216,7 @@
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
- @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
int targetSdkVersion, byte[] restrictUpdatedHash)
throws PackageManagerException {
final String pkgName = pkgSetting.getPackageName();
@@ -1273,7 +1270,6 @@
.setSecondaryCpuAbi(secondaryCpuAbi)
.updateMimeGroups(mimeGroupNames)
.setDomainSetId(domainSetId)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdatedHash);
// Update SDK library dependencies if needed.
@@ -3071,7 +3067,6 @@
serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
serializer.attributeLong(null, "version", pkg.getVersionCode());
- serializer.attributeBoolean(null, "isPersistent", pkg.isPersistent());
serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
if (pkg.getRestrictUpdateHash() != null) {
serializer.attributeBytesBase64(null, "restrictUpdateHash",
@@ -3140,7 +3135,6 @@
serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
serializer.attributeLong(null, "version", pkg.getVersionCode());
- serializer.attributeBoolean(null, "isPersistent", pkg.isPersistent());
serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
if (pkg.getRestrictUpdateHash() != null) {
serializer.attributeBytesBase64(null, "restrictUpdateHash",
@@ -3182,8 +3176,6 @@
if (pkg.getVolumeUuid() != null) {
serializer.attribute(null, "volumeUuid", pkg.getVolumeUuid());
}
- serializer.attributeBoolean(null, "defaultToDeviceProtectedStorage",
- pkg.isDefaultToDeviceProtectedStorage());
if (pkg.getCategoryOverride() != ApplicationInfo.CATEGORY_UNDEFINED) {
serializer.attributeInt(null, "categoryHint", pkg.getCategoryOverride());
}
@@ -3878,7 +3870,6 @@
}
long versionCode = parser.getAttributeLong(null, "version", 0);
- boolean isPersistent = parser.getAttributeBoolean(null, "isPersistent", false);
int targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
byte[] restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash",
null);
@@ -3901,7 +3892,6 @@
.setSecondaryCpuAbi(secondaryCpuAbiStr)
.setCpuAbiOverride(cpuAbiOverrideStr)
.setLongVersionCode(versionCode)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdateHash);
long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
@@ -3982,7 +3972,6 @@
String installInitiatingPackageName = null;
boolean installInitiatorUninstalled = false;
String volumeUuid = null;
- boolean defaultToDeviceProtectedStorage = false;
boolean updateAvailable = false;
int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
int pkgFlags = 0;
@@ -3997,7 +3986,6 @@
long loadingCompletedTime = 0;
UUID domainSetId;
String appMetadataFilePath = null;
- boolean isPersistent = false;
int targetSdkVersion = 0;
byte[] restrictUpdateHash = null;
try {
@@ -4023,7 +4011,6 @@
}
versionCode = parser.getAttributeLong(null, "version", 0);
- isPersistent = parser.getAttributeBoolean(null, "isPersistent", false);
targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash", null);
installerPackageName = parser.getAttributeValue(null, "installer");
@@ -4038,8 +4025,6 @@
installInitiatorUninstalled = parser.getAttributeBoolean(null,
"installInitiatorUninstalled", false);
volumeUuid = parser.getAttributeValue(null, "volumeUuid");
- defaultToDeviceProtectedStorage = parser.getAttributeBoolean(
- null, "defaultToDeviceProtectedStorage", false);
categoryHint = parser.getAttributeInt(null, "categoryHint",
ApplicationInfo.CATEGORY_UNDEFINED);
appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
@@ -4179,7 +4164,6 @@
installInitiatorUninstalled);
packageSetting.setInstallSource(installSource)
.setVolumeUuid(volumeUuid)
- .setDefaultToDeviceProtectedStorage(defaultToDeviceProtectedStorage)
.setCategoryOverride(categoryHint)
.setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
.setPrimaryCpuAbi(primaryCpuAbiString)
@@ -4189,7 +4173,6 @@
.setLoadingProgress(loadingProgress)
.setLoadingCompletedTime(loadingCompletedTime)
.setAppMetadataFilePath(appMetadataFilePath)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdateHash);
// Handle legacy string here for single-user mode
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 29d99a73..e8cebef 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 @@
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 @@
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 04cd183..0e7ce2e 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.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 @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 7331bc1..154ee6e 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.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 @@
}
@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/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 61e96ca..2ad8bcf 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -1016,8 +1016,8 @@
return;
}
- if (android.content.pm.Flags.nullableDataDir()
- && !state.isInstalled() && !state.dataExists()) {
+ if (!state.isInstalled() && !state.dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The data dir has been deleted
output.dataDir = null;
return;
@@ -1065,8 +1065,8 @@
return;
}
- if (android.content.pm.Flags.nullableDataDir()
- && !state.isInstalled() && !state.dataExists()) {
+ if (!state.isInstalled() && !state.dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The data dir has been deleted
output.dataDir = null;
return;
@@ -1113,9 +1113,9 @@
return Environment.getDataSystemDirectory();
}
- if (android.content.pm.Flags.nullableDataDir()
- && !ps.getUserStateOrDefault(userId).isInstalled()
- && !ps.getUserStateOrDefault(userId).dataExists()) {
+ if (!ps.getUserStateOrDefault(userId).isInstalled()
+ && !ps.getUserStateOrDefault(userId).dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The app has been uninstalled for the user and the data dir has been deleted
return null;
}
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 46121dc..8240c47 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 @@
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 @@
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 @@
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 3a6664a..b439681 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.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 @@
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 @@
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 @@
return mWindowManagerFuncs;
}
+ Looper getLooper() {
+ return mLooper;
+ }
+
AccessibilityShortcutController getAccessibilityShortcutController(
Context context, Handler handler, int initialUserId) {
return new AccessibilityShortcutController(context, handler, initialUserId);
@@ -2208,7 +2220,7 @@
/** {@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 @@
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();
@@ -2466,7 +2478,7 @@
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
initKeyCombinationRules();
- initSingleKeyGestureRules();
+ initSingleKeyGestureRules(injector.getLooper());
mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager);
}
@@ -2737,8 +2749,8 @@
}
}
- private void initSingleKeyGestureRules() {
- mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext);
+ private void initSingleKeyGestureRules(Looper looper) {
+ mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext, looper);
mSingleKeyGestureDetector.addRule(new PowerKeyRule());
if (hasLongPressOnBackBehavior()) {
mSingleKeyGestureDetector.addRule(new BackKeyRule());
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 5fc0637..047555a 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -40,6 +40,7 @@
private static final int MSG_KEY_LONG_PRESS = 0;
private static final int MSG_KEY_VERY_LONG_PRESS = 1;
private static final int MSG_KEY_DELAYED_PRESS = 2;
+ private static final int MSG_KEY_UP = 3;
private int mKeyPressCounter;
private boolean mBeganFromNonInteractive = false;
@@ -144,6 +145,13 @@
* Callback when very long press has been detected.
*/
void onVeryLongPress(long eventTime) {}
+ /**
+ * Callback executed upon each key up event that hasn't been processed by long press.
+ *
+ * @param eventTime the timestamp of this event.
+ * @param pressCount the number of presses detected leading up to this key up event.
+ */
+ void onKeyUp(long eventTime, int pressCount) {}
@Override
public String toString() {
@@ -171,8 +179,8 @@
}
}
- static SingleKeyGestureDetector get(Context context) {
- SingleKeyGestureDetector detector = new SingleKeyGestureDetector();
+ static SingleKeyGestureDetector get(Context context, Looper looper) {
+ SingleKeyGestureDetector detector = new SingleKeyGestureDetector(looper);
sDefaultLongPressTimeout = context.getResources().getInteger(
com.android.internal.R.integer.config_globalActionsKeyTimeout);
sDefaultVeryLongPressTimeout = context.getResources().getInteger(
@@ -180,8 +188,8 @@
return detector;
}
- private SingleKeyGestureDetector() {
- mHandler = new KeyHandler();
+ private SingleKeyGestureDetector(Looper looper) {
+ mHandler = new KeyHandler(looper);
}
void addRule(SingleKeyRule rule) {
@@ -330,6 +338,13 @@
}
if (event.getKeyCode() == mActiveRule.mKeyCode) {
+ // key-up action should always be triggered if not processed by long press.
+ Message msgKeyUp =
+ mHandler.obtainMessage(
+ MSG_KEY_UP, mActiveRule.mKeyCode, mKeyPressCounter, mActiveRule);
+ msgKeyUp.setAsynchronous(true);
+ mHandler.sendMessage(msgKeyUp);
+
// Directly trigger short press when max count is 1.
if (mActiveRule.getMaxMultiPressCount() == 1) {
if (DEBUG) {
@@ -402,8 +417,8 @@
}
private class KeyHandler extends Handler {
- KeyHandler() {
- super(Looper.myLooper());
+ KeyHandler(Looper looper) {
+ super(looper);
}
@Override
@@ -417,6 +432,12 @@
final int keyCode = msg.arg1;
final int pressCount = msg.arg2;
switch(msg.what) {
+ case MSG_KEY_UP:
+ if (DEBUG) {
+ Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode));
+ }
+ rule.onKeyUp(mLastDownTime, pressCount);
+ break;
case MSG_KEY_LONG_PRESS:
if (DEBUG) {
Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode));
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 1d5cac5..7f55836 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -833,18 +833,24 @@
private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
public boolean isUidForeground(int uid) {
- return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
- <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ synchronized (this) {
+ return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+ <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ }
}
@Override
public void onUidGone(int uid, boolean disabled) {
- mProcStatesCache.delete(uid);
+ synchronized (this) {
+ mProcStatesCache.delete(uid);
+ }
}
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
- mProcStatesCache.put(uid, procState);
+ synchronized (this) {
+ mProcStatesCache.put(uid, procState);
+ }
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index c54e3bd..5f8bbe5 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 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.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 @@
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 @@
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 @@
}
/**
- * 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 loadSystem = (which & FLAG_SYSTEM) != 0;
+ boolean loadLock = (which & FLAG_LOCK) != 0;
+ WallpaperData wallpaper = null;
+ WallpaperData lockWallpaper = 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;
- }
-
- if (wallpaper == null && loadSystem) {
+ if (loadSystem) {
// Do this once per boot
if (migrateFromOld) migrateFromOld();
wallpaper = new WallpaperData(userId, FLAG_SYSTEM);
@@ -188,11 +166,8 @@
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 @@
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 c7a3c43..bdcde66 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 @@
}
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 @@
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 @@
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 @@
}
saveSettingsLocked(wallpaper.userId);
- if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) {
+ if (localSync != null) {
localSync.complete();
}
}
@@ -389,129 +387,9 @@
}
}
- // 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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
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 @@
&& 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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
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 @@
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 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 @@
}
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 @@
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 @@
} 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 @@
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 @@
}
}
- // 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 @@
* @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 @@
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 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 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 @@
*/
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 @@
*/
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 @@
*/
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 @@
}
}
- @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 @@
: 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 @@
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 @@
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 @@
}
}
- 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 @@
// 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 @@
@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 @@
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 @@
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 @@
}
// 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 @@
}
}
- // 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 @@
}
@Override
- public boolean isLockscreenLiveWallpaperEnabled() {
- return mIsLockscreenLiveWallpaperEnabled;
- }
-
- @Override
public boolean isMultiCropEnabled() {
return mIsMultiCropEnabled;
}
@@ -4074,13 +3522,12 @@
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 @@
if (mFallbackWallpaper != null) {
dumpWallpaper(mFallbackWallpaper, pw);
}
- pw.print("mIsLockscreenLiveWallpaperEnabled=");
- pw.println(mIsLockscreenLiveWallpaperEnabled);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9fa5ed2..fd8f6bf 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3055,7 +3055,7 @@
@Override
boolean providesOrientation() {
- return mStyleFillsParent;
+ return mStyleFillsParent || mOccludesParent;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 01b8bf7..52ab9b8 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 @@
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 d604402..5db02df 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 @@
* @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 06448d0..022ef61 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.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.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 @@
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 @@
@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 @@
}
}
+ 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 @@
*/
@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 @@
}
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 @@
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/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 823fbc9..2fabb0e 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -29,7 +29,7 @@
*/
public abstract class Dimmer {
- static final boolean DIMMER_REFACTOR = Flags.dimmerRefactor();
+ static final boolean DIMMER_REFACTOR = Flags.introduceSmootherDimmer();
/**
* The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b7b5c2af..4fa6e29 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 @@
// 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/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index f9fa9e6..f6aad4c 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -36,7 +36,10 @@
import com.android.server.UiThread;
+import java.util.function.BooleanSupplier;
+import java.util.function.DoubleSupplier;
import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
import java.util.function.Supplier;
/**
@@ -50,12 +53,12 @@
private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
- private final Supplier<Boolean> mAreCornersRounded;
+ private final BooleanSupplier mAreCornersRounded;
private final Supplier<Color> mColorSupplier;
// Parameters for "blurred wallpaper" letterbox background.
- private final Supplier<Boolean> mHasWallpaperBackgroundSupplier;
- private final Supplier<Integer> mBlurRadiusSupplier;
- private final Supplier<Float> mDarkScrimAlphaSupplier;
+ private final BooleanSupplier mHasWallpaperBackgroundSupplier;
+ private final IntSupplier mBlurRadiusSupplier;
+ private final DoubleSupplier mDarkScrimAlphaSupplier;
private final Supplier<SurfaceControl> mParentSurfaceSupplier;
private final Rect mOuter = new Rect();
@@ -82,11 +85,11 @@
*/
public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- Supplier<Boolean> areCornersRounded,
+ BooleanSupplier areCornersRounded,
Supplier<Color> colorSupplier,
- Supplier<Boolean> hasWallpaperBackgroundSupplier,
- Supplier<Integer> blurRadiusSupplier,
- Supplier<Float> darkScrimAlphaSupplier,
+ BooleanSupplier hasWallpaperBackgroundSupplier,
+ IntSupplier blurRadiusSupplier,
+ DoubleSupplier darkScrimAlphaSupplier,
IntConsumer doubleTapCallbackX,
IntConsumer doubleTapCallbackY,
Supplier<SurfaceControl> parentSurface) {
@@ -247,7 +250,7 @@
* Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}.
*/
private boolean useFullWindowSurface() {
- return mAreCornersRounded.get() || mHasWallpaperBackgroundSupplier.get();
+ return mAreCornersRounded.getAsBoolean() || mHasWallpaperBackgroundSupplier.getAsBoolean();
}
private final class TapEventReceiver extends InputEventReceiver {
@@ -424,7 +427,7 @@
mSurfaceFrameRelative.height());
t.reparent(mSurface, mParentSurface);
- mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.get();
+ mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.getAsBoolean();
updateAlphaAndBlur(t);
t.show(mSurface);
@@ -445,17 +448,17 @@
t.setBackgroundBlurRadius(mSurface, 0);
return;
}
- final float alpha = mDarkScrimAlphaSupplier.get();
+ final float alpha = (float) mDarkScrimAlphaSupplier.getAsDouble();
t.setAlpha(mSurface, alpha);
// Translucent dark scrim can be shown without blur.
- if (mBlurRadiusSupplier.get() <= 0) {
+ if (mBlurRadiusSupplier.getAsInt() <= 0) {
// Removing pre-exesting blur
t.setBackgroundBlurRadius(mSurface, 0);
return;
}
- t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.get());
+ t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.getAsInt());
}
private float[] getRgbColorArray() {
@@ -472,7 +475,7 @@
// and mParentSurface may never be updated in applySurfaceChanges but this
// doesn't mean that update is needed.
|| !mSurfaceFrameRelative.isEmpty()
- && (mHasWallpaperBackgroundSupplier.get() != mHasWallpaperBackground
+ && (mHasWallpaperBackgroundSupplier.getAsBoolean() != mHasWallpaperBackground
|| !mColorSupplier.get().equals(mColor)
|| mParentSurfaceSupplier.get() != mParentSurface);
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7a442e7..2281395 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 @@
}
handleResizingWindows();
+ clearFrameChangingWindows();
if (mWmService.mDisplayFrozen) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -1015,6 +1016,17 @@
}
/**
+ * 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 2e7ff7a..2be2a1a 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 android.os.Trace;
import android.view.WindowManager;
+import android.window.TaskSnapshot;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -85,7 +86,7 @@
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 @@
}
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 4922e90..6c31e3e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,7 +199,6 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.am.AppTimeTracker;
import com.android.server.uri.NeededUriGrants;
-import com.android.window.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -241,7 +240,6 @@
private static final String ATTR_ROOT_AFFINITY = "root_affinity";
private static final String ATTR_ROOTHASRESET = "root_has_reset";
private static final String ATTR_AUTOREMOVERECENTS = "auto_remove_recents";
- private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
private static final String ATTR_USERID = "user_id";
private static final String ATTR_USER_SETUP_COMPLETE = "user_setup_complete";
private static final String ATTR_EFFECTIVE_UID = "effective_uid";
@@ -344,7 +342,6 @@
// the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
boolean autoRemoveRecents; // If true, we should automatically remove the task from
// recents when activity finishes
- boolean askedCompatMode;// Have asked the user about compat mode for this task.
private boolean mHasBeenVisible; // Set if any activities in the task have been visible
String stringName; // caching of toString() result.
@@ -617,7 +614,7 @@
private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
- boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
+ boolean _autoRemoveRecents, int _userId, int _effectiveUid,
String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
@@ -652,7 +649,6 @@
rootWasReset = _rootWasReset;
isAvailable = true;
autoRemoveRecents = _autoRemoveRecents;
- askedCompatMode = _askedCompatMode;
mUserSetupComplete = userSetupComplete;
effectiveUid = _effectiveUid;
touchActiveTime();
@@ -1333,7 +1329,7 @@
clearRootProcess();
- mAtmService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
+ mAtmService.mWindowManager.mTaskSnapshotController.removeAndDeleteSnapshot(
mTaskId, mUserId);
}
@@ -3304,7 +3300,7 @@
// Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
// If true, we want to get the Dimmer from the level above since we don't want to animate
// the dim with the Task.
- if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible())
+ if (!isRootTask() || (Dimmer.DIMMER_REFACTOR && isTranslucentAndVisible())
|| isTranslucent(null)) {
return super.getDimmer();
}
@@ -3768,8 +3764,8 @@
pw.println(")");
}
pw.print(prefix); pw.print("Activities="); pw.println(mChildren);
- if (!askedCompatMode || !inRecents || !isAvailable) {
- pw.print(prefix); pw.print("askedCompatMode="); pw.print(askedCompatMode);
+ if (!inRecents || !isAvailable) {
+ pw.print(prefix);
pw.print(" inRecents="); pw.print(inRecents);
pw.print(" isAvailable="); pw.println(isAvailable);
}
@@ -3877,7 +3873,6 @@
}
out.attributeBoolean(null, ATTR_ROOTHASRESET, rootWasReset);
out.attributeBoolean(null, ATTR_AUTOREMOVERECENTS, autoRemoveRecents);
- out.attributeBoolean(null, ATTR_ASKEDCOMPATMODE, askedCompatMode);
out.attributeInt(null, ATTR_USERID, mUserId);
out.attributeBoolean(null, ATTR_USER_SETUP_COMPLETE, mUserSetupComplete);
out.attributeInt(null, ATTR_EFFECTIVE_UID, effectiveUid);
@@ -3975,7 +3970,6 @@
String windowLayoutAffinity = null;
boolean rootHasReset = false;
boolean autoRemoveRecents = false;
- boolean askedCompatMode = false;
int taskType = 0;
int userId = 0;
boolean userSetupComplete = true;
@@ -4036,9 +4030,6 @@
case ATTR_AUTOREMOVERECENTS:
autoRemoveRecents = Boolean.parseBoolean(attrValue);
break;
- case ATTR_ASKEDCOMPATMODE:
- askedCompatMode = Boolean.parseBoolean(attrValue);
- break;
case ATTR_USERID:
userId = Integer.parseInt(attrValue);
break;
@@ -4192,7 +4183,6 @@
.setOrigActivity(origActivity)
.setRootWasReset(rootHasReset)
.setAutoRemoveRecents(autoRemoveRecents)
- .setAskedCompatMode(askedCompatMode)
.setUserId(userId)
.setEffectiveUid(effectiveUid)
.setLastDescription(lastDescription)
@@ -6269,7 +6259,6 @@
private ComponentName mOrigActivity;
private boolean mRootWasReset;
private boolean mAutoRemoveRecents;
- private boolean mAskedCompatMode;
private int mUserId;
private int mEffectiveUid;
private String mLastDescription;
@@ -6524,11 +6513,6 @@
return this;
}
- private Builder setAskedCompatMode(boolean askedCompatMode) {
- mAskedCompatMode = askedCompatMode;
- return this;
- }
-
private Builder setAffinityIntent(Intent affinityIntent) {
mAffinityIntent = affinityIntent;
return this;
@@ -6661,7 +6645,7 @@
Task buildInner() {
return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
- mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
+ mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 906b3b5..82d3424 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -103,7 +103,6 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
-import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -210,7 +209,7 @@
*/
int mMinHeight;
- Dimmer mDimmer = Flags.dimmerRefactor()
+ Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
? new SmoothDimmer(this) : new LegacyDimmer(this);
/** Apply the dim layer on the embedded TaskFragment. */
@@ -391,41 +390,6 @@
private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
new EnsureActivitiesVisibleHelper(this);
- private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
- new EnsureVisibleActivitiesConfigHelper();
- private class EnsureVisibleActivitiesConfigHelper implements Predicate<ActivityRecord> {
- private boolean mUpdateConfig;
- private boolean mPreserveWindow;
- private boolean mBehindFullscreen;
-
- void reset(boolean preserveWindow) {
- mPreserveWindow = preserveWindow;
- mUpdateConfig = false;
- mBehindFullscreen = false;
- }
-
- void process(ActivityRecord start, boolean preserveWindow) {
- if (start == null || !start.isVisibleRequested()) {
- return;
- }
- reset(preserveWindow);
- forAllActivities(this, start, true /* includeBoundary */,
- true /* traverseTopToBottom */);
-
- if (mUpdateConfig) {
- // Ensure the resumed state of the focus activity if we updated the configuration of
- // any activity.
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
- }
-
- @Override
- public boolean test(ActivityRecord r) {
- mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
- mBehindFullscreen |= r.occludesParent();
- return mBehindFullscreen;
- }
- }
/** Creates an embedded task fragment. */
TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
@@ -2163,13 +2127,6 @@
return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
}
- /**
- * Ensures all visible activities at or below the input activity have the right configuration.
- */
- void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
- mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
- }
-
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 2b12e74..d8e18e4 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 @@
}
}
- 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 3e8c017..233daad 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 @@
* @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 94e66ff..33ef3c5 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.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 @@
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 @@
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 1ed1431..15bd607 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 @@
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;
+ // 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 */);
- }
+ // 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 fbd226e..1456184 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 @@
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 88f72f9..4a074ff 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 @@
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 3a793e9..f14a6f9 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 @@
void updateTrustedOverlay() {
mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl,
isWindowTrustedOverlay());
+ mInputWindowHandle.forceChange();
}
boolean isWindowTrustedOverlay() {
@@ -1346,6 +1347,11 @@
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 @@
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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index debd891..215934f 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -272,12 +272,12 @@
<xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger">
<xs:annotation name="final"/>
</xs:element>
- <!-- Animation time for brightness increase in millis -->
- <xs:element name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger">
+ <!-- Animation speed for brightness increase. In framework brightness units per second. -->
+ <xs:element name="screenBrightnessRampIncrease" type="nonNegativeDecimal">
<xs:annotation name="final"/>
</xs:element>
- <!-- Animation time for brightness decrease in millis -->
- <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger">
+ <!-- Animation speed for brightness decrease. In framework brightness units per second. -->
+ <xs:element name="screenBrightnessRampDecrease" type="nonNegativeDecimal">
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 2d27f0c..f7e0043 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -179,15 +179,15 @@
public class HdrBrightnessConfig {
ctor public HdrBrightnessConfig();
method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
- method public final java.math.BigInteger getBrightnessDecreaseDurationMillis();
method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
- method public final java.math.BigInteger getBrightnessIncreaseDurationMillis();
method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+ method public final java.math.BigDecimal getScreenBrightnessRampDecrease();
+ method public final java.math.BigDecimal getScreenBrightnessRampIncrease();
method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
- method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger);
method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
- method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger);
method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public final void setScreenBrightnessRampDecrease(java.math.BigDecimal);
+ method public final void setScreenBrightnessRampIncrease(java.math.BigDecimal);
}
public class HighBrightnessMode {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 25e8475..323d387 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_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;
@@ -65,7 +66,6 @@
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.utils.Slogf;
import libcore.io.IoUtils;
@@ -159,7 +159,7 @@
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 @@
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
}
@@ -428,7 +428,7 @@
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 @@
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
decreasePolicySizeForAdmin(policyState, enforcingAdmin);
}
@@ -1781,7 +1781,7 @@
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 5a620a3..5f2d87c 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_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;
@@ -490,7 +491,6 @@
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
-import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pdb.PersistentDataBlockManagerInternal;
@@ -3430,7 +3430,7 @@
}
revertTransferOwnershipIfNecessaryLocked();
- if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (!policyEngineMigrationV2Enabled()) {
updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
}
}
@@ -21571,7 +21571,7 @@
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 @@
}
synchronized (getLockObject()) {
- if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (policyEngineMigrationV2Enabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
caller.getPackageName(),
@@ -21621,7 +21621,7 @@
@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/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
deleted file mode 100644
index 1a45782..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-aconfig_declarations {
- name: "device_policy_aconfig_flags",
- package: "com.android.server.devicepolicy.flags",
- srcs: [
- "flags.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "device_policy_aconfig_flags_lib",
- aconfig_declarations: "device_policy_aconfig_flags",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
deleted file mode 100644
index 7e17ef11..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
+++ /dev/null
@@ -1,38 +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.devicepolicy.flags;
-
-import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled;
-import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
-
-import android.os.Binder;
-
-public final class FlagUtils {
- private FlagUtils(){}
-
- public static boolean isPolicyEngineMigrationV2Enabled() {
- return Binder.withCleanCallingIdentity(() -> {
- return policyEngineMigrationV2Enabled();
- });
- }
-
- public static boolean isDevicePolicySizeTrackingEnabled() {
- return Binder.withCleanCallingIdentity(() -> {
- return devicePolicySizeTrackingEnabled();
- });
- }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
deleted file mode 100644
index 0dde496..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-package: "com.android.server.devicepolicy.flags"
-
-flag {
- name: "policy_engine_migration_v2_enabled"
- namespace: "enterprise"
- description: "V2 of the policy engine migrations for Android V"
- bug: "289520697"
-}
-flag {
- name: "device_policy_size_tracking_enabled"
- namespace: "enterprise"
- description: "Add feature to track the total policy size and have a max threshold."
- bug: "281543351"
-}
\ No newline at end of file
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 924e2f8..0d024d6 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2083,17 +2083,14 @@
t.traceEnd();
}
- // Devices without WebView/JavaScript cannot support PAC proxies.
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
- t.traceBegin("StartPacProxyService");
- try {
- pacProxyService = new PacProxyService(context);
- ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
- } catch (Throwable e) {
- reportWtf("starting PacProxyService", e);
- }
- t.traceEnd();
+ t.traceBegin("StartPacProxyService");
+ try {
+ pacProxyService = new PacProxyService(context);
+ ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
+ } catch (Throwable e) {
+ reportWtf("starting PacProxyService", e);
}
+ t.traceEnd();
t.traceBegin("StartConnectivityService");
// This has to be called after NetworkManagementService, NetworkStatsService
diff --git a/services/midi/Android.bp b/services/midi/Android.bp
index 4b5f8a7..a385fe3 100644
--- a/services/midi/Android.bp
+++ b/services/midi/Android.bp
@@ -18,8 +18,8 @@
name: "services.midi",
defaults: ["platform_service_defaults"],
srcs: [":services.midi-sources"],
- libs: ["services.core"],
- static_libs: [
+ libs: [
+ "services.core",
"aconfig_midi_flags_java_lib",
],
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 6ff7b26..b63a58a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -37,6 +37,7 @@
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -598,6 +599,28 @@
false /* orientationPortrait */);
}
+ @Test
+ public void switchesKeyboardLayout_withShortcut_onlyIfImeVisible() throws Exception {
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
+ new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
+ KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isFalse();
+
+ verifyInputViewStatusOnMainSync(
+ () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ true /* expected */,
+ true /* inputViewStarted */);
+
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+ assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
+ new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
+ KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isTrue();
+ }
+
/**
* This checks that when the system navigation bar is not created (e.g. emulator),
* then the IME caption bar is also not created.
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 6eacef7..c617ec4 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -58,6 +58,7 @@
":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 c490604..304f605 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 @@
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 @@
@After
fun removeApk() {
device.uninstallPackage(TEST_PKG_NAME)
+ device.uninstallPackage(ANOTHER_PKG_NAME)
}
@Before
@@ -90,7 +95,9 @@
.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 @@
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 @@
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 bee7c40..b826590 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 @@
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 0000000..0c5d36e
--- /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/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index aaad669..2810145 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1051,7 +1051,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
@@ -1092,7 +1091,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
@@ -1135,7 +1133,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
fail("Expected a PackageManagerException");
@@ -1174,7 +1171,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
@@ -1222,7 +1218,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
@@ -1270,7 +1265,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
@@ -1319,7 +1313,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
@@ -1365,7 +1358,6 @@
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index c12aedb..a400f12 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -90,7 +90,9 @@
.append("\n isSlowChange:")
.append(displayBrightnessState.isSlowChange())
.append("\n maxBrightness:")
- .append(displayBrightnessState.getMaxBrightness());
+ .append(displayBrightnessState.getMaxBrightness())
+ .append("\n customAnimationRate:")
+ .append(displayBrightnessState.getCustomAnimationRate());
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index c37d21a..179a9d5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -570,9 +570,9 @@
assertNotNull(data);
assertEquals(2, data.mMaxBrightnessLimits.size());
assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
- assertEquals(10000, data.mBrightnessDecreaseDurationMillis);
+ assertEquals(0.1f, data.mScreenBrightnessRampDecrease, SMALL_DELTA);
assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
- assertEquals(11000, data.mBrightnessIncreaseDurationMillis);
+ assertEquals(0.11f, data.mScreenBrightnessRampIncrease, SMALL_DELTA);
assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
@@ -841,9 +841,9 @@
+ " </point>\n"
+ " </brightnessMap>\n"
+ " <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n"
- + " <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n"
+ + " <screenBrightnessRampIncrease>0.11</screenBrightnessRampIncrease>\n"
+ " <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n"
- + " <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n"
+ + " <screenBrightnessRampDecrease>0.1</screenBrightnessRampDecrease>\n"
+ "</hdrBrightnessConfig>";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 8b54d6d2..47521d1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -74,6 +74,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -1314,6 +1315,54 @@
}
@Test
+ public void testRampRateForClampersControllerApplied() {
+ float transitionRate = 1.5f;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(transitionRate).build());
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+ eq(transitionRate), anyBoolean());
+ }
+
+ @Test
+ public void testRampRateForClampersControllerNotApplied_ifDoze() {
+ float transitionRate = 1.5f;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ dpr.dozeScreenState = Display.STATE_UNKNOWN;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(transitionRate).build());
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
+ verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
+ eq(transitionRate), anyBoolean());
+ }
+
+ @Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
public void testRampMaxTimeInteractiveThenIdle() {
// Send a display power request
@@ -1637,13 +1686,20 @@
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
final HdrClamper hdrClamper = mock(HdrClamper.class);
+ BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(-1).build());
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
- flags));
+ clamperController, flags));
final LogicalDisplay display = mock(LogicalDisplay.class);
final DisplayDevice device = mock(DisplayDevice.class);
@@ -1662,8 +1718,8 @@
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
animator, automaticBrightnessController, wakelockController,
- screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata,
- brightnessMappingStrategy, injector, config);
+ screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
+ hbmMetadata, brightnessMappingStrategy, injector, config);
}
/**
@@ -1682,6 +1738,7 @@
public final HighBrightnessModeController hbmController;
public final HdrClamper hdrClamper;
+ public final BrightnessClamperController clamperController;
public final HighBrightnessModeMetadata hbmMetadata;
public final BrightnessMappingStrategy brightnessMappingStrategy;
public final DisplayPowerController2.Injector injector;
@@ -1695,6 +1752,7 @@
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController hbmController,
HdrClamper hdrClamper,
+ BrightnessClamperController clamperController,
HighBrightnessModeMetadata hbmMetadata,
BrightnessMappingStrategy brightnessMappingStrategy,
DisplayPowerController2.Injector injector,
@@ -1709,6 +1767,7 @@
this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
this.hbmController = hbmController;
this.hdrClamper = hdrClamper;
+ this.clamperController = clamperController;
this.hbmMetadata = hbmMetadata;
this.brightnessMappingStrategy = brightnessMappingStrategy;
this.injector = injector;
@@ -1728,6 +1787,8 @@
private final HdrClamper mHdrClamper;
+ private final BrightnessClamperController mClamperController;
+
private final DisplayManagerFlags mFlags;
TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
@@ -1738,6 +1799,7 @@
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
HdrClamper hdrClamper,
+ BrightnessClamperController clamperController,
DisplayManagerFlags flags) {
mDisplayPowerState = dps;
mAnimator = animator;
@@ -1748,6 +1810,7 @@
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
mHdrClamper = hdrClamper;
+ mClamperController = clamperController;
mFlags = flags;
}
@@ -1864,6 +1927,14 @@
}
@Override
+ BrightnessClamperController getBrightnessClamperController(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+ return mClamperController;
+ }
+
+ @Override
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return mDisplayWhiteBalanceControllerMock;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 32e2871..a77a958 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -429,7 +429,7 @@
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -451,7 +451,7 @@
SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
new Display.Mode(displayMode2.width, displayMode2.height,
- displayMode2.refreshRate));
+ displayMode2.peakRefreshRate));
updateAvailableDisplays();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
@@ -485,7 +485,7 @@
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -947,7 +947,7 @@
// Set the user preferred display mode
mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
new Display.Mode(
- displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+ displayMode3.width, displayMode3.height, displayMode3.peakRefreshRate));
updateAvailableDisplays();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
displayDeviceInfo = mListener.addedDisplays.get(
@@ -992,6 +992,52 @@
}
@Test
+ public void testGetAndSetDisplayModesDisambiguatesByVsyncRate() throws Exception {
+ SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f, 120f);
+ SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 60f, 60f);
+ SurfaceControl.DisplayMode[] modes =
+ new SurfaceControl.DisplayMode[]{displayMode1, displayMode2};
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 0);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode1)).isTrue();
+ assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), displayMode1))
+ .isTrue();
+
+ display.dynamicInfo.preferredBootDisplayMode = 1;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice changedDisplayDevice = mListener.changedDisplays.get(0);
+ changedDisplayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = changedDisplayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+
+ assertThat(
+ matches(changedDisplayDevice.getSystemPreferredDisplayModeLocked(), displayMode2))
+ .isTrue();
+ }
+
+ @Test
public void testHdrSdrRatio_notifiesOnChange() throws Exception {
FakeDisplay display = new FakeDisplay(PORT_A);
setUpDisplay(display);
@@ -1230,7 +1276,7 @@
private void assertModeIsSupported(Display.Mode[] supportedModes,
SurfaceControl.DisplayMode mode) {
assertThat(Arrays.stream(supportedModes).anyMatch(
- x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+ x -> x.matches(mode.width, mode.height, mode.peakRefreshRate))).isTrue();
}
private void assertModeIsSupported(Display.Mode[] supportedModes,
@@ -1244,7 +1290,7 @@
+ Arrays.toString(supportedModes);
Truth.assertWithMessage(message)
.that(Arrays.stream(supportedModes)
- .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
+ .anyMatch(x -> x.matches(mode.width, mode.height, mode.peakRefreshRate)
&& Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
.isTrue();
}
@@ -1332,16 +1378,28 @@
private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
float refreshRate) {
- return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
+ return createFakeDisplayMode(id, width, height, refreshRate, refreshRate);
}
private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
- float refreshRate, int group) {
+ float refreshRate,
+ float vsyncRate) {
+ return createFakeDisplayMode(id, width, height, refreshRate, vsyncRate, /* group */ 0);
+ }
+
+ private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+ float refreshRate, int group) {
+ return createFakeDisplayMode(id, width, height, refreshRate, refreshRate, group);
+ }
+
+ private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+ float refreshRate, float vsyncRate, int group) {
final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
mode.id = id;
mode.width = width;
mode.height = height;
- mode.refreshRate = refreshRate;
+ mode.peakRefreshRate = refreshRate;
+ mode.vsyncRate = vsyncRate;
mode.xDpi = 100;
mode.yDpi = 100;
mode.group = group;
@@ -1444,6 +1502,9 @@
private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
- && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
+ && Float.floatToIntBits(a.getRefreshRate())
+ == Float.floatToIntBits(b.peakRefreshRate)
+ && Float.floatToIntBits(a.getVsyncRate())
+ == Float.floatToIntBits(b.vsyncRate);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index c0e0df9..ff2b1f4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -137,8 +137,10 @@
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(false);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -150,6 +152,7 @@
assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(0,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertEquals(initialSlowChange, state.isSlowChange());
}
@@ -158,8 +161,10 @@
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -171,6 +176,7 @@
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertFalse(state.isSlowChange());
}
@@ -179,8 +185,10 @@
float initialBrightness = 0.6f;
boolean initialSlowChange = true;
float clampedBrightness = 0.8f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -192,6 +200,7 @@
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertFalse(state.isSlowChange());
}
@@ -200,8 +209,10 @@
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -216,6 +227,7 @@
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertEquals(initialSlowChange, state.isSlowChange());
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index ee187ba..8d8274c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -56,9 +56,9 @@
private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
Map.of(500f, 0.6f),
/* brightnessIncreaseDebounceMillis= */ 1000,
- /* brightnessIncreaseDurationMillis= */ 2000,
+ /* screenBrightnessRampIncrease= */ 0.02f,
/* brightnessDecreaseDebounceMillis= */ 3000,
- /* brightnessDecreaseDurationMillis= */4000
+ /* screenBrightnessRampDecrease= */0.04f
);
private static final int WIDTH = 600;
@@ -152,8 +152,7 @@
mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
- assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.04, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
@@ -181,8 +180,7 @@
mClock.fastForward(1000);
mTestHandler.timeAdvance();
assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2
- assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.02f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
@@ -209,8 +207,7 @@
mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
- assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.04f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
// MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c493f84..37fe8d1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2617,6 +2617,31 @@
assertTrue(CACHED_APP_MAX_ADJ >= app3.mState.getSetAdj());
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_AboveClient_NotStarted() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+ doReturn(app).when(sService).getTopApp();
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+
+ assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+ // Start binding to a service that isn't running yet.
+ ServiceRecord sr = makeServiceRecord(app);
+ sr.app = null;
+ bindService(null, app, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+
+ // Since sr.app is null, this service cannot be in the same process as the
+ // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
+ app.mServices.updateHasAboveClientLocked();
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ assertTrue(app.mServices.hasAboveClient());
+ assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+ }
+
private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
String packageName, boolean hasShownUi) {
long now = SystemClock.uptimeMillis();
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 610ea90..7a6ac4e 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 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.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 @@
private PackageSetting mPackageSetting;
+ private PackageManagerService mPackageManagerService;
+
private PackageArchiver mArchiveManager;
@Before
@@ -130,7 +135,7 @@
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 @@
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 @@
}
@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 eefe5af..3dbab13 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 @@
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/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 5cc84b1..78bf9b0 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -44,7 +43,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
-import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionAllowlistManager;
import com.android.server.contentprotection.ContentProtectionConsentManager;
import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.pm.UserManagerInternal;
@@ -94,7 +93,7 @@
@Mock private UserManagerInternal mMockUserManagerInternal;
- @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+ @Mock private ContentProtectionAllowlistManager mMockContentProtectionAllowlistManager;
@Mock private ContentCaptureServiceInfo mMockContentCaptureServiceInfo;
@@ -108,7 +107,7 @@
private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
- private int mContentProtectionBlocklistManagersCreated;
+ private int mContentProtectionAllowlistManagersCreated;
private int mContentProtectionServiceInfosCreated;
@@ -132,10 +131,10 @@
@Test
public void constructor_contentProtection_flagDisabled_noManagers() {
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -145,10 +144,10 @@
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -158,10 +157,10 @@
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -171,26 +170,13 @@
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@Test
- public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
- mDevCfgEnableContentProtectionReceiver = true;
- mContentCaptureManagerService = new TestContentCaptureManagerService();
- mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
- verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
-
- mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
-
- verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
- }
-
- @Test
public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -201,13 +187,13 @@
assertThat(actual).isNull();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -239,13 +225,13 @@
assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
assertThat(actual.whitelistedComponents).isNull();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
@@ -273,7 +259,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -287,13 +273,13 @@
USER_ID, PACKAGE_NAME);
assertThat(actual).isFalse();
- verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
}
@Test
public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -317,7 +303,7 @@
assertThat(actual).isTrue();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -331,7 +317,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -345,13 +331,13 @@
USER_ID, COMPONENT_NAME);
assertThat(actual).isFalse();
- verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
}
@Test
public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -375,13 +361,13 @@
assertThat(actual).isTrue();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void isContentProtectionReceiverEnabled_true() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -400,7 +386,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -415,7 +401,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -431,7 +417,7 @@
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -572,9 +558,9 @@
}
@Override
- protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
- mContentProtectionBlocklistManagersCreated++;
- return mMockContentProtectionBlocklistManager;
+ protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+ mContentProtectionAllowlistManagersCreated++;
+ return mMockContentProtectionAllowlistManager;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
new file mode 100644
index 0000000..6767a85
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
@@ -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.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionAllowlistManager}.
+ *
+ * <p>Run with: {@code atest FrameworksServicesTests:
+ * com.android.server.contentprotection.ContentProtectionAllowlistManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionAllowlistManagerTest {
+
+ private static final String PACKAGE_NAME = "com.test.package.name";
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
+
+ @Before
+ public void setup() {
+ mContentProtectionAllowlistManager = new ContentProtectionAllowlistManager();
+ }
+
+ @Test
+ public void isAllowed() {
+ boolean actual = mContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
deleted file mode 100644
index ba9956a..0000000
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
+++ /dev/null
@@ -1,236 +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.contentprotection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.content.pm.PackageInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.collect.ImmutableList;
-
-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 java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test for {@link ContentProtectionBlocklistManager}.
- *
- * <p>Run with: {@code atest
- * FrameworksServicesTests:
- * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ContentProtectionBlocklistManagerTest {
-
- private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";
-
- private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";
-
- private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";
-
- private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
- "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
-
- private static final PackageInfo PACKAGE_INFO = new PackageInfo();
-
- private static final List<String> LINES =
- ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);
-
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;
-
- private final List<String> mReadRawFiles = new ArrayList<>();
-
- private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
-
- @Before
- public void setup() {
- mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
- }
-
- @Test
- public void isAllowed_blocklistNotLoaded() {
- boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- assertThat(mReadRawFiles).isEmpty();
- verifyZeroInteractions(mMockContentProtectionPackageManager);
- }
-
- @Test
- public void isAllowed_inBlocklist() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verifyZeroInteractions(mMockContentProtectionPackageManager);
- }
-
- @Test
- public void isAllowed_packageInfoNotFound() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(null);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never())
- .hasRequestedInternetPermissions(any());
- verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_notRequestedInternet() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(false);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_systemApp() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_updatedSystemApp() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
- when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
- .thenReturn(true);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isAllowed_allowed() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
- when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
- .thenReturn(false);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void updateBlocklist_negativeSize() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
- assertThat(mReadRawFiles).isEmpty();
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_zeroSize() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
- assertThat(mReadRawFiles).isEmpty();
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_positiveSize_belowTotal() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
- assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
-
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_positiveSize_aboveTotal() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
- assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
-
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
- }
-
- private final class TestContentProtectionBlocklistManager
- extends ContentProtectionBlocklistManager {
-
- TestContentProtectionBlocklistManager() {
- super(mMockContentProtectionPackageManager);
- }
-
- @Override
- protected List<String> readLinesFromRawFile(@NonNull String filename) {
- mReadRawFiles.add(filename);
- return LINES;
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
deleted file mode 100644
index 7d45ea4..0000000
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
+++ /dev/null
@@ -1,207 +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.contentprotection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.Manifest.permission;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.testing.TestableContext;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-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;
-
-/**
- * Test for {@link ContentProtectionPackageManager}.
- *
- * <p>Run with: {@code atest
- * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionPackageManagerTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ContentProtectionPackageManagerTest {
- private static final String PACKAGE_NAME = "PACKAGE_NAME";
-
- private static final PackageInfo EMPTY_PACKAGE_INFO = new PackageInfo();
-
- private static final PackageInfo SYSTEM_APP_PACKAGE_INFO = createSystemAppPackageInfo();
-
- private static final PackageInfo UPDATED_SYSTEM_APP_PACKAGE_INFO =
- createUpdatedSystemAppPackageInfo();
-
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Rule
- public final TestableContext mContext =
- new TestableContext(ApplicationProvider.getApplicationContext());
-
- @Mock private PackageManager mMockPackageManager;
-
- private ContentProtectionPackageManager mContentProtectionPackageManager;
-
- @Before
- public void setup() {
- mContext.setMockPackageManager(mMockPackageManager);
- mContentProtectionPackageManager = new ContentProtectionPackageManager(mContext);
- }
-
- @Test
- public void getPackageInfo_found() throws Exception {
- PackageInfo expected = createPackageInfo(/* flags= */ 0);
- when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
- .thenReturn(expected);
-
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isEqualTo(expected);
- }
-
- @Test
- public void getPackageInfo_notFound() throws Exception {
- when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
- .thenThrow(new NameNotFoundException());
-
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isNull();
- }
-
- @Test
- public void getPackageInfo_null() {
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isNull();
- }
-
- @Test
- public void isSystemApp_true() {
- boolean actual = mContentProtectionPackageManager.isSystemApp(SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void isSystemApp_false() {
- boolean actual =
- mContentProtectionPackageManager.isSystemApp(UPDATED_SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isSystemApp_noApplicationInfo() {
- boolean actual = mContentProtectionPackageManager.isSystemApp(EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isUpdatedSystemApp_true() {
- boolean actual =
- mContentProtectionPackageManager.isUpdatedSystemApp(
- UPDATED_SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void isUpdatedSystemApp_false() {
- boolean actual =
- mContentProtectionPackageManager.isUpdatedSystemApp(SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isUpdatedSystemApp_noApplicationInfo() {
- boolean actual = mContentProtectionPackageManager.isUpdatedSystemApp(EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void hasRequestedInternetPermissions_true() {
- PackageInfo packageInfo = createPackageInfo(new String[] {permission.INTERNET});
-
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void hasRequestedInternetPermissions_false() {
- PackageInfo packageInfo = createPackageInfo(new String[] {permission.ACCESS_FINE_LOCATION});
-
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void hasRequestedInternetPermissions_noRequestedPermissions() {
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(
- EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- private static PackageInfo createSystemAppPackageInfo() {
- return createPackageInfo(ApplicationInfo.FLAG_SYSTEM);
- }
-
- private static PackageInfo createUpdatedSystemAppPackageInfo() {
- return createPackageInfo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
- }
-
- private static PackageInfo createPackageInfo(int flags) {
- return createPackageInfo(flags, /* requestedPermissions= */ new String[0]);
- }
-
- private static PackageInfo createPackageInfo(String[] requestedPermissions) {
- return createPackageInfo(/* flags= */ 0, requestedPermissions);
- }
-
- private static PackageInfo createPackageInfo(int flags, String[] requestedPermissions) {
- PackageInfo packageInfo = new PackageInfo();
- packageInfo.packageName = PACKAGE_NAME;
- packageInfo.applicationInfo = new ApplicationInfo();
- packageInfo.applicationInfo.packageName = PACKAGE_NAME;
- packageInfo.applicationInfo.flags = flags;
- packageInfo.requestedPermissions = requestedPermissions;
- return packageInfo;
- }
-}
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 6bfd93b..4bb7d63 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_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.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 @@
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 @@
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 f94aff7..4e6dd06 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.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.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.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 @@
private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
new MediaProjectionManagerService.Injector() {
@Override
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
return mMediaProjectionMetricsLogger;
}
};
@@ -311,6 +311,70 @@
}
@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 @@
/* 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 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 0000000..410604f
--- /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 5dfce06..89c6a220 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 @@
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 9bd938f..cf8548c 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.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 java.util.List;
import java.util.Objects;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -190,7 +193,7 @@
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 75d012a..6792cfe 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.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.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.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 @@
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 @@
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 @@
});
// 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 @@
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 @@
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 @@
@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 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 @@
verify(service, times(1)).setDNDMigrationDone(user.id);
}
+ @Test
+ public void testProfileUnavailableIntent() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
+ verify(mWorkerHandler).post(any(Runnable.class));
+ verify(mSnoozeHelper).clearData(anyInt());
+ }
+
+
+ @Test
+ public void testManagedProfileUnavailableIntent() throws RemoteException {
+ mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ verify(mWorkerHandler).post(any(Runnable.class));
+ verify(mSnoozeHelper).clearData(anyInt());
+ }
+
private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
throws RemoteException {
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 121e296..337dd22 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -91,7 +91,6 @@
private static final Multimap<Class<?>, String> KNOWN_BAD =
ImmutableMultimap.<Class<?>, String>builder()
.put(Person.Builder.class, "setUri") // TODO: b/281044385
- .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
.build();
// Types that we can't really produce. No methods receiving these parameters will be invoked.
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index af39b2f..1b8d746 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -60,6 +60,7 @@
"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 8db09f9..270d5df 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 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 @@
* 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);
@@ -208,5 +203,6 @@
mPhoneWindowManager.dispatchUnhandledKey(keyEvent);
}
}
+ mPhoneWindowManager.dispatchAllPendingEvents();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index d388db8..f2721a5 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -25,10 +25,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
import android.content.Context;
+import android.os.Looper;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -38,7 +40,9 @@
import org.junit.Before;
import org.junit.Test;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
@@ -57,6 +61,7 @@
private CountDownLatch mLongPressed = new CountDownLatch(1);
private CountDownLatch mVeryLongPressed = new CountDownLatch(1);
private CountDownLatch mMultiPressed = new CountDownLatch(1);
+ private BlockingQueue<KeyUpData> mKeyUpQueue = new LinkedBlockingQueue<>();
private final Instrumentation mInstrumentation = getInstrumentation();
private final Context mContext = mInstrumentation.getTargetContext();
@@ -76,7 +81,7 @@
public void setUp() {
mInstrumentation.runOnMainSync(
() -> {
- mDetector = SingleKeyGestureDetector.get(mContext);
+ mDetector = SingleKeyGestureDetector.get(mContext, Looper.myLooper());
initSingleKeyGestureRules();
});
@@ -134,6 +139,11 @@
assertTrue(mMaxMultiPressCount >= count);
assertEquals(mExpectedMultiPressCount, count);
}
+
+ @Override
+ void onKeyUp(long eventTime, int multiPressCount) {
+ mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount));
+ }
});
mDetector.addRule(
@@ -167,12 +177,27 @@
}
@Override
+ void onKeyUp(long eventTime, int multiPressCount) {
+ mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount));
+ }
+
+ @Override
void onLongPress(long downTime) {
mLongPressed.countDown();
}
});
}
+ private static class KeyUpData {
+ public final int keyCode;
+ public final int pressCount;
+
+ KeyUpData(int keyCode, int pressCount) {
+ this.keyCode = keyCode;
+ this.pressCount = pressCount;
+ }
+ }
+
private void pressKey(int keyCode, long pressTime) {
pressKey(keyCode, pressTime, true /* interactive */);
}
@@ -250,6 +275,21 @@
}
@Test
+ public void testOnKeyUp() throws InterruptedException {
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+
+ verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
+ }
+
+ private void verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount)
+ throws InterruptedException {
+ KeyUpData keyUpData = mKeyUpQueue.poll(mWaitTimeout, TimeUnit.MILLISECONDS);
+ assertNotNull(keyUpData);
+ assertEquals(expectedKeyCode, keyUpData.keyCode);
+ assertEquals(expectedMultiPressCount, keyUpData.pressCount);
+ }
+
+ @Test
public void testNonInteractive() throws InterruptedException {
// Disallow short press behavior from non interactive.
mAllowNonInteractiveForPress = false;
@@ -314,6 +354,33 @@
}
@Test
+ public void testOnKeyUp_Pressure() throws InterruptedException {
+ final HandlerThread handlerThread =
+ new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY);
+ handlerThread.start();
+ Handler newHandler = new Handler(handlerThread.getLooper());
+ try {
+ // To make sure we won't get any unexpected multi-press count.
+ for (int i = 0; i < 5; i++) {
+ newHandler.runWithScissors(
+ () -> {
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ },
+ mWaitTimeout);
+ newHandler.runWithScissors(
+ () -> pressKey(KEYCODE_BACK, 0 /* pressTime */), mWaitTimeout);
+
+ verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
+ verifyKeyUpData(KEYCODE_POWER, 2 /* expectedMultiPressCount */);
+ verifyKeyUpData(KEYCODE_BACK, 1 /* expectedMultiPressCount */);
+ }
+ } finally {
+ handlerThread.quitSafely();
+ }
+ }
+
+ @Test
public void testUpdateRule() throws InterruptedException {
// Power key rule doesn't allow the long press gesture.
mLongPressOnPowerBehavior = false;
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 2244dbe..e26260a 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.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;
@@ -117,7 +118,6 @@
import java.util.function.Supplier;
class TestPhoneWindowManager {
- private static final long SHORTCUT_KEY_DELAY_MILLIS = 150;
private static final long TEST_SINGLE_KEY_DELAY_MILLIS
= SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 1000L * HW_TIMEOUT_MULTIPLIER;
@@ -160,12 +160,13 @@
@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 +185,10 @@
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();
+ setUp(supportSettingsUpdate);
+ mTestLooper.dispatchAll();
}
private void setUp(boolean supportSettingsUpdate) {
@@ -301,12 +300,15 @@
}
void tearDown() {
- mHandlerThread.quitSafely();
LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
Mockito.reset(mPhoneWindowManager);
mMockitoSession.finishMocking();
}
+ void dispatchAllPendingEvents() {
+ mTestLooper.dispatchAll();
+ }
+
// Override accessibility setting and perform function.
private void overrideLaunchAccessibility() {
doReturn(true).when(mAccessibilityShortcutController)
@@ -327,10 +329,6 @@
mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
}
- void waitForIdle() {
- mHandler.runWithScissors(() -> { }, 0 /* timeout */);
- }
-
/**
* Below functions will override the setting or the policy behavior.
*/
@@ -451,6 +449,7 @@
doNothing().when(mPhoneWindowManager).sendCloseSystemWindows();
doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
+ doReturn(mSearchManager).when(mContext).getSystemService(eq(SearchManager.class));
}
void overrideSearchManager(SearchManager searchManager) {
@@ -504,68 +503,61 @@
* Below functions will check the policy behavior could be invoked.
*/
void assertTakeScreenshotCalled() {
- waitForIdle();
- verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .takeScreenshot(anyInt(), anyInt());
+ mTestLooper.dispatchAll();
+ verify(mDisplayPolicy).takeScreenshot(anyInt(), anyInt());
}
void assertShowGlobalActionsCalled() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPhoneWindowManager).showGlobalActions();
- verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .showDialog(anyBoolean(), anyBoolean());
- verify(mPowerManager, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .userActivity(anyLong(), anyBoolean());
+ verify(mGlobalActions).showDialog(anyBoolean(), anyBoolean());
+ verify(mPowerManager).userActivity(anyLong(), anyBoolean());
}
void assertVolumeMute() {
- waitForIdle();
- verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .silenceRingerModeInternal(eq("volume_hush"));
+ mTestLooper.dispatchAll();
+ verify(mAudioManagerInternal).silenceRingerModeInternal(eq("volume_hush"));
}
void assertAccessibilityKeychordCalled() {
- waitForIdle();
- verify(mAccessibilityShortcutController,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut();
+ mTestLooper.dispatchAll();
+ verify(mAccessibilityShortcutController).performAccessibilityShortcut();
}
void assertDreamRequest() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mDreamManagerInternal).requestDream();
}
void assertPowerSleep() {
- waitForIdle();
- verify(mPowerManager,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt());
+ mTestLooper.dispatchAll();
+ verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
}
void assertPowerWakeUp() {
- waitForIdle();
- verify(mPowerManager,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString());
+ mTestLooper.dispatchAll();
+ verify(mPowerManager).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();
- verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
+ mTestLooper.dispatchAll();
+ verify(mSearchManager).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 +570,17 @@
}
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 +591,7 @@
}
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 +599,17 @@
}
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 +634,12 @@
}
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 +647,13 @@
}
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 +662,7 @@
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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 40ac7b1c..bdbfb7a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2627,6 +2627,13 @@
// Can specify orientation if the current orientation candidate is orientation behind.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE,
activity.getOrientation(SCREEN_ORIENTATION_BEHIND));
+
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
+ .setCreateTask(true).build();
+ assertFalse(translucentActivity.providesOrientation());
+ translucentActivity.setOccludesParent(true);
+ assertTrue(translucentActivity.providesOrientation());
}
@Test
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 d2eb1cc..78566fb 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 @@
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(),
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
- mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
-
doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
- // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
+ // 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());
+ }
+
+ private DisplayInfo createDisplayInfo(int width, int height) {
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ mSurfaceSize = new Point(width, height);
+ mRecordedSurface = surfaceControlMirrors(mSurfaceSize);
+
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 @@
// 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 @@
@Test
public void testUpdateRecording_display() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -147,6 +163,7 @@
@Test
public void testUpdateRecording_display_invalidDisplayIdToMirror() {
+ defaultInit();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
INVALID_DISPLAY);
mContentRecorder.setContentRecordingSession(session);
@@ -156,6 +173,7 @@
@Test
public void testUpdateRecording_display_noDisplayContentToMirror() {
+ defaultInit();
doReturn(null).when(
mWm.mRoot).getDisplayContent(anyInt());
mContentRecorder.setContentRecordingSession(mDisplaySession);
@@ -165,6 +183,7 @@
@Test
public void testUpdateRecording_task_nullToken() {
+ defaultInit();
ContentRecordingSession session = mTaskSession;
session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId());
session.setTokenToRecord(null);
@@ -176,6 +195,7 @@
@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 @@
@Test
public void testUpdateRecording_wasPaused() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -197,6 +218,7 @@
@Test
public void testUpdateRecording_waitingForConsent() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -209,6 +231,7 @@
@Test
public void testOnConfigurationChanged_neverRecording() {
+ defaultInit();
mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
@@ -218,6 +241,7 @@
@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 @@
@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 @@
@Test
public void testOnConfigurationChanged_rotateVirtualDisplay() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -271,12 +297,13 @@
*/
@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 @@
@Test
public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -314,6 +342,7 @@
@Test
public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
+ defaultInit();
mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
final int minWidth = 222;
@@ -351,6 +380,7 @@
@Test
public void testTaskWindowingModeChanged_pip_stopsRecording() {
+ defaultInit();
// WHEN a recording is ongoing.
mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -368,6 +398,7 @@
@Test
public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+ defaultInit();
// WHEN a recording is ongoing.
mTask.setWindowingMode(WINDOWING_MODE_PINNED);
mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -384,6 +415,7 @@
@Test
public void testStartRecording_notifiesCallback_taskSession() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -396,6 +428,7 @@
@Test
public void testStartRecording_notifiesCallback_displaySession() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -408,6 +441,7 @@
@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 @@
@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 @@
@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 @@
@Test
public void testOnVisibleRequestedChanged_notifiesCallback() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -471,6 +508,7 @@
@Test
public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+ defaultInit();
// WHEN a recording is not ongoing.
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -493,6 +531,7 @@
@Test
public void testPauseRecording_pausesRecording() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -502,12 +541,14 @@
@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 @@
@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 @@
@Test
public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
mContentRecorder.setContentRecordingSession(null);
@@ -541,6 +585,7 @@
@Test
public void testUpdateMirroredSurface_capturedAreaResized() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -548,9 +593,9 @@
// 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 @@
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 @@
}
@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 @@
@Test
public void testDisplayContentUpdatesRecording_withSurface() {
+ defaultInit();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
@@ -602,12 +772,13 @@
@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/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 6a738be..9f58491 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -41,7 +41,6 @@
import com.android.server.testutils.StubTransaction;
import com.android.server.wm.utils.MockAnimationAdapter;
-import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -150,7 +149,7 @@
mHost = new MockSurfaceBuildingContainer(mWm);
mTransaction = spy(StubTransaction.class);
mChild = new TestWindowContainer(mWm);
- if (Flags.dimmerRefactor()) {
+ if (Dimmer.DIMMER_REFACTOR) {
sTestAnimation = spy(new MockAnimationAdapter());
mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
} else {
@@ -177,7 +176,7 @@
@Test
public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
final float alpha = 0.7f;
final int blur = 50;
mHost.addChild(mChild, 0);
@@ -197,7 +196,7 @@
@Test
public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
final float alpha = 0.7f;
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, alpha, 20);
@@ -212,7 +211,7 @@
@Test
public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final float alpha = 0.8f;
@@ -232,7 +231,7 @@
@Test
public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final float alpha = 0.8f;
@@ -292,7 +291,7 @@
@Test
public void testRemoveDimImmediately_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 2);
mDimmer.adjustRelativeLayer(mChild, -1);
@@ -312,7 +311,7 @@
@Test
public void testRemoveDimImmediately_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 0);
mDimmer.adjustRelativeLayer(mChild, -1);
@@ -332,7 +331,7 @@
@Test
public void testDimmerWithBlurUpdatesTransaction_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final int blurRadius = 50;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5f92fd5..0d4c443 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -676,60 +676,59 @@
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
throws Exception {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationPortraitOrUndefined_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationLandscape_returnsReverseLandscape() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LANDSCAPE),
- SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LANDSCAPE));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_NOSENSOR));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_NOSENSOR));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION})
public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -740,8 +739,8 @@
mController = new LetterboxUiController(mWm, mActivity);
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -760,8 +759,8 @@
doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isActivityEligibleForOrientationOverride(eq(mActivity));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -780,8 +779,8 @@
doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isActivityEligibleForOrientationOverride(eq(mActivity));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -789,8 +788,8 @@
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_USER);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -799,8 +798,8 @@
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_USER);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -808,8 +807,8 @@
spyOn(mController);
doReturn(false).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -817,15 +816,15 @@
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserMinAspectRatioOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LOCKED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LOCKED));
// unchanged if orientation is specified
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LANDSCAPE), SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LANDSCAPE));
}
@Test
@@ -833,8 +832,8 @@
spyOn(mController);
doReturn(false).when(mController).shouldApplyUserMinAspectRatioOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
// shouldApplyUser...Override
@@ -1286,16 +1285,16 @@
mActivity = setUpActivityWithComponent();
mController = new LetterboxUiController(mWm, mActivity);
- assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
- mActivity.getParent().getConfiguration()), 1.5f, /* delta */ 0.01);
+ assertEquals(1.5f, mController.getFixedOrientationLetterboxAspectRatio(
+ mActivity.getParent().getConfiguration()), /* delta */ 0.01);
spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isTreatmentEnabledForActivity(eq(mActivity));
- assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
- mActivity.getParent().getConfiguration()), mController.getSplitScreenAspectRatio(),
- /* delta */ 0.01);
+ assertEquals(mController.getSplitScreenAspectRatio(),
+ mController.getFixedOrientationLetterboxAspectRatio(
+ mActivity.getParent().getConfiguration()), /* delta */ 0.01);
}
private void mockThatProperty(String propertyName, boolean value) throws Exception {
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 4864868..3cb4a1d 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 @@
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 @@
/** 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 @@
}
/**
+ * 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 7634d9f..03188f8 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 @@
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 @@
}
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 @@
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 @@
}
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 13a4c11..8fecbb9 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 @@
@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 08438c8..267bec9 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 @@
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 @@
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/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 9146889..e0ed642 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1184,6 +1184,7 @@
private boolean mCreateTask = false;
private Task mParentTask;
private int mActivityFlags;
+ private int mActivityTheme;
private int mLaunchMode;
private int mResizeMode = RESIZE_MODE_RESIZEABLE;
private float mMaxAspectRatio;
@@ -1232,6 +1233,14 @@
return this;
}
+ ActivityBuilder setActivityTheme(int theme) {
+ mActivityTheme = theme;
+ // Use the real package of test so it can get a valid context for theme.
+ mComponent = ComponentName.createRelative(mService.mContext.getPackageName(),
+ DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
+ return this;
+ }
+
ActivityBuilder setActivityFlags(int flags) {
mActivityFlags = flags;
return this;
@@ -1381,6 +1390,9 @@
if (mTargetActivity != null) {
aInfo.targetActivity = mTargetActivity;
}
+ if (mActivityTheme != 0) {
+ aInfo.theme = mActivityTheme;
+ }
aInfo.flags |= mActivityFlags;
aInfo.launchMode = mLaunchMode;
aInfo.resizeMode = mResizeMode;
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 0000000..31418d6
--- /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 55b5d11..e413663 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 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
}
} 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 @@
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 @@
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 7a0bf90..038b93f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1056,6 +1056,14 @@
"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 @@
*
* @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 @@
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 73220c3..c0d6b30 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17483,6 +17483,8 @@
* {@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 e9af486..11cbcb1 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.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 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 @@
TYPE_BIP,
TYPE_VSIM,
TYPE_ENTERPRISE,
+ TYPE_RCS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApnType {
@@ -356,6 +362,17 @@
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
this.mCarrierId = builder.mCarrierId;
this.mSkip464Xlat = builder.mSkip464Xlat;
this.mAlwaysOn = builder.mAlwaysOn;
+ this.mInfrastructureBitmask = builder.mInfrastructureBitmask;
}
/**
@@ -1031,6 +1095,8 @@
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 @@
.setCarrierId(apn.mCarrierId)
.setSkip464Xlat(apn.mSkip464Xlat)
.setAlwaysOn(apn.mAlwaysOn)
+ .setInfrastructureBitmask(apn.mInfrastructureBitmask)
.buildWithoutCheck();
}
@@ -1115,6 +1182,7 @@
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 @@
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 @@
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 @@
&& 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 @@
&& 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 @@
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 @@
dest.writeInt(mCarrierId);
dest.writeInt(mSkip464Xlat);
dest.writeBoolean(mAlwaysOn);
+ dest.writeInt(mInfrastructureBitmask);
}
private static ApnSetting readFromParcel(Parcel in) {
@@ -1686,6 +1759,7 @@
.setCarrierId(in.readInt())
.setSkip464Xlat(in.readInt())
.setAlwaysOn(in.readBoolean())
+ .setInfrastructureBitmask(in.readInt())
.buildWithoutCheck();
}
@@ -1767,6 +1841,7 @@
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 @@
}
/**
+ * 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 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 f346b92..88a32d1 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -431,6 +431,8 @@
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/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 158e065..dc55dd2 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -237,6 +237,12 @@
*/
private transient LinkAddress[] mLinkAddresses6;
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ * @hide
+ */
+ private @NonNull List<LinkAddress> mLinkAddresses7 = new ArrayList<>();
+
+ /**
* When using transient fields for caching it's often also a good idea to initialize them
* lazily.
*
@@ -486,6 +492,8 @@
* Making a field public will suppress getter generation in favor of accessing it directly.
* @param linkAddresses5
* Final fields suppress generating a setter (when setters are requested).
+ * @param linkAddresses7
+ * For hidden lists, getters, setters and adders will be hidden.
* @param stringRes
* Fields with certain annotations are automatically validated in constructor
*
@@ -529,9 +537,10 @@
@State int state,
@NonNull CharSequence charSeq,
@Nullable LinkAddress[] linkAddresses5,
+ @NonNull List<LinkAddress> linkAddresses7,
@StringRes int stringRes,
@android.annotation.IntRange(from = 0, to = 6) int dayOfWeek,
- @Size(2) @NonNull @FloatRange(from = 0f) float[] coords,
+ @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] coords,
@NonNull IBinder token,
@Nullable ICompanionDeviceManager iPCInterface) {
this.mNum = num;
@@ -595,6 +604,9 @@
AnnotationValidations.validate(
NonNull.class, null, charSeq);
this.mLinkAddresses5 = linkAddresses5;
+ this.mLinkAddresses7 = linkAddresses7;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
this.mStringRes = stringRes;
AnnotationValidations.validate(
StringRes.class, null, mStringRes);
@@ -609,13 +621,11 @@
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
this.mToken = token;
AnnotationValidations.validate(
NonNull.class, null, mToken);
@@ -777,6 +787,16 @@
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<LinkAddress> getLinkAddresses7() {
+ return mLinkAddresses7;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -815,7 +835,7 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @Size(2) @NonNull @FloatRange(from = 0f) float[] getCoords() {
+ public @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] getCoords() {
return mCoords;
}
@@ -1065,6 +1085,19 @@
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull SampleDataClass setLinkAddresses7(@NonNull List<LinkAddress> value) {
+ mLinkAddresses7 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
+ return this;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -1111,20 +1144,18 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @NonNull SampleDataClass setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) {
+ public @NonNull SampleDataClass setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
mCoords = value;
AnnotationValidations.validate(
Size.class, null, mCoords.length,
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
return this;
}
@@ -1172,6 +1203,7 @@
"state = " + stateToString(mState) + ", " +
"charSeq = " + charSeq + ", " +
"linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " +
+ "linkAddresses7 = " + mLinkAddresses7 + ", " +
"stringRes = " + mStringRes + ", " +
"dayOfWeek = " + mDayOfWeek + ", " +
"coords = " + java.util.Arrays.toString(mCoords) + ", " +
@@ -1210,6 +1242,7 @@
&& mState == that.mState
&& Objects.equals(charSeq, that.charSeq)
&& java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5)
+ && Objects.equals(mLinkAddresses7, that.mLinkAddresses7)
&& mStringRes == that.mStringRes
&& mDayOfWeek == that.mDayOfWeek
&& java.util.Arrays.equals(mCoords, that.mCoords)
@@ -1241,6 +1274,7 @@
_hash = 31 * _hash + mState;
_hash = 31 * _hash + Objects.hashCode(charSeq);
_hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses5);
+ _hash = 31 * _hash + Objects.hashCode(mLinkAddresses7);
_hash = 31 * _hash + mStringRes;
_hash = 31 * _hash + mDayOfWeek;
_hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
@@ -1270,6 +1304,7 @@
actionInt.acceptInt(this, "state", mState);
actionObject.acceptObject(this, "charSeq", charSeq);
actionObject.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+ actionObject.acceptObject(this, "linkAddresses7", mLinkAddresses7);
actionInt.acceptInt(this, "stringRes", mStringRes);
actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek);
actionObject.acceptObject(this, "coords", mCoords);
@@ -1298,6 +1333,7 @@
action.acceptObject(this, "state", mState);
action.acceptObject(this, "charSeq", charSeq);
action.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+ action.acceptObject(this, "linkAddresses7", mLinkAddresses7);
action.acceptObject(this, "stringRes", mStringRes);
action.acceptObject(this, "dayOfWeek", mDayOfWeek);
action.acceptObject(this, "coords", mCoords);
@@ -1338,7 +1374,7 @@
if (mOtherParcelable != null) flg |= 0x40;
if (mLinkAddresses4 != null) flg |= 0x800;
if (mLinkAddresses5 != null) flg |= 0x10000;
- if (mIPCInterface != null) flg |= 0x200000;
+ if (mIPCInterface != null) flg |= 0x400000;
dest.writeLong(flg);
dest.writeInt(mNum);
dest.writeInt(mNum2);
@@ -1357,6 +1393,7 @@
dest.writeInt(mState);
dest.writeCharSequence(charSeq);
if (mLinkAddresses5 != null) dest.writeTypedArray(mLinkAddresses5, flags);
+ dest.writeParcelableList(mLinkAddresses7, flags);
dest.writeInt(mStringRes);
dest.writeInt(mDayOfWeek);
dest.writeFloatArray(mCoords);
@@ -1395,11 +1432,13 @@
int state = in.readInt();
CharSequence _charSeq = (CharSequence) in.readCharSequence();
LinkAddress[] linkAddresses5 = (flg & 0x10000) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
+ List<LinkAddress> linkAddresses7 = new ArrayList<>();
+ in.readParcelableList(linkAddresses7, LinkAddress.class.getClassLoader());
int stringRes = in.readInt();
int dayOfWeek = in.readInt();
float[] coords = in.createFloatArray();
IBinder token = (IBinder) in.readStrongBinder();
- ICompanionDeviceManager iPCInterface = (flg & 0x200000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
+ ICompanionDeviceManager iPCInterface = (flg & 0x400000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
this.mNum = num;
this.mNum2 = num2;
@@ -1462,6 +1501,9 @@
AnnotationValidations.validate(
NonNull.class, null, charSeq);
this.mLinkAddresses5 = linkAddresses5;
+ this.mLinkAddresses7 = linkAddresses7;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
this.mStringRes = stringRes;
AnnotationValidations.validate(
StringRes.class, null, mStringRes);
@@ -1476,13 +1518,11 @@
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
this.mToken = token;
AnnotationValidations.validate(
NonNull.class, null, mToken);
@@ -1529,9 +1569,10 @@
private @State int mState;
private @NonNull CharSequence charSeq;
private @Nullable LinkAddress[] mLinkAddresses5;
+ private @NonNull List<LinkAddress> mLinkAddresses7;
private @StringRes int mStringRes;
private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek;
- private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords;
+ private @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] mCoords;
private @NonNull IBinder mToken;
private @Nullable ICompanionDeviceManager mIPCInterface;
@@ -1823,6 +1864,30 @@
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLinkAddresses7(@NonNull List<LinkAddress> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20000;
+ mLinkAddresses7 = value;
+ return this;
+ }
+
+ /** @see #setLinkAddresses7 @hide */
+ @DataClass.Generated.Member
+ public @NonNull Builder addLinkAddresses7(@NonNull LinkAddress value) {
+ // You can refine this method's name by providing item's singular name, e.g.:
+ // @DataClass.PluralOf("item")) mItems = ...
+
+ if (mLinkAddresses7 == null) setLinkAddresses7(new ArrayList<>());
+ mLinkAddresses7.add(value);
+ return this;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -1837,7 +1902,7 @@
@DataClass.Generated.Member
public @NonNull Builder setStringRes(@StringRes int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20000;
+ mBuilderFieldsSet |= 0x40000;
mStringRes = value;
return this;
}
@@ -1852,7 +1917,7 @@
@DataClass.Generated.Member
public @NonNull Builder setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x40000;
+ mBuilderFieldsSet |= 0x80000;
mDayOfWeek = value;
return this;
}
@@ -1867,9 +1932,9 @@
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @NonNull Builder setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) {
+ public @NonNull Builder setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x80000;
+ mBuilderFieldsSet |= 0x100000;
mCoords = value;
return this;
}
@@ -1880,7 +1945,7 @@
@DataClass.Generated.Member
public @NonNull Builder setToken(@NonNull IBinder value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x100000;
+ mBuilderFieldsSet |= 0x200000;
mToken = value;
return this;
}
@@ -1891,7 +1956,7 @@
@DataClass.Generated.Member
public @NonNull Builder setIPCInterface(@NonNull ICompanionDeviceManager value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x200000;
+ mBuilderFieldsSet |= 0x400000;
mIPCInterface = value;
return this;
}
@@ -1899,7 +1964,7 @@
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull SampleDataClass build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x400000; // Mark builder used
+ mBuilderFieldsSet |= 0x800000; // Mark builder used
if ((mBuilderFieldsSet & 0x10) == 0) {
mName2 = "Bob";
@@ -1935,18 +2000,21 @@
charSeq = "";
}
if ((mBuilderFieldsSet & 0x20000) == 0) {
- mStringRes = 0;
+ mLinkAddresses7 = new ArrayList<>();
}
if ((mBuilderFieldsSet & 0x40000) == 0) {
- mDayOfWeek = 3;
+ mStringRes = 0;
}
if ((mBuilderFieldsSet & 0x80000) == 0) {
- mCoords = new float[] { 0f, 0f };
+ mDayOfWeek = 3;
}
if ((mBuilderFieldsSet & 0x100000) == 0) {
- mToken = new Binder();
+ mCoords = new float[] { 0f, 0f };
}
if ((mBuilderFieldsSet & 0x200000) == 0) {
+ mToken = new Binder();
+ }
+ if ((mBuilderFieldsSet & 0x400000) == 0) {
mIPCInterface = null;
}
SampleDataClass o = new SampleDataClass(
@@ -1967,6 +2035,7 @@
mState,
charSeq,
mLinkAddresses5,
+ mLinkAddresses7,
mStringRes,
mDayOfWeek,
mCoords,
@@ -1976,7 +2045,7 @@
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x400000) != 0) {
+ if ((mBuilderFieldsSet & 0x800000) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -1984,10 +2053,10 @@
}
@DataClass.Generated(
- time = 1616541539978L,
+ time = 1697693846352L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
- inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
+ inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses7\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 82aa85d..f4f2be6 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -53,7 +53,17 @@
}
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 @@
exclude_srcs: [
":FlickerTestsAppClose-src",
":FlickerTestsIme-src",
- ":FlickerTestsAppLaunch-src",
+ ":FlickerTestsAppLaunch1-src",
+ ":FlickerTestsAppLaunch2-src",
":FlickerTestsQuickswitch-src",
":FlickerTestsRotation-src",
":FlickerTestsNotification-src",
@@ -162,7 +173,8 @@
instrumentation_target_package: "com.android.server.wm.flicker.launch",
srcs: [
":FlickerTestsBase-src",
- ":FlickerTestsAppLaunch-src",
+ ":FlickerTestsAppLaunchCommon-src",
+ ":FlickerTestsAppLaunch2-src",
],
exclude_srcs: [
":FlickerTestsActivityEmbedding-src",
@@ -170,6 +182,39 @@
}
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",
+ ],
+}
+
+android_test {
name: "FlickerTestsNotification",
defaults: ["FlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestNotification.xml"],
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 48d5041..f788efa 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 @@
@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 f575fcc..d86dc50 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.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 93d0520..be07053 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.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 78b58f4..f66eff9 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.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 3f931c4..65214764 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 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 b85362a..4d31c28 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.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 1bdb6e71..42e34b3 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.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 3d9c067..0000000
--- 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 0000000..c854701
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch.common
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+
+abstract class OpenAppFromIconTransition(flicker: LegacyFlickerTest) :
+ OpenAppFromLauncherTransition(flicker) {
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ if (flicker.scenario.isTablet) {
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+ } else {
+ tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+ }
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ }
+ transitions {
+ tapl
+ .goHome()
+ .switchToAllApps()
+ .getAppIcon(testApp.appName)
+ .launch(testApp.packageName)
+ }
+ teardown { testApp.exit(wmHelper) }
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
similarity index 95%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
index 4fc9bcb..9d7096e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
similarity index 97%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
index cc501e6..7b08843 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
similarity index 98%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
index bb11be5..989619e 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 0000000..2e9620b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch.common
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
+ *
+ * Actions:
+ * ```
+ * Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
+ * by clicking it's icon on all apps, and wait for transfer splash screen complete
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Verify no flickering when transfer splash screen to app window.
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
+ OpenAppFromIconTransition(flicker) {
+ override val testApp = TransferSplashscreenAppHelper(instrumentation)
+
+ /**
+ * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+ * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
+ * visible until the end
+ */
+ @Presubmit
+ @Test
+ fun appWindowAfterSplash() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
+ .then()
+ .isAppWindowOnTop(testApp)
+ .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+ }
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun focusChanges() {
+ super.focusChanges()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() {
+ super.appWindowReplacesLauncherAsTopWindow()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowAsTopWindowAtEnd() {
+ super.appWindowAsTopWindowAtEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesTopWindow() {
+ super.appWindowBecomesTopWindow()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesVisible() {
+ super.appWindowBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowIsTopWindowAtEnd() {
+ super.appWindowIsTopWindowAtEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerBecomesVisible() {
+ super.appLayerBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerReplacesLauncher() {
+ super.appLayerReplacesLauncher()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun cujCompleted() {
+ super.cujCompleted()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun entireScreenCovered() {
+ super.entireScreenCovered()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() {
+ super.navBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarWindowIsAlwaysVisible() {
+ super.navBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarWindowIsVisibleAtStartAndEnd() {
+ super.navBarWindowIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ super.statusBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() {
+ super.statusBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ super.taskBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+}
diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
index 49cbde0..d2537f6 100644
--- a/tests/FsVerityTest/AndroidTest.xml
+++ b/tests/FsVerityTest/AndroidTest.xml
@@ -24,7 +24,7 @@
<!-- This test requires root to write against block device. -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="FsVerityTestApp.apk"/>
<option name="cleanup-apks" value="true"/>
</target_preparer>
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 6857333..d3a8b03 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -327,7 +327,8 @@
+"return$maybeCast this;"
}
- val javadocSeeSetter = "/** @see #$setterName */"
+ val javadocSeeSetter =
+ if (isHidden()) "/** @see #$setterName @hide */" else "/** @see #$setterName */"
val adderName = "add$SingularName"
val singularNameCustomizationHint = if (SingularNameOrNull == null) {
@@ -750,6 +751,15 @@
}
}
+fun FieldInfo.isHidden(): Boolean {
+ if (javadocFull != null) {
+ (javadocFull ?: "/**\n */").lines().forEach {
+ if (it.contains("@hide")) return true
+ }
+ }
+ return false
+}
+
fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
if (javadocFull != null || forceHide) {
var hidden = false
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 01f1591..3d5a0f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -16,6 +16,7 @@
package android.net.wifi.sharedconnectivity.app;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -169,6 +170,7 @@
* @return Returns the Builder object.
*/
@NonNull
+ @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
public Builder setBatteryCharging(boolean isBatteryCharging) {
mIsBatteryCharging = isBatteryCharging;
return this;
@@ -283,6 +285,7 @@
*
* @return Returns true if the battery of the remote device is charging.
*/
+ @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
public boolean isBatteryCharging() {
return mIsBatteryCharging;
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 71ac94b..b0f68f7 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -17,6 +17,7 @@
package android.net.wifi.sharedconnectivity.app;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -296,6 +297,7 @@
*/
@TestApi
@NonNull
+ @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
public BroadcastReceiver getBroadcastReceiver() {
return mBroadcastReceiver;
}